精华内容
下载资源
问答
  • 唯一ID生成算法剖析

    千次阅读 2019-10-08 14:21:58
    引在业务开发中,大量场景需要唯一ID来进行标识:用户需要唯一身份标识;商品需要唯一标识;消息需要唯一标识;事件需要唯一标识…等等,都需要全局唯一ID,尤其是分布式场景下。...
        
    640?wx_fmt=gif

    在业务开发中,大量场景需要唯一ID来进行标识:用户需要唯一身份标识;商品需要唯一标识;消息需要唯一标识;事件需要唯一标识…等等,都需要全局唯一ID,尤其是分布式场景下。

    唯一ID有哪些特性或者说要求呢?按照我的分析有以下特性:
    • 唯一性:生成的ID全局唯一,在特定范围内冲突概率极小
    • 有序性:生成的ID按某种规则有序,便于数据库插入及排序
    • 可用性:可保证高并发下的可用性
    • 自主性:分布式环境下不依赖中心认证即可自行生成ID
    • 安全性:不暴露系统和业务的信息

    一般来说,常用的唯一ID生成方法有这些:

    UUID:
    • 基于时间戳&时钟序列生成
    • 基于名字空间/名字的散列值 (MD5/SHA1) 生成
    • 基于随机数生成

    数据库自增ID:
    • 多台机器不同初始值、同步长自增
    • 批量缓存自增ID

    雪花算法
    • 时钟回拨解决方案
    • 本文便分别对这些算法进行讲解及分析。

    UUID
    UUID全称为:Universally Unique IDentifier(通用唯一识别码),有的地方也称作GUID(Globally Unique IDentifier),实际上GUID指微软对于UUID标准的实现的实现。

    UUID算法的目的是为了生成某种形式的全局唯一ID来标识系统中的任一元素,尤其在分布式环境下,该ID需要不依赖中心认证即可自动生成全局唯一ID。
    其优势有:
    • 无需网络,单机自行生成
    • 速度快,QPS高(支持100ns级并发)
    • 各语言均有相应实现库供直接使用

    而缺点为:
    • String存储,占空间,DB查询及索引效率低
    • 无序,可读性差
    • 根据实现方式不同可能泄露信息

    1.UUID的格式

    UUID的标准形式为32个十六进制数组成的字符串,且分隔为五个部分,如:

    467e8542-2275-4163-95d6-7adc205580a9

    各部分的数字个数为:8-4-4-4-12

    2.UUID版本

    根据需要不同,标准提供了不同的UUID版本以供使用,分别对应于不同的UUID生成规则:
    • 版本1 - 基于时间的UUID:主要依赖当前的时间戳及机器mac地址,因此可以保证全球唯一性
    • 版本2 - 分布式安全的UUID:将版本1的时间戳前四位换为POSIX的UID或GID,很少使用
    • 版本3 - 基于名字空间的UUID(MD5版):基于指定的名字空间/名字生成MD5散列值得到,标准不推荐
    • 版本4 - 基于随机数的UUID:基于随机数或伪随机数生成,
    • 版本5 - 基于名字空间的UUID(SHA1版):将版本3的散列算法改为SHA1

    3.UUID各版本优缺点

    版本1 - 基于时间的UUID:
    • 优点:能基本保证全球唯一性
    • 缺点:使用了Mac地址,因此会暴露Mac地址和生成时间

    版本2 - 分布式安全的UUID:
    • 优点:能保证全球唯一性
    • 缺点:很少使用,常用库基本没有实现

    版本3 - 基于名字空间的UUID(MD5版):
    • 优点:不同名字空间或名字下的UUID是唯一的;相同名字空间及名字下得到的UUID保持重复。
    • 缺点:MD5碰撞问题,只用于向后兼容,后续不再使用

    版本4 - 基于随机数的UUID:
    • 优点:实现简单
    • 缺点:重复几率可计算

    版本5 - 基于名字空间的UUID(SHA1版):
    • 优点:不同名字空间或名字下的UUID是唯一的;相同名字空间及名字下得到的UUID保持重复。
    • 缺点:SHA1计算相对耗时

    总得来说:
    • 版本 1/2 适用于需要高度唯一性且无需重复的场景;
    • 版本 3/5 适用于一定范围内唯一且需要或可能会重复生成UUID的环境下;
    • 版本 4 适用于对唯一性要求不太严格且追求简单的场景。

    4.UUID结构及生成规则

    以版本1 - 基于时间的UUID为例先梳理UUID的结构:

    UUID为32位的十六机制数,因此实际上是16-byte (128-bit),各位分别为:
    640?wx_fmt=png

    时间值:在基于时间的UUID中,时间值是一个60位的整型值,对应UTC的100ns时间间隔计数,因此其支持支持一台机器每秒生成10M次。在UUID中,将这60位放置到了15~08这8-byte中(除了09位有4-bit的版本号内容)。
    版本号:版本号即上文所说的五个版本,在五个版本的UUID中,都总是在该位置标识版本,占据 4-bit,分别以下列数字表示:
    640?wx_fmt=png

    因此版本号这一位的取值只会是1,2,3,4,5

    变体值:表明所依赖的标准(X表示可以是任意值):
    640?wx_fmt=png

    时钟序列:在基于时间的UUID中,时钟序列占据了07~06位的14-bit。不同于时间值,时钟序列实际上是表示一种逻辑序列,用于标识事件发生的顺序。在此,如果前一时钟序列已知,则可以通过自增来实现时钟序列值的改变;否则,通过(伪)随机数来设置。主要用于避免因时间值向未来设置或节点值改变可能导致的UUID重复问题。

    节点值:在基于时间的UUID中,节点值占据了05~00的48-bit,由机器的MAC地址构成。如果机器有多个MAC地址,则随机选其中一个;如果机器没有MAC地址,则采用(伪)随机数。

    了解了基于时间的UUID结构及生成规则后,再看看其他版本的UUID生成规则:
    • 版本2 - 分布式安全的UUID:

    将基于时间的UUID中时间戳前四位换为POSIX的UID或GID,其余保持一致。

    • 版本3/5 - 基于名字空间的UUID (MD5/SHA1):
    1. 将命名空间 (如DNS、URL、OID等) 及名字转换为字节序列;
    2. 通过MD5/SHA1散列算法将上述字节序列转换为16字节哈希值 (MD5散列不再推荐,SHA1散列的20位只使用其15~00位);
    3. 将哈希值的 3~0 字节置于UUID的15~12位;
    4. 将哈希值的 5~4 字节置于UUID的11~10位;
    5. 将哈希值的 7~6 字节置于UUID的09~08位,并用相应版本号覆盖第9位的高4位 (同版本1位置);
    6. 将哈希值的 8 字节置于UUID的07位,并用相应变体值覆盖其高2位 (同版本1位置);
    7. 将哈希值的 9 字节置于UUID的06位 (原时钟序列位置);
    8. 将哈希值的 15~10 字节置于UUID的05~00位 (原节点值位置)。

    • 版本4 - 基于随机数的UUID:
    1. 生成16byte随机值填充UUID。重复机率与随机数产生器的质量有关。若要避免重复率提高,必须要使用基于密码学上的假随机数产生器来生成值才行;
    2. 将变体值及版本号填到相应位置。

    5.多版本伪码

    // 版本 1 - 基于时间的UUID:	
    gen_uuid() {	
        struct uuid uu;	
    
    	
        // 获取时间戳	
        get_time(&clock_mid, &uu.time_low);	
        uu.time_mid = (uint16_t) clock_mid; // 时间中间位	
        uu.time_hi_and_version = ((clock_mid >> 16) & 0x0FFF) | 0x1000; // 时间高位 & 版本号	
    
    	
        // 获取时钟序列。在libuuid中,尝试取时钟序列+1,取不到则随机;在python中直接使用随机	
        get_clock(&uu.clock_seq);// 时钟序列+1 或 随机数	
        uu.clock_seq |= 0x8000;// 时钟序列位 & 变体值	
    
    	
        // 节点值	
        char node_id[6];	
        get_node_id(node_id);// 根据mac地址等获取节点id	
        uu.node = node_id;	
        return uu;	
    }	
    
    	
    // 版本4 - 基于随机数的UUID:	
    gen_uuid() {	
        struct uuid uu;	
        uuid_t buf;	
    
    	
        random_get_bytes(buf, sizeof(buf));// 获取随机出来的uuid,如libuuid根据进程id、当日时间戳等进行srand随机	
    
    	
        uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000;// 变体值覆盖	
        uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x4000;// 版本号覆盖	
        return uu;	
    }	
    
    	
    // 版本5 - 基于名字空间的UUID(SHA1版):	
    gen_uuid(name) {	
        struct uuid uu;	
        uuid_t buf;	
    
    	
        sha_get_bytes(name, buf, sizeof(buf));// 获取name的sha1散列出来的uuid	
    
    	
        uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000;// 变体值覆盖	
        uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x5000;// 版本号覆盖	
        return uu;	
    }

    (左滑查看完整代码)

    数据库自增ID
    数据库自增ID可能是大家最熟悉的一种唯一ID生成方式,其具有使用简单,满足基本需求,天然有序的优点,但也有缺陷:

    • 并发性不好
    • 数据库写压力大
    • 数据库故障后不可使用
    • 存在数量泄露风险

    因此这里给出两种优化方案。

    1. 数据库水平拆分,设置不同的初始值和相同的步长

    640?wx_fmt=png

    如图所示,可保证每台数据库生成的ID是不冲突的,但这种固定步长的方式也会带来扩容的问题,很容易想到当扩容时会出现无ID初始值可分的窘境,解决方案有:
    • 根据扩容考虑决定步长
    • 增加其他位标记区分扩容

    这其实都是在需求与方案间的权衡,根据需求来选择最适合的方式。

    2.批量生成一批ID

    如果要使用单台机器做ID生成,避免固定步长带来的扩容问题,可以每次批量生成一批ID给不同的机器去慢慢消费,这样数据库的压力也会减小到N分之一,且故障后可坚持一段时间。
    640?wx_fmt=png

    如图所示,但这种做法的缺点是服务器重启、单点故障会造成ID不连续。还是那句话,没有最好的方案,只有最适合的方案。

    雪花算法
    定义一个64bit的数,对指定机器 & 同一时刻 & 某一并发序列,是唯一的,其极限QPS约为400w/s。其格式为:
    640?wx_fmt=png
    640?wx_fmt=png

    将64 bit分为了四部分。其中时间戳有时间上限(69年)。机器id只有10位,能记录1024台机器,常用前几位表示数据中心id,后几位表示数据中心内的机器id。序列号用来对同一个毫秒之内的操作产生不同的ID,最多4095个。

    这种结构是雪花算法提出者Twitter的分法,但实际上这种算法使用可以很灵活,根据自身业务的并发情况、机器分布、使用年限等,可以自由地重新决定各部分的位数,从而增加或减少某部分的量级。比如百度的UidGenerator、美团的Leaf等,都是基于雪花算法做一些适合自身业务的变化。

    由于雪花算法是强依赖于时间的,在分布式环境下,如果发生时钟回拨,很可能会引起id冲突的问题。解决方案有:
    • 将ID生成交给少量服务器,并关闭时钟同步。
    • 直接报错,交给上层业务处理。
    • 如果回拨时间较短,在耗时要求内,比如5ms,那么等待回拨时长后再进行生成。
    • 如果回拨时间很长,那么无法等待,可以匀出少量位(1~2位)作为回拨位,一旦时钟回拨,将回拨位加1,可得到不一样的ID,2位回拨位允许标记三次时钟回拨,基本够使用。如果超出了,可以再选择抛出异常。

    方案对比
    可以发现,常用的分布式唯一ID生成思路基本是利用一个长串数字或字符串,将其分割成多个部分,分别记录时间信息、机器/名字信息、随机信息、序列信息等。时间信息部分决定了该策略能使用的时长,机器/名字信息支持了分布式环境下的独自生成唯一ID与识别能力,序列信息保证了事件的顺序记录以及同一时间单位下的并发数,而随机信息则加大了ID整体的不可识别性。

    实际上如果现有的方法依然不能满足,我们完全可以依据自身业务和发展需求,来自行决定使用何种策略生成唯一ID。各种方案都有其优缺点,技术的使用没有绝对的好坏之分,主要在于是否适合使用场景:
    • 要求生成全局唯一且不会重复ID,不关心顺序 —— 使用基于时间的UUID(如游戏聊天室中不同用户的身份ID)
    • 要求生成唯一ID,具有名称不可变性,可重复生成 —— 使用基于名称哈希的UUID(如基于不可变信息生成的用户ID,若不小心删除,仍可根据信息重新生成同一ID)
    • 要求生成有序且自然增长的ID —— 使用数据库自增ID(如各业务操作流水ID,高并发下可参考优化方案)
    • 要求生成数值型无序定长ID —— 使用雪花算法(如对存储空间、查询效率、传输数据量等有较高要求的场景)

    对于最初我们定义的唯一ID特性,各方案的对比如下:
    640?wx_fmt=png

    从冲突率、QPS和算法时间复杂度来比较的话:
    640?wx_fmt=png
    展开全文
  • 如何保证数据库表中数据的唯一

    千次阅读 2019-09-11 10:42:55
    在很多时候,我们需要保证数据库表中某条数据是唯一的,那如何保证数据库表中数据的唯一性呢?这个值得探讨一下

    问题描述:在之前的应用开发中,有这么一个需求,多个用户会对阅读内容提出自己的意见,后端采用不记名方式,前者提的意见会覆盖后者的意见。 系统在运行过程中出现过一个问题,二个用户都对同一篇文章提意见,时间相同,前端在用户的二个客户端浏览器下分别获取到数据库中都不存在这条记录,所以当用户同时点击保存的时候数据库中会有针对这篇文章的两条数据,这与实际业务规定的不符合。

    针对这个问题可能出现的原因咱们来分析一下原因:
    (1)最不靠谱的情况是,A用户发现数据库没有数据,编辑完后产生一条,同时B用户也发现从来没有用户编辑过这条数据,从而也产生一条数据,此时前端已经存在二条相关数据了,后端没有任何判断和校验,直溜溜的插入库里,这种一般程序员应该不会犯;
    (2)在(1)一样的前提下,产生二条相关数据,程序员在service中先查一个数据是否存在,如果不存在相关数据就插入,存在则更新原有记录,这种会很大程度的解决重复数据的问题。但是应付不了多并发的情况,大家都知道Mysql支持可重复读模式,当第一个线程已经插入但是还没有提交的时候,另一个事务在执行期间是读不到这个数据的,还是会执行插入,谁也不能保证他们不是同时commit的,这样数据库中又出现了两条数据,此时问题无法解决;
    (3)有的程序员暴脾气,没招就只能把这个任务交给数据库拜,给数据库表加唯一索引,看它还插入,当然,这种不管不顾的做法确实保证了最终的数据没问题,但是这是以牺牲程序的可用性为前提的,没有经验的程序员还有可能把这个错误留给前端,客户都能看到异常,更有可能导致参与的事务不回滚,造成其他数据问题。无论怎么样一句话就是本次用户白干了拜而且还发现系统出问题了,暴脾气的用户一个电话就找过来了,这样很不好啊!

    最终解决方案
    synchronized 参数从来都是为控制并发设计的,当然这个是以牺牲性能为前提的,一个系统你得先保证数据对的前提下,你才能去保证性能吧。有的同学直接随意的在service的方法上加入这个synchronized 参数,这太随意了,其实他没注意到自己的方法上加了@Transactional参数开启了事务,大家都知道事务是通过AOP动态代理实现的,执行这个方法之前,先打开数据库连接,开启事务,当进入这个方法的时候,锁才开始起作用,当这个方法退出,事务还是没有结束,还没有commit提交的情况下,另一个事务执行的进度就赶上了了,又可以同时提交,最终出现2条数据,那问题出现在哪呢?
    问题是你这个synchronized 应该加到调用这个service的地方,让锁能够控制整个事务提交过程,这样就能保证数据的唯一性了,所以有些事情如果程序能够解决的不要交给数据库处理,如果哪天来个新手整错脚本,漏掉了这个校验,还是会有脏数据入库,你只能苦哈哈的在一片声讨声中处理历史数据,怎一个惨字了得!!!
    如果大家有别的更好更优雅的办法也请不吝赐教。

    展开全文
  • iOS设备唯一标识探讨

    千次阅读 2016-05-16 15:29:55
    首先iOS中获取设备唯一标示符的方法一直随版本的更新而变化。iOS 2.0版本以后UIDevice提供一个获取设备唯一标识符的方法uniqueIdentifier,通过该方法我们可以获取设备的序列号,这个也是目前为止唯一可以确认唯一的...
          首先iOS中获取设备唯一标示符的方法一直随版本的更新而变化。iOS 2.0版本以后UIDevice提供一个获取设备唯一标识符的方法uniqueIdentifier,通过该方法我们可以获取设备的序列号,这个也是目前为止唯一可以确认唯一的标示符。好景不长,因为该唯一标识符与手机一一对应,苹果觉得可能会泄露用户隐私,所以在 iOS 5.0之后该方法就被废弃掉了;iOS 6.0系统新增了两个用于替换uniqueIdentifier的接口,分别是:identifierForVendor,advertisingIdentifier,但这两个接口会在应用重新安装时改变数值,并不是唯一的标示符,所以开发者改为使用WiFi的mac地址来取代;iOS
     7中苹果又封杀mac地址,所以开发者再次改变思路使用KeyChain来保存获取到的UDID,这样以后即使APP删了再装回来,也可以从KeyChain中读取回来。

    首先保存设备的UUID,可以使用类方法+ (id)UUID 是一个类方法,调用该方法可以获得一个UUID。通过下面的代码可以获得一个UUID字符串:

     NSString *uuid = [[NSUUID UUID] UUIDString];

    也可以保存在iOS 6中新增的Vindor标示符 (IDFV-identifierForVendor),获取这个IDFV的新方法被添加在已有的UIDevice类中。跟advertisingIdentifier一样,该方法返回的是一个NSUUID对象。

    NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];

    如果用户卸载了同一个vendor对应的所有程序,然后在重新安装同一个vendor提供的程序,此时identifierForVendor会被重置,所以这里要用到KeyChain来保存。
    KeyChain(钥匙串)是使用苹果设备经常使用的,通常要调试的话,都得安装证书之类的,这些证书就是保存在KeyChain中,还有我们平时浏览网页记录的账号密码也都是记录在KeyChain中。iOS中的KeyChain相比OS X比较简单,整个系统只有一个KeyChain,每个程序都可以往KeyChain中记录数据,而且只能读取到自己程序记录在KeyChain中的数据。iOS中Security.framework框架提供了四个主要的方法来操作KeyChain:

    • SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result);//查询OSStatus
    • SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result); //添加OSStatus
    • SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate);//更新KeyChain中的ItemOSStatus
    • SecItemDelete(CFDictionaryRef query)//删除KeyChain中的ItemOSStatus

      这四个方法参数比较复杂,一旦传错就会导致操作KeyChain失败,文档中介绍的比较详细,大家可以查查官方文档。而苹果提供的KeyChain使用起来略麻烦,所以这里推荐一个第三方库SSKeyChains.SSKeyChains对苹果安全框架API进行了简单封装,支持对存储在钥匙串中密码、账户进行访问,包括读取、删除和设置。SSKeyChains使用简单,通过实例代码便可掌握。


    原文链接:http://www.jianshu.com/p/7ad22ca88b83

    接下来我想跟大家探讨的是如何通过“合法”的手段来尽量拿到不会轻易发生变化的“唯一标识”。

    移动统计平台都用啥来标识设备呢?
    研究了下国内两大移动统计平台友盟和TalkingData 的SDK
    友盟用的是 Openudid + IDFA
    TalkingData用的是 Keychain + IDFA

    Openudid

    Openudid是一个github上一个开源的项目: 地址
    原理是利用iOS系统中的UIPasteboard剪贴板类,它用 app-special pastboards 来存储一160位的随机字符串,存取的方式类似字典的key-value。 app-special pastboards 可持久存储字符串,即使开关机、卸载应用,并能在app之间共享。Openudid的第一次访问的时候用key去检查剪贴板内是否存在对应的value(随机数),如果不存在就生成一个并存储在Pasteboard中,第二次访问的时候就可以直接取到而不去生成新的随机数。
    但是iOS7之后,苹果封堵了剪贴板通信的漏洞,iOS之前是所有的应用都可以共享同一个剪贴板存储内容,现在只有在同一CFBundleIdentifier标识下的App才能共享内容,如com.koudai.a和com.koudai.b,它们的com.mycompany部分是一样的,就能共享(请用真机测试,模拟器会有偏差)。

    Keychain

    keychain中文翻译为钥匙串,是苹果用来存储密码和证书的一块加密存储区域,目的是为了帮助用户安全存储应用或者浏览器的密码,省去了很多输入密码和记密码的麻烦。keychain不是存储在手机的沙盒内,而是手机的某个公共区域,手机重启和应用卸载,都不会对这片存储区域造成影响,因为是加密存储不存在被其他应用修改的问题,所以就有人拿keychain来存储唯一标识。
    再Max OS上访问keychian需要提供用户的登录密码,而在iOS上用户原则上只能访问本应用存储的keychain,除非是同一个provisioning 证书的两个应用,比如美团的猫眼就能读取美团app中的keychian,用户第一次打开猫眼app就会弹出提示,用户可以读取美团的账号和密码免登录进入猫眼。keychain是根据provision 证书来鉴定权限,所以app的版本需要使用同一个,否则版本之间会失效。用户恢复出厂设置,机器上的keychain会被清除,但如果事先对手机进行了备份,keychain存储的内容依然有效。顺便提一句keychain在越狱的机器上是可以被导出的,所以存储敏感信息前请加密。

    NSUUID

    上节keychain把存储的问题解决了,那存什么来保证每个设备的id都是唯一的呢?大部分应用存的是UUID,它是苹果再iOS6后提供的一个获取大随机数的方法。UUID, 全球独立标识(Globally Unique Identifier),据wiki说UUID随机数算法得到的数重复概率为170亿分之一,170亿分之一什么概念?可以告诉你买一注双色球的中奖概率是1700万分之一。随机算法有几套,包括用时间戳、MD5什么的,苹果是遵循的RFC 4122 version 4,大家可以去google下。

    NSString *uuid = [[NSUUID UUID] UUIDString];

    每次调用此方法得到的UUID肯定是不一样的,所以必须借助于持久化存储。

    IDFA

    IDFA这是iOS 6中另外一个新的方法,是AdSupport.framework框架中ASIdentifierManager单例提供了一个方法advertisingIdentifier,通过调用该方法会返回一个的NSUUID实例。广告标示符也是由系统存储着的,还原系统和在(设置程序-> 通用 -> 关于本机 -> 广告 -> 还原广告标示符)设置中重置都会被还原。苹果对IDFA的使用做了限制,使用IDFA但未集成任何广告服务的应用审核都会被拒,应用中使用了IDFA的话,必须在iTunes Connect中的上传页面进行相应的设置,否则上传应用审核的时候会出现错误。

    当然还有许多其他的ID可以用来标识设备,我文中提到了只是自己认为相对靠谱的方法,如果让我选择的话,我会用keychain存储NSUUID的方法,毕竟一个用户重置系统是小概率事件,即使有人真的想伪造用户量做一次系统重置的成本非常高。如果大家有更好的获取iOS设备唯一标示的方法,不妨留言讨论。


    原文链接:http://www.jianshu.com/p/b83b0240bd0e
    著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

    展开全文
  • 我却觉得如果新手不辨真假,盲目顺从,那么会造成误人子弟的事实。 首先从作者的写这篇文章的目的上讲他想实现的无非是下面目的: 1、不用自增长ID,因为自增长移植的时候不方便。 2、这个存储过程可以很高效的...

    关于生成并发唯一性流水号的解决方案 
      看了文章《弃用数据库自增ID,曝光一下我自己用到的解决方法 》,居然还显示到首页上去。我却觉得如果新手不辨真假,盲目顺从,那么会造成误人子弟的事实。
    首先从作者的写这篇文章的目的上讲他想实现的无非是下面目的:
    1、不用自增长ID,因为自增长移植的时候不方便。
    2、这个存储过程可以很高效的产生唯一性的自增长ID
     
    从我小虎的认知上来回答:
    1、对于作者的第一点,完全可以用Guid来替代自增长,或者在移植的时候,可以先去掉自增长的属性。
    有的人说Guid性能比不上自增长ID,这里我们先不讨论这一点,个人认为效率问题主要体现在索引技巧上。
    2、关键是作者的第二点,完全是不正确的,也是我写这篇文章的首要目的。因为这个存储过程根本就没有实现在多并发(多用户)的情况
    下能真正产生唯一性的主键ID。
    我们看原作者的代码:
     

    SQL code
    1create procedure [dbo].[up_get_table_key] 2( 3 @table_name varchar(50), 4 @key_value int output 5) 6as 7begin 8 begin tran 9 declare @key int 10 11 --initialize the key with 1 12 set @key=1 13 --whether the specified table is exist 14 if not exists(select table_name from table_key where table_name=@table_name) 15 begin 16 insert into table_key values(@table_name,@key) --default key vlaue:1 17 end 18 -- step increase 19 else 20 begin 21 select @key=key_value from table_key with (nolock) where table_name=@table_name 22 set @key=@key+1 23 --update the key value by table name 24 update table_key set key_value=@key where table_name=@table_name 25 end 26 --set ouput value 27 set @key_value=@key 28 29 --commit tran 30 commit tran 31 if @@error>0 32 rollback tran 33end



    请看我的测试代码以及并发结果图 

    C# code
    protected void Page_Load(object sender, EventArgs e) ...{ if (!IsPostBack) ...{ for (int i = 0; i < 100; i++) ...{ System.Threading.Thread temp3 = new System.Threading.Thread(new System.Threading.ThreadStart(Run3)); temp3.Start(); } } } private void Run3() ...{ System.Data.SqlClient.SqlParameter[] p = ...{ new System.Data.SqlClient.SqlParameter("@table_name", "test"), new System.Data.SqlClient.SqlParameter("@key_value",System.Data.SqlDbType.Int) }; p[1].Direction = System.Data.ParameterDirection.Output; SqlHelper.ExecuteStoredProcedure("up_get_table_key", p); Response.Write(p[1].Value.ToString() + "<br/>"); }


    结果图1
     

    从上面多线程的测试效果上来说,绝对不要去按照原作者的方法去做。
      


    --------------------------------------------------------------------------------

     


    本来这么晚了,我不想在写了,但是不想让别人说我不厚道,说我只说不做,所以,我打算就再写一个切实可行的例子,供大家参考,仅仅作为抛砖引玉。
    但是本人是经过多线程测试的,至少在我测试情况下不会出现并发出差错的情况。
    1、表结构和效果图,这个表是用来存储基础因子的,需要的可以拓展字段,比如,升序,降序,起始序号等。
     

    SQL code
    CREATE TABLE [dbo].[SerialNo]( [sCode] [varchar](50) NOT NULL,--主键也是多个流水号的类别区分 [sName] [varchar](100) NULL,--名称,备注形式 [sQZ] [varchar](50) NULL,--前缀 [sValue] [varchar](80) NULL,--因子字段 CONSTRAINT [PK_SerialNo] PRIMARY KEY CLUSTERED ( [sCode] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]

     



    2、存储过程代码 

     

    SQL code
    1Create procedure [dbo].[GetSerialNo] 2( 3 @sCode varchar(50) 4) 5 6 as 7 8--exec GetSerialNo 9 10begin 11 12 Declare @sValue varchar(16), 13 14 @dToday datetime, 15 16 @sQZ varchar(50) --这个代表前缀 17 18 Begin Tran 19 20 Begin Try 21 22 -- 锁定该条记录,好多人用lock去锁,起始这里只要执行一句update就可以了 23 --在同一个事物中,执行了update语句之后就会启动锁 24 Update SerialNo set sValue=sValue where sCode=@sCode 25 26 Select @sValue = sValue From SerialNo where sCode=@sCode 27 28 Select @sQZ = sQZ From SerialNo where sCode=@sCode 29 30 -- 因子表中没有记录,插入初始值 31 32 If @sValue is null 33 34 Begin 35 36 Select @sValue = convert(bigint, convert(varchar(6), getdate(), 12) + '000001') 37 38 Update SerialNo set sValue=@sValue where sCode=@sCode 39 40 end else 41 42 Begin --因子表中没有记录 43 44 Select @dToday = substring(@sValue,1,6) 45 46 --如果日期相等,则加1 47 48 If @dToday = convert(varchar(6), getdate(), 12) 49 50 Select @sValue = convert(varchar(16), (convert(bigint, @sValue) + 1)) 51 52 else --如果日期不相等,则先赋值日期,流水号从1开始 53 54 Select @sValue = convert(bigint, convert(varchar(6), getdate(), 12) +'000001') 55 56 57 58 Update SerialNo set sValue =@sValue where sCode=@sCode 59 60 End 61 62 Select result = @sQZ+@sValue 63 64 Commit Tran 65 66 End Try 67 68 Begin Catch 69 70 Rollback Tran 71 72 Select result = 'Error' 73 74 End Catch 75 76end 77 78



     废话不多说了,看测试代码和效果图
     


    第一张图(左)是单独对进货单执行循环多进程 
    第二张图(中)是单独对发货单执行循环多进程
    第三张图(右)是对进货单发货单同时执行循环多进程
    也就是上面三个Thread,自己注释测试就可以了。

     

    测试并发代码

    C# code
    1protected void Page_Load(object sender, EventArgs e) 2 ...{ 3 if (!IsPostBack) 4 ...{ 5 for (int i = 0; i < 100; i++) 6 ...{ 7 System.Threading.Thread temp = new System.Threading.Thread(new System.Threading.ThreadStart(Run)); 8System.Threading.Thread temp2 = new System.Threading.Thread(new System.Threading.ThreadStart(Run2)); 9 System.Threading.Thread temp3 = new System.Threading.Thread(new System.Threading.ThreadStart(Run3)); 10 temp.Start(); 11 temp2.Start(); 12 temp3.Start(); 13 } 14 } 15 } 16 17 private void Run() 18 ...{ 19System.Data.SqlClient.SqlParameter[] p = ...{ 20 new System.Data.SqlClient.SqlParameter("@sCode", "JHD") }; 21 Response.Write(SqlHelper.ExecuteStoredProcedure("GetSerialNo", p).Rows[0][0].ToString() + "<br/>"); 22 } 23 private void Run2() 24 ...{ 25 System.Data.SqlClient.SqlParameter[] p = ...{ 26 new System.Data.SqlClient.SqlParameter("@sCode", "XSD") }; 27 Response.Write(SqlHelper.ExecuteStoredProcedure("GetSerialNo", p).Rows[0][0].ToString() + "<br/>"); 28 } 29 private void Run3() 30 ...{ 31 System.Data.SqlClient.SqlParameter[] p = ...{ 32 new System.Data.SqlClient.SqlParameter("@table_name", "test"), 33 new System.Data.SqlClient.SqlParameter("@key_value",System.Data.SqlDbType.Int) }; 34 p[1].Direction = System.Data.ParameterDirection.Output; 35 SqlHelper.ExecuteStoredProcedure("up_get_table_key", p); 36 Response.Write(p[1].Value.ToString() + "<br/>"); 37 } 38


     

    总结:我写的整个方法和存储过程如果要实现流水号的话,还是相当可以的。在当前测试过程中是可以避免并发而导致数据的同步性出错的情况。

    展开全文
  • 升级IE7.0的唯一理由

    万次阅读 热门讨论 2006-10-19 22:53:00
     虽然IE7较6.0升级了很多,界面变化也很大,但吸引我的唯一亮点就是开始支持TAB(网页标签),在一个窗口可以选择浏览多个网页,确实方便了不少。其实,基于IE核心的MyIE(后续的Maxthon)当年能吸引我使用,主要是...
  • "唯一不变的就是变化"一两年前这话我说给别人听,现在别人说给我听需求当然是容易变化的,但是在有时候还是要区别对待,当心这么种理念对项目造成大的伤害首先,需求分析不到位即开始设计实现,名曰"以后反正要变的" -...
  • 在我建立外键的时候发生了这样的错误,猜测可能是跟主键有关系我的语句是这么写的: constraint for_score_students foreign key(studentid) references students(studentid)错误提示在了红色s处说明: 1 这语句...
  • 蚂蚁金服是我认为目前唯一各一个可以称得上综合金融生态的新物种。除此之外腾讯、百度、京东都有潜力形成互联网金融生态,但还有很大的差距。腾讯主要是社交数据使用的“度”的把握上未能找到突破口从而迟迟不会有结...
  • 簡述改善命運的原理與唯一的方法

    千次阅读 2011-07-08 21:38:50
    簡述改善命運的原理與唯一的方法——明因果,改命運 香港著名命理大师 刘易荣 不少人在遇到运程阻滞、生意失败、事业不利,或婚姻受到挫折,或病魔缠身,甚至多灾多难时,都会来找我占卜,问问今后的
  • 【刘晓燕语法长难句】 并列

    千次阅读 多人点赞 2020-05-12 22:50:16
    并列一、什么是并列?二、常见的并列连词三、并列的考点分析1. 写作2. 完型3. 长难句分析 一、什么是并列? 并列就是用连词连接两个句子。 I love you, you love the dog. 这话是错误的,逗号不能连接两...
  • 首先我来提出个问题,怎么在分布式系统中生成唯一性id并保持该id大致自增?在twitter中这是最重要的业务场景,于是twitter推出了一种snowflake算法。参考地址:https://github.com/twitter/snowflake 小镇为什么想...
  • 教你如何实现荣耀3C支持OTG功能,解决荣耀3C的唯一缺点 转自 朕爱3C 前言: OTG是手机非常非常实用的功能,能接移动硬盘、U盘(这个功能相当于你的手机可以无限扩展内存卡了,什么8G16G32G128G内存卡都是浮云...
  • 淘宝客服话术900

    万次阅读 2019-05-20 14:08:01
    客服对于买家的每话都要敏感,因为这里面有买家传达给你的情报信息,了解了情报,你就能更好的引导买家,控制沟通过程; 对于买家表露的信息动向,客服要根据自家店铺及货品的实际情况做应对: 沟通要精力集中,...
  • 145经典诗句

    万次阅读 2012-06-16 00:13:23
    自己造成的罪孽可就无处可逃。多被引用自作自受时的感受。也可以理解为:人的命运是可以改造的,所以“天作孽,犹可违”,但是自己不上进,就是“自作孽,不可活”。 7、满招损,谦受益。(尚书 大禹谟) 释义...
  • 王阳明让你内心强大的100名言

    千次阅读 2019-07-30 08:52:40
    王阳明让你内心强大的100名言 1、你未看此花时,此花与汝同归于寂;你来看此花时,则此花颜色一时明白起来,便知此花不在你的心外。 2、种树者必培其根,种德者必养其心。 3、君自保重,我心送君三十里。 4、无...
  • 男人励志100

    千次阅读 2011-12-08 14:01:51
    12、积极思考造成积极人生,消极思考造成消极人生。 13、人之所以有一张嘴,而有两只耳朵,原因是听的要比说的多一倍。 14、别想一下造出大海,必须先由小河川开始。 15、即使是不成熟的尝试,也胜于...
  • 也是造成严重泄漏的关键之一。它与DOM Insertion Order Leak Model的原理一致,并且加剧了闭包造成的内存泄漏。 最后,我想说的是IE8 beta1没有完全解决内存泄漏问题,它解决了若干原则中违反第三条后造成的...
  • 会拖慢计算速度,并很有可能造成内存 OOM。  client_batch_size 对速度的影响  client_batch_size 是指每一次客户端调用 encode() 时所传给服务器 List 的大小。出于性能考虑,请尽可能每次传入较多的句子而非...
  • 一:伊利丹被解禁的时候,说的第一话:“ Tydrande ? It is your voice ,After all this ages spent in darkness , your voice is like the pure light of moon upon my mind . ”(“泰兰德?真的是你的声音,...
  • 代码整洁 vs 代码肮脏

    万次阅读 多人点赞 2019-09-16 12:05:12
    WTF/min是衡量代码质量的唯一标准,Uncle Bob在书中称糟糕的代码为沼泽(wading),这只突出了我们是糟糕代码的受害者。国内有一个更适合的词汇:屎山,虽然不是很文雅但是更加客观,程序员既是受害者也是加害者。 ...
  • 线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的(注意,这话只适用大部分线性表,而不是全部。比如,循环链表逻辑层次上也是一种线性表(存储层次...
  • GIS大讨论(十):GIS专业就业之职业态度——转:职场人必看 十话让您在职场少奋斗30年 粟卫民 http://www.gisdev.cn/ http://blog.csdn.net/suen/ 日期:2012-4-1 保留所有版权。如需转载,请联系作者,并在...
  • 3.newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行 4. newScheduledThreadPool 创建一个支持定时及周期性的任务执行的...
  • 话其实不对。如果一个应用程序都能让一个操作系统崩溃了,那这一定是这个系统在设计上或者实现上的BUG!再次重申,我不知道谭浩强的C语言教材现在是怎么讲的,但是至少在15年前,很多老师都会说访问空指针会造成...
  • 华为C语言编程规范(精华总结)

    万次阅读 多人点赞 2020-03-24 09:48:55
    直接包含一切想到的头文件,甚至有些产品干脆发布了一个god.h,其中包含了所有头文件,然后发布给各个项目组使用,这种只图一时省事的做法,导致整个系统的编译时间进一步恶化,并对后来人的维护造成了巨大的麻烦。...
  • 从B树、B+树、B*树谈到R 树

    万次阅读 多人点赞 2011-06-07 17:52:00
    树的深度过大而造成磁盘I/O读写过于频繁,进而导致查询效率低下 (为什么会出现这种情况,待会在外部存储器-磁盘中有所解释),那么如何减少树的深度(当然是不能减少查询的数据量),一个基本的想法就是:采用 多叉...
  • MySQL基本概念--索引&索引类型

    千次阅读 2014-03-06 23:38:10
    上面都在说使用索引的好处,但过多的使用索引将会造成滥用。因此索引也会有它的缺点: ◆ 虽然索引大大提高了查询速度,同时却会降低更新表的速度 ,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要...
  • 解释一下AOP 传统oop开发代码逻辑自上而下的,这个过程中会产生一些横切性问题,这些问题与我们主业务逻辑关系不大,会散落在代码的各个地方,造成难以维护,aop思想就是把业务逻辑与横切的问题进行分离,达到解耦...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 78,954
精华内容 31,581
关键字:

唯一造成句