如何自动生成无重复的'编号'?(50分)

  • 主题发起人 主题发起人 dptt
  • 开始时间 开始时间
龙丹:
IDBuf表是怎样的一个表?在客户端?内存表?实际表?单用户访问,还是多用户访问?
我还不太理解你说的,你能给个例子详细说说吗?
 
龙丹的程序也有问题. 理由同zyy04
用事务也无法解决这种问题, 事务只保证写不冲突, 但你在写的时候并不禁止其他人
读(所以有脏读这一说), 因此完全有两个client取到同一个ID的可能.
唯一能解决的是: 加个锁字段, Client在读前先判断锁字段以决定是否当前记录不可用
(别人正在读这个ID), 如果可用则加锁(置锁字段标记), 然后再读取保存当前最大数的
字段, 然后将字段内容加1写回, 再解锁. 如果你愿意考虑得深一点的话, 还得考虑到
用户做到加完锁后却突然断线了(没执行解锁操作)怎么办. 所以结论只有做成SP, 这个
SP所做的就是加锁->读数->修改新值->解锁->返回读到的数. 我在项目中就是这么做的.
测试过, 测试时同时运行10个相同的进程, 每个进程读取1000次ID然后写到文件中, 最后
看文件中是否存在相同编号. 测试结果发觉总共大约有300-500次冲突并且都避免了.
 
于小澜, Another_eYes:
IDBuf是数据库中的一个实际表,字段有"表名"和"ID"两个,其中表名是主键,存放需
要生成标识的表的名称,ID存放这个表当前最大的ID值(因为一个表只有一个ID字段,所
以我没有设“ID字段名”字段)。
当要给人员基本信息增加一条记录时,用以下算法获取ID值:
select ID from IDBuf where 表名='Person' for update (这里已加锁)
DataID:=Query.ID;
DataID:=DataID+1;
update IDBuf set ID=DataID for 表名='Person'
commit (这里解锁)
至此,DataID就是新增记录的ID,插入记录时使用这个值就可以了。
从这个算法来看,Another_eYes所说的问题并不存在。但实际的程序要复杂得多,
当IDBuf中没有Person这个表的记录时,我就从Person表中取最大的ID,这可能会有一点
问题,这就是前一贴我说的“基本上”的意思。不过,发生冲突的可能性非常小,因为
只有当多人同时创建第一个相同的ID时才有可能发生冲突,而我的实际应用中根本就没
有发生过。
下面我把实际代码贴出来供参考。也请各位帮我看看能否把“基本上”这几个字去掉
(不要让我初始化IDBuf)。

function GetIDFromBuffer(DSetID:string):string;
begin
Result:='';
if TempQuery.ExecShortSQL(Format('select %s from %s Where %s=''%s'' for update',
[fnIDBufDataID,GetTableName(dsidIDBuf),fnIDBufDSetID,DSetID]),sstSelect)
and not TempQuery.IsEmpty then
Result:=TempQuery.Fields[0].AsString;
end;
//TempQuery.ExecShortSQL就是执行SQL语句,'for update'是Oracle的附带加锁的语法
//fnIDBufDataID等是一些常量和函数等

procedure PutIDIntoBuffer(DSetID,RecID:string);
begin
TempQuery.ExecShortSQL(Format('Insert into %s Values(''%s'',''%s'')',
[GetTableName(dsidIDBuf),DSetID,RecID]),sstNotSelect);
TempQuery.Database.DoCommit; //DoCommit是不怕嵌套的 StartTransaction和Commit组合
GetIDFromBuffer(DSetID); //锁定
end;

procedure ChangeBufferID(DSetID,RecID:string);
begin
TempQuery.ExecShortSQL(Format('Update %s Set %s=''%s'' where %s=''%s''',
[GetTableName(dsidIDBuf),fnIDBufDataID,RecID,fnIDBufDSetID,DSetID]),sstNotSelect);
TempQuery.Database.DoCommit; //提交并解锁
end;

function CreateDataID(DSetID,DFieldID:string; Reusable:boolean):string; //生成标识,不要理会Reusable
function GetFieldMax:string; //从目标表选取最大的ID,可能发生冲突的地方
begin
Result:='';
if TempQuery.ExecShortSQL(Format('select Max(%s) from %s',[DFieldID,GetTableName(DSetID)]),sstSelect)
and not TempQuery.IsEmpty then
Result:=TempQuery.Fields[0].AsString;
end;
function GetMaxLenOf(FieldName:string):integer;
begin
Result:=DefaultIDLength;
if DDDM.LocateDSet(NewID(DSetID)) and DDDM.LocateDField(DFieldID) then
Result:=DDDM.FldLen;
end;
var
MaxLength:Integer;
BufID:string;
begin
Result:='';
if Reusable then
Result:=GetFreeDataID(DSetID); //不要理会,Result==''
if Result='' then
begin
MaxLength:=GetMaxLenOf(DFieldID); //标识串的最大长度
Result:=GetIDFromBuffer(DSetID); //从IDBuf取ID
if Result='' then //只有第一次返回值才为空
begin
Result:=GetFieldMax; //从目标表选取最大的ID,可能发生冲突的地方
if Result='' then Result:='A';
PutIDIntoBuffer(DSetID,Result); //插入IDBuf
end;
if Length(Result)<MaxLength then
Result:=Result+CharS('A',MaxLength-Length(Result))
else
ChangeCodeToNext(Result,Length(Result));
ChangeBufferID(DSetID,Result); //修改并解锁
end;
end;

注:上面这些代码中涉及到的许多元素都是我自产的,所以,直接编译的话会有许多错误。

 
咦,怎么掉了一些? 补上:
...
if Length(Result)<MaxLength then
Result:=Result+CharS('A',MaxLength-Length(Result))
else
ChangeCodeToNext(Result,Length(Result));
ChangeBufferID(DSetID,Result); //修改并解锁
end;
end;

注:上面这些代码中涉及到的许多元素都是我自产的,所以,直接编译的话会有许多错误。

Another_eYes: 怎么联系?
 
咦,怎么还没上去?
哦,当成标记啦! 补上:
......
if Length(Result)〈 MaxLength then
Result:=Result+CharS('A',MaxLength-Length(Result))
else
ChangeCodeToNext(Result,Length(Result));
ChangeBufferID(DSetID,Result);
end;
end;

注:这些代码中的许多元素都是本人自产的,所以你要是直接编译的话会有很多错误。
 
接受答案了.
 
后退
顶部