现在的位置: 首页 > 综合 > 正文

唯一且连续的编码的实现

2012年12月22日 ⁄ 综合 ⁄ 共 6500字 ⁄ 字号 评论关闭

    在信息系统的开发过程中,我们通常要处理各种各样的编码问题,有的教科书甚至将编码设计提升为系统设计阶段的一个重要步骤。此处所谓的编码,是“编号”的近义词,而非有时我们所说的“编写代码”,它通常作为对象的标识存储在数据库中。

    既然是标识,那么编码应当是唯一的,事实上,唯一性是比较容易实现的:自动编号类型的字段(如果支持的话)、一定精度的当前时间等等。此时,我们不考虑这种编码是否具备一定的含义(通常这是编码设计的一个原则),我们在唯一性基础之上考虑另外一种常见的需求:编码应当是连续的,即不重号且不断号。

    这种需求下的编码中无疑都存在一个由简单整数数字组成的部分,如下图所示。由于其它部分可以通过某种方式补齐,为了简化问题,我们可以假设要实现的编码只有该整数数字部分,即我们要实现的编码序列是{1, 2, 3, ..., 10, 11, 12, ...}。

    图1 唯一且连续的编码示例

    下面的篇幅将讨论在后台数据库为SQL Server 2000的情况下的解决方案和可能遇到的一些问题。

    假设SQL Server 2000中已经存在一个名为tEsTdB的数据库,执行以下脚本——    

代码1 创建用户表Invoice的SQL语句

    通过以上脚本,我们在tEsTdB数据库中获得一个名为Invoice的用户表,其中InvoiceID是一个自动编号的bigint型字段,而InvoiceNo是一个普通的bigint型字段。准备工作之后,我们要做的就是向Invoice表中新增记录,要求是对于InvoiceNo字段必须是唯一且连续的。

    对于有过开发经验的朋友来说,这是一个很稀松平常的任务,看看他是如何实现的:

代码2 向Invoice表中增加新记录

    OK,这段简单代码看起来运行不错,尤其是在一些基础数据维护时由于基础数据不需要经常变动(也许整个生命周期都不会变动一次,因为在数据准备阶段已经固定下来了,提供修改的接口只是为了软件的完整性),不会出现什么大问题。现在,来看看下面的情况——

    修改代码2让按钮单击事件中的代码执行10000次,然后将生成的exe文件复制10份并分别执行它们,如下图所示。

    图2 将SeqNo_IncorrectWay.exe复制10份并运行

    [下载代码2]

    分别单击这10个窗体上的按钮向Invoice表中插入数据,不用等到所有的操作都完成,大概差不多就可以了,在查询分析器中执行下面的语句:

代码3 检查InvoiceID和InvoiceNo是否相等

    由于InvoiceID是自动编号类型的字段,在只有Insert操作的情况下,如果InvoiceNo是唯一且连续的,每一条记录的InvoiceID应该和InvoiceNo相同,当然,在执行Insert操作之前应当保证InvoiceID是从1开始的,最好在这之前使用语句TRUNCATE TABLE Invoice重新创建Invoice表。所以,如果代码3返回任何记录,则证明InvoiceNo不是唯一的,即出现了重号的情况。(如果是SQL Server 2005的话,我们甚至根本不需要InvoiceID这个字段,因为在SQL Server 2005中可以使用ROW_NUMBER()返回记录的行号,我的随笔RDL(C) Report Design Step by Step 3: Mail Label中有一个关于ROW_NUMBER()的应用)

    事实上,上面同时运行的10个窗体模拟了多个客户端的并发情况,但由于InvoiceNo字段并非不能重复,所以这又和常说的并发冲突有所区别,程序的执行没有问题,但是在逻辑上是错误的。也就是说,在多用户并发的情况下,这种实现无法满足需求。

    需要指出的是,上面的简单代码中,由于线程被阻塞,所以标签lblCurrentNO并不会显示当前插入的InvoiceNo,在稍后会给出一个多线程的可以显示当前插入的InvoiceNo的值的例子。

    接下来,我们看一下,是否可以通过调用下面的存储过程来达到我们的目的:

代码4 存储过程GetInvoiceNo

    其中,表CurrentNo专门用于存储当前插入的InvoiceNo的值,这样可以带来的一个显而易见的好处是避免扫描Invoice表获取InvoiceNo的最大值,在记录数较大时提高效率。该表只有一个bigint型的字段CurrentNo,可以使用下面的语句创建该表:

代码5 创建用户表CurrentNo的SQL语句

    测试用窗体的界面如下图所示:

    图3 多线程测试用窗体

    窗体上的每个按钮单击后都创建一个一个新的线程向数据库中循环插入记录,同时避免由于线程阻塞无法显示当前插入的InvoiceNo的值,窗体代码如下:

代码6 窗体SeqNo_MultiThread.frmTestSeqNo

抱歉!评论已关闭.