欢迎参加讨论!

解决方案 »

  1.   

    大家可以先看看
    http://expert.csdn.net/Expert/topic/1653/1653321.xml?temp=.6031763
    欢迎就此问题讨论!
      

  2.   

    我个人的看法与foreveryday007(foreveryday007)的一样
    我认为锁的事情应该交给数据库处理。
    但是现在我在回顾以前那篇文章的时候,发现一个问题,不知如何处理
    http://expert.csdn.net/Expert/topic/1653/1653321.xml?temp=.6031763文中yczyk(有鬼:兄弟会)提到
      “我在做医院软件时,做病人入院处理,有多少收费处进行入院管理,这时要产生一个唯一的入院号,而且入院号是主键,当多个用户提交时,有可能大家的入院号是相同的,这样就会使提交工作失败。所以,我一般不会在TDataSet的AfterInsert事件获取入院号,而是在OnBeforePost的时候再获取它,另外,即使不幸真的有可能两个用户同时提交(可能性极小),我也会触发异常,在异常处理中再次去获取新的入院号,直到不再有异常为止!”但是由此,我联想到另外一个问题
      不知道大家有没有用过IC卡或者磁卡?如果产生的一个唯一的入院号要写入IC卡或者磁卡的时候怎么办?你必须在写卡之前就生成入院号,不可能等到OnBeforePost的时候再获取。
    比如说,a用户取到100号,写入卡中,存盘的时候发现用户b已经将100号写入了另外一张卡,那用户a只能重新写卡再存盘。用户的工作量明显增大。
    好象除了使用悲观锁或者将号码分段使用外,没有什么好办法了!不知各位有什么好建议?
      

  3.   

    good good study and day day up!!
      

  4.   

    1) 对于 TDataSet
    我想你用的DB..之类 的控件,不知道 是不是
    我是很少用的,大点的项目 也很少用这些东西,
    可能用EDIT,STRINGGRID(或者是别人的)2)其实就是多用户同时取编号 的问题
    也就是并发控制
    我有一解决办法,
    就是在输入卡号后(退出此EDIT时)进行卡号校验,存在报错,不存在 INSERT INTO一个临时记录给资料表里
    这样在存档 时就没有问题了,别人校验时,也会考虑到的
    其实并发操作是很难的
      

  5.   

    To foreveryday007(foreveryday007) 
    首先谢谢您的热情参与!1。你和我一样,我从不用数据感知控件来更新数据。
    2。你说的方法是可行的,但稍微麻烦了一点。
    不知还有什么更简便的方法没有。
      

  6.   

    事务不是该不该用的问题,而是必须用。
    锁对于功能性较差的数据库来说,有时候也是万般无耐的选择。因为就算采用 foreveryday007所说的办法不采用数据感知控件,但也会产生数据提交时产生的错误。
    其实你们所说的锁无外乎是用来确保提交数据时产生的某一字段值的唯一,当你采用象oracle之类的关系型数据库时,完全可以依靠序列来完成这个任务。
      

  7.   

    什么是事务
    事务(Transaction)是并发控制的基本单位。所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。例如,银行转帐工作:从一个帐号扣款并使另一个帐号增款,这两个操作要么都执行,要么都不执行。所以,应该把他们看成一个事务。事务是数据库维护数据一致性的单位,在每个事务结束时,都能保持数据一致性。数据一致性问题
    多用户并发存取同一数据将会导致以下的数据不一致性问题。
    • 丢失修改( Lost Update)
    在下表中,T1、T2、T3和T4表示顺序的时间。
    用户 T 1 T 2 T 3 T 4
    A x = 40 X = x-30
    B X = 40 X = x-20假设用户A和B都读取x ( x = 40 ) ,然后分别把x减少30和20。用户A在t3把改后的x ( x = 10 )写入数据库。随后,用户B在t4把改后的x ( x = 20 )写入数据库。于是,对用户A而言,他的修改在t4
    处丢失了。
    • 脏读数据( Dirty Read)
    请看下表,
    用户 T1 T2 T3 T4
    A x = 40 X = x + 30 X = x - 30 rollback
    B X = 70 X = x-20
    用户A在t2把x增加30(尚没写入数据库),用户B在t3由数据缓存读出x = 70。但用户A在t4时撤消(Undo)了对x的修改,数据库中仍维持x = 40。但用户B已把改变的数据( x = 70)取走。
    • 不能重复读(Non-Repeatable Read)
    用户 T1 T2 T3 T4 T5 T6
    A X=40 Y=30 X+Y=70 Z=30 X+Y+Z=100
    B x=40 X=X+20 Commit X=x-20
    用户A、用户B分别读取x = 40后,在t 3用户A取出y = 30并计算x + y = 70。在t4时用户B把x增加20,并于t 5把x ( x = 60 )写入数据库。在t6时,用户A取出z ( z = 30 )并继续计算x + y + z = 100。但如果用户A为进行核算而把x、y、x重读一次再进行计算,却出现x + y + z = 120!(x已增加20)。如何标识一个事务
    在SQL Server中,通常事务是指以BEGIN TRAN开始,到ROLLBACK或一个相匹配的COMMIT之间的所有语句序列。ROLLBACK表示要撤消( U n d o)该事务已做的一切操作,回退到事务开始的状态。COMMIT表示提交事务中的一切操作,使得对数据库的改变生效。
    在SQL Server中,对事务的管理包含三个方面:
    • 事务控制语句:它使程序员能指明把一系列操作( Transact - SQL命令)作为一个工作单
    位来处理。
    • 锁机制( Locking):封锁正被一个事务修改的数据,防止其他用户访问到“不一致”的数据。
    • 事务日志( Transaction Log):使事务具有可恢复性。SQL Server的锁机制
    所谓封锁,就是一个事务可向系统提出请求,对被操作的数据加锁( Lock )。其他事务必须等到此事务解锁( Unlock)之后才能访问该数据。从而,在多个用户并发访问数据库时,确保不互相干扰。可锁定的单位是:行、页、表、盘区和数据库。
    1. 锁的类型
    SQL Server支持三种基本的封锁类型:共享( S)锁,排它(X)锁和更新(U)锁。封锁的基本粒度为行。
    1) 共享(S)锁:用于读操作。
    • 多个事务可封锁一个共享单位的数据。
    • 任何事务都不能修改加S锁的数据。
    • 通常是加S锁的数据被读取完毕,S锁立即被释放。
    2) 独占(X)锁:用于写操作。
    • 仅允许一个事务封锁此共享数据。
    • 其他任何事务必须等到X锁被释放才能对该数据进行访问。
    • X锁一直到事务结束才能被释放。
    3) 更新(U)锁。
    • 用来预定要对此页施加X锁,它允许其他事务读,但不允许再施加U锁或X锁。
      

  8.   

    • 当被读取数据页将要被更新时,则升级为X锁。
    • U锁一直到事务结束时才能被释放。
    2. 三种锁的相容性
    如下表简单描述了三种锁的相容性:
    通常,读操作(SELECT)获得共享锁,写操作( INSERT、DELETE)获得独占锁;而更新操作可分解为一个有更新意图的读和一个写操作,故先获得更新锁,然后再升级为独占锁。
    执行的命令 获得锁 其他进程可以查询? 其他进程可以修改?
    Select title_id from titles S Yes No
    delete titles where price>25 X No No
    insert titles values( ...) X No No
    update titles set type=“general” U Yes No
    where type=“business” 然后X NO No使用索引降低锁并发性
    我们为什么要讨论锁机制?如果用户操作数据时尽可能锁定最少的数据,这样处理过程,就不会等待被锁住的数据解锁,从而可以潜在地提高SQL Server的性能。如果有200个用户打算修改不同顾客的数据,仅对存储单个顾客信息的单一行进行加锁要比锁住整个表好得多。那么,用户如何只锁定行而不是表呢?当然是使用索引了。正如前面所提到的,对存有要修改数据的字段使用索引可以提高性能,因为索引能直接找到数据所在的页面,而不是搜索所有的数据页面去找到所需的行。如果用户直接找到表中对应的行并进行更新操作,只需锁定该行即可,而不是锁定多个页面或者整个表。性能的提高不仅仅是因为在修改时读取的页面较少,而且锁定较少的页面潜在地避免了一个用户在修改数据完成之前其他用户一直等待解锁的情况。事务的隔离级别
    ANSI标准为SQL事务定义了4个隔离级别(isolation level),隔离级别越高,出现数据不一致性的可能性就越小(并发度也就越低)。较高的级别中包含了较低级别中所规定了的限制。
    • 隔离级别0:防止“丢失修改”,允许脏读。
    • 隔离级别1:防止脏读。允许读已提交的数据。
    • 隔离级别2:防止“不可重复读”。
    • 隔离级别3:“可串行化”(serializable)。其含义为,某组并行事务的一种交叉调度产生的结果和这些事务的某一串行调度的结果相同(可避免破坏数据一致性)。SQL Server支持四种隔离级别,级别1为缺省隔离级别,表中没有隔离级别2, 请参考表:
    SQL Server支持的隔离级别 封锁方式 数据一致性保证
    X锁施加于被修改的页 S锁施加于被读取的页 防止丢失修改 防止读脏数据 可以重复读取
    级别0 封锁到事务结束 是
    级别1(缺省) 封锁到事务结束 读后立即释放 是 是
    级别3 封锁到事务结束 封锁到事务结束 是 是 是
    在SQL Server也指定级别2,但级别3已包含级别2。ANSI-92 SQL中要求把级别3作为所有事务的缺省隔离级别。
    SQL Server用holdlock选项加强S锁的限制,实现隔离级别3。SQL Server的缺省隔离级别为级别1,共享读锁(S锁)是在该页被读完后立即释放。在select语句中加holdlock选项,则可使S锁一直保持到事务结束才释放。她符合了ANSI隔离级别3的标准─“可串行化”。下面这个例子中,在同一事务中对avg ( advance )要读取两次,且要求他们取值不变─“可重复读”,为此要使用选项holdlock。
    BEGIN tran
    DECLARE @avg-adv money
    SELECT @avg-adv = avg(advance)
    FROM titles holdlock
    WHERE type = "business"
    if @avg-adv > 5000
    SELECT title from titles
    WHERE type="business" and advance >@avg_adv
    COMMIT tran
    在SQL Server中设定事务隔离级别的方法有三种:
      

  9.   

    • 会话层设定
    语法如下:
    SET TRANSACTION ISOLATION LEVEL
    {
    READ COMMITTED
    | READ UNCOMMITTED
    | REPEATABLE READ
    | SERIALIZABLE
    }
    系统提供的系统存储过程将在级别1下执行,它不受会话层设定的影响。
    • 语法层设定
    在SELECT、DECLARE cursor及read text语句中增加选项。比如:
    SELECT...at isolation{0|read uncommitted}
    注意:语法层的设定将替代会话层的设定。
    • 利用关键词设定
    ─在SELECT语句中,加选项holdlock则设定级别3
    ─在SELECT语句中,加noholdlock则设定级别0如下程序清单中所列的脚本实例在authors表上持有一个共享锁,它将用户检查服务器当前活动的时间推迟两分钟。
    程序清单测试事务隔离等级
    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
    GO
    BEGIN TRAN
    SELECT *
    FROM authors
    WHERE au_lname = 'Green'
    WAITFOR DELAY '00:02:00'
    ROLLBACK TRAN
    GO
    Activity Legend(活动图标)表明:当SQL Server检索数据时会去掉页面表意向锁。Current Activity窗口(见图3 - 3 )显示共享锁一直被保持直到事务完成为止(也就是说,直到WAITFOR和ROLLBACK TRAN语句完成)。
    使用锁定优化程序提示
    让我们再深入考察程序清单的实例。通过改变优化程序提示,用户可以令SQL Server在authors表上设置一个独占表锁(如程序所示)。
    BEGIN TRAN
    SELECT *
    FROM authors (tablockx)
    WHERE au_lname = 'Green'
    WAITFOR DELAY '00:02:00'
    ROLLBACK TRAN
    GOSELECT语句使用优化程序提示tablockx来保持独占表锁直到事务结束为止。下表显示了可用的锁定优化程序提示。
    锁定优化程序提示及其描述
    优化程序提示 优化程序提示描述
    holdlock 保持锁定直到事务结束
    nolock 检索数据时不使用锁
    paglock 使用页面锁
    tablock 使用表锁
    tablockx 使用独占表锁
    updlock 使用更新锁
    holdlock优化程序提示能够在整个事务期间保持共享锁,读者在可串行化和可重复读事务隔离等级中对此已很熟悉了。如果用户偶尔想使用共享锁,最好使用系统默认的读交付事务隔离等级并需要使用holdlock优化程序提示。holock优化程序提示与读不交付事务隔离等级有相同的功能,它通过在读数据时不要任何锁定而实现非交付数据的读操作(从而避免了任何独占锁定引起的阻隔)。使用索引和锁定优化程序提示需要注意的是:用户可以将这两种类型的提示结合起来使
    用,但必须将索引提示最后列出,这一点很重要。如下程序清单中的代码给出了合法优化程序提示的正确方法。如一个混合优化程序提示
    SELECT *
    FROM authors (paglock holdlock index=aunmind)
      

  10.   

    Delphi的Dataset屬性已封裝好以上的解決方法﹐
    當讀數據時:
    CacheSize property = 1000 
    CursorLocation = clUseClient 
    LockType = ltReadOnly 
    CursorType = ctKeyset 
    CommandTimeOut = 30 當打印數據時﹕ 
    CacheSize property = 1000 
    CursorLocation = clUseClient 
    LockType = ltReadOnly 
    CursorType = ctOpenForwardOnly 
    CommandTimeOut = 30 當寫數據時: 
    CacheSize property = 1000 
    CursorLocation = clUseServer 
    LockType = ltPessimistic 
    CursorType = ctKeyset 
    CommandTimeOut = 5 
      

  11.   

    我也做过医院系统。对于使用住院号问题,我是这么处理的:在类似配置表中保存当前的号码,每被使用一次就加1,这样就不会有多个用户共同访问的问题。
    Create Procedure GetSerialNo(@SerialNo as varchar(10) output) as 
     declare @No as int, @sNo as varchar(10)
     select @sNo = ParamValue From MisConfig Where ParamName = 'SERIALNO'
     Set @No = convert(int, @sNo)
     Update MisConfig Set ParamValue = convert(varchar, @No+1) Where ParamName = 'SERIALNO'
      

  12.   

    To Wally_wu(韦利) 
    这星期有点忙,加上受沸点影响,一直没有注意到您的大作,抱歉!
    比较深奥,容我这两天慢慢看,呵呵!
      

  13.   

    我將以下的原來以Doc形式發送給你吧,因為很多表格顯示不了﹗
      

  14.   

    To: kooncan(*-{--<) 你的方法会生成重复的号,虽然这种可能性非常小。应该先执行Update,再Select新号,最后提交。 Update MisConfig Set ParamValue = convert(varchar, @No+1) Where ParamName = 'SERIALNO'select @sNo = ParamValue From MisConfig Where ParamName = 'SERIALNO'
     Set @No = convert(int, @sNo)
      

  15.   

    错了。应是: Update MisConfig Set ParamValue = ParamValue +1 Where ParamName = 'SERIALNO'select @sNo = ParamValue From MisConfig Where ParamName = 'SERIALNO'
      

  16.   

    To Wally_wu(韦利) 
      你这篇文章很有价值,能把你它发给我吗?EMAIL:[email protected]
      

  17.   

    to Wally_wu(韦利)
    能给我也发一份吗,谢谢
    [email protected]
      

  18.   

    To Wally_wu(韦利) 
    方便的话,发我一份,谢谢。[email protected]
      

  19.   

    to:netwolfds(Good Good Study!)
    你说的那个问题我也用到,不过我通常是这样解决的:id="yyyy"+"MM"+"dd"+"hh"+"mm"+"ss"+
    随机生成的定长整数序列(长度视并发程度而论)
    一般来说我想应该够了.
    当然,从数学意义上来说还有重的可能,但在这个意义上com的id都是会产生重复的啦.
    请大家多提意见.
      

  20.   

    To foreveryday007(foreveryday007你说的重复的序列号的问题,其实这个对与多用户来说,最好的办法就是触发器(有时自增
    字段也能实现要求比较简单的功能),在你操作的表里设置一个trigger就可以啦,在insert的时候出发就ok.因为所有的并发操作,在微观上,在服务器上都是串行的
      

  21.   

    up
    在sql中你可以用几种办法:
    GUID字段
    自增长字段
    TimeStamp字段
    都可以直接实现你说的不重复id的功能。
      

  22.   

    就我知主流数据库都直接或间接提供了这种功能。
    ORACLE:sequence提供唯一id.
    SQLSERVER:建表时可以指定一个字段实现自增功能。
    其他的数据库就不知道,不过我个人反对在TRIGGER中查询最大值加一的方法,如果这张表的数据很多的话代价就是很大的了。
      

  23.   

    to Wally_wu(韦利)
    能给我也发一份吗,谢谢
    [email protected]
      

  24.   

    to Wally_wu(韦利)
    能给我也发一份吗,谢谢
    [email protected]
    继续关注!!
      

  25.   

    kooncan(*-{--<) 比较安全的办法是,设置一个字段Lock,
    假如该字段为空,那你试图上锁,Update set Lock=你的机器名或用户名,
    然后Select Where Lock=你的机器名或用户名 验证你是否成功上锁,假如是成功,则取号,加1,Update set Lock=Null,否则等待你的方法 可能会一个号分给2人
      

  26.   

    产生顺序号的方法ORACLE 中用SEQUENCEM$SQL 中大批量ID用IDENTITY, 
    小批量ID用如下方法
    CREATE TABLE IDS(ID INT, ...)
    GOCREATE FUNCTION GETID  AS
    DECLARE @ID INT
    BEGIN TRAN
    SELECT @ID = ID FROM IDS WITH (ROWLOCK, UPDLOCK) WHERE ... --获取记录锁,UPDATE IDS SET ID = ID + 1COMMIT TRAN
    RETURN @ID
    GOSYBASE 中与M$SQL类似
      

  27.   

    http://expert.csdn.net/Expert/topic/1653/1653321.xml?temp=.6031763楼主的例子,自己开一个事务,然后判断是否加锁成功,假如数据量较大(多大算大呢?:),可能自身数据库死锁(Sql server 2000),我碰到过:(
      

  28.   

    很简单,专门写一SERVICE程序A,在某服务器上监听。
    所有产生唯一ID的客户程序B首先向服务器上产生ID的服务提交请求,
    A首先会把请求放入请求队列,然后有一线程负责从队列中顺序处理请求,返回这个唯一ID。
    用锁很难解决的很好
      

  29.   

    在医院系统中其实最容易发生并发问题的是在门诊收费(量大、收费快、并发问题凸显)中,我一般采用两个方法解决:(门诊主表和用药细表)
    一、采用事务和锁的机制:
      1、形成新记录时,立即形成临时门诊号(YYYY+MM+DD+HH+MM+SS+收费员编号),共18位;
      2、在门诊主表和用药细表使用此临时门诊号建立关联,进行患者信息和相关用药信息的录入;
      3、如果确认保存,立即启动一个事务,使用TabLockX锁查找门诊号(YYYY+MM+DD+4位顺序号)长度为12并且前8位YYYYMMDD为今天的最大门诊号,找到,则在该门诊号+1,上否则为今天的YYYYMMDD+'0001',用该号修改所有临时门诊号,保存,提交事务;
      4、如果取消,则丢弃;
    二、采用自增1的标识列:
      1、初始值设为:1000000000;本方法已用于多家医院,在日高峰门诊达1000人次、5台收费终端情况下使用近两年无任何问题。
      

  30.   

    chenylin(陈SIR)当然也是很不错的一个方法
      

  31.   

    liuchcn(michael) :
    做数据库不用锁?那数据库厂商开发出锁的功能是有什么用的?锁定与事务都是必须的。锁定对于生不不重复的序号是一个很好的解决方法,其它的方法大部分都是自找麻烦。我想你可能是個學生,或者是沒有開發過大項目的(2年以上)
      

  32.   

    /*==============================================================*/
    /* 流水号表及取号存储过程 */
    /*==============================================================*/
    /*----1.9 开单流水号表----*/
    if exists(select 1 from sysobjects where name = 'PUB_SYS_SERIAL'
      and type = 'u')
       drop table PUB_SYS_SERIAL
    gocreate table PUB_SYS_SERIAL
    (
      type char(1) NOT NULL, /*类别*/
      descr varchar(32) NULL, /*描述*/
      lsh numeric(8,0) NULL, /*流水号*/
      rq char(8) NULL, /*日期*/
      CONSTRAINT PK_PUB_SYS_SERIAL PRIMARY KEY  nonclustered (type)
    )
    go/*付印单*/
    insert into PUB_SYS_SERIAL(type,lsh,rq) values('1', 0, convert(char(8),getdate(),112))
    goinsert into PUB_SYS_SERIAL(type,lsh,rq) values('2', 0, convert(char(8),getdate(),112))
    goinsert into PUB_SYS_SERIAL(type,lsh,rq) values('3', 0, convert(char(8),getdate(),112))
    go/*取开单流水号--存储过程*/
    if exists(select 1 from sysobjects where name = 'p_get_PUB_SYS_SERIAL'
      and type = 'p')
       drop proc p_get_PUB_SYS_SERIAL
    gocreate proc p_get_PUB_SYS_SERIAL
    @flag char(1),
    @lsh char(8) output
    with encryption
    as
    declare @lsh1 numeric(8,0),
    @rq1  char(8),
    @rq2  char(8)
      select @rq1 = convert(char(8),getdate(),112)
      update PUB_SYS_SERIAL
         set lsh = lsh + 1
       where type = @flag
      select @lsh1 = lsh,
     @rq2  = rq
        from PUB_SYS_SERIAL
       where type = @flag
      if @@rowcount <> 1
        begin
    raiserror 99999 '取流水号出错!'
    return -1
        end
      if @rq1 <> @rq2
        begin
    update PUB_SYS_SERIAL
               set rq =@rq1
    --             lsh = 1
             where type=@flag
    -- select @lsh1=1
        end
      select @lsh = right('00000000'+ltrim(str(@lsh1)), 8)
      return 1
    go
      

  33.   

    我的文章都發送給了以上提供Email的朋友﹐請查收﹗
      

  34.   


        依然觉得数据库本身的事务管理已经够用 ;对于大型数据库如 Sql Server 2000 / racle ,数据库本身就有并发处理的能力,有很完善的并发机制来对数据库进行读写,它会根据用户对数据库表的操作类型自动作 记录/页/表 的锁定,在程序级并不需要对多用户的并发操作过多考虑,不需要手动加锁 ,我们只要对一些突发事件下有可能出现的"脏数据"做出应对来保证数据的完整性和一致性,也就是基本的一些事务处理 ; 不知大家怎么认为  ;
       卡号唯一用自增字段或者触发器都可以完成 ;Study