风之舞原创,转载请注明来源
=============================
这两天做一个数据加载的项目,遇到一个需要加行级锁的问题。解决如下:
场景:
某条记录,同时有两个用户需要远程修改。如 A 用户的超市卡有主卡和副卡,卡中金额为 5000 元,用户1在 商场购物 1000 元,用户2在商场服务中心充值 2000 元。
在服务中心充值 2000 元时,先取出余额 5000 元,经过运算后写回余额 7000 元,在服务中心进行读取操作之后,尚未进行运算之前,商场购物需要支取 1000 元,此时读取了余额 5000 元,在服务中心写回运算后的余额 7000 元后,消费 1000 元的运算是用 5000 元作为基础,写回运算后的余额 4000 元,结果是超市卡的余额变成了 4000 元,而实际上,这个余额应该是 6000 才对。
解决思路:
在充值的查询的时候,加行级锁,锁住这条记录,待修改完成提交后,再开放该条记录让其他的进程进行查询。
模拟解决过程如下:
建表:
db2 "create table testtb ( a int not null, b char(12),constraint pk_testtb_1 primary key(a))"
db2 "insert into testtb values ( 1, 'a')"
db2 "insert into testtb values ( 2, 'b')"
修改操作模式为非 autocommit
db2set DB2OPTIONS=+c
打开两个终端,模拟两个进程对同一个表的同一条数据进行操作。
在第一个终端中执行如下查询语句:
db2 "select * from testtb where a = 1 for update with rs"
在第二个终端中执行同样的 sql 语句,会发现第二个终端中的 sql 查询被锁定。
再在第一个终端中执行 db2 commit 或 db2 rollback
则第二个终端被锁定的记录被检索出来。
再在第一个终端中在此执行相同的语句,会发现第一个终端中的检索同样被锁住,直到第二个终端中执行 db2 commit 或 db2 rollback 后,才能检索出数据。
这是因为第二个终端中检索出数据以后,同样对该条记录进行了锁定。
这样就达到了我们所要的目的。
总结如下:
DB2 有四个数据隔离级别,依次为 ur 、 cs、rs、rr
其中:
ur 为未提交读,该级别下第二个进程可以读取到第一个进程中已经修改但还没有执行 commit 提交命令的数据,这也就是所谓的“脏读”,一般情况下,我们不建议使用这个隔离级别。
举例如下:
进程一:执行 update testtb set b = 'sss' where a = 1
进程二:执行 select * from testtb where a = 1
此时,尽管进程一没有执行 commit 提交,进程二检索到的数据依然是 1,sss , 而不是 1,'a'
很显然,尽管进程一没有执行 commit ,但进程一修改过后的数据依然被进程二读取到了,这也就是出现了所谓的脏读。
cs 为游标稳定性隔离,该级别下,第二个进程可以读取到第一个进程中已经提交的数据,但没有执行 commit 提交命令的数据则无法读取到。
举例如下:
进程一:执行 update testtb set b = 'sss' where a = 1
进程二:执行 select * from testtb where a = 1
此时,由于进程一没有执行 commit 的提交,进程二检索出来的数据为 1,’a'
rs 为读稳定性隔离,该级别下,被显式要求提交的数据不能被第二个进程所读取。
举例如下:
进程一:执行 select * from testtb where a =1 for update with rs
进程而:执行 select * from testtb where a =1 for update with rs
此时,进程二的查询动作被锁定,没有返回信息。
我们再进入第一个终端,执行 commit , 此时第二个终端返回了查询的信息。
再进入第一个终端执行 执行 select * from testtb where a =1 for update with rs , 会发现第一个终端的查询动作被锁定,原因是第二个终端执行了需要显示提交的语句后,没有执行 commit 或者是 rollback 。
我们可以再试试:在第二个终端中执行 rollback ,让第一个终端中的查询顺利的执行下去,然后再在第二个终端中执行:
执行 select * from testtb where a =2 for update with rs
我们会发现查询返回了正确的结果 2,'b' , 很显然,我们的锁仅仅是锁住了 a =1 的那一条记录,而这正是我们想要的排他的行级读加锁的结果。
从这里我们可以发现,执行 rs 隔离界别,实际上达到了 行级的排他读加锁的目的。
rr 为可重复读隔离。该隔离级别下,在单个事务执行期间锁定该事务引用的所有行。使用这种隔离级别时,同一事务多次发出的同一个 SELECT 语句将始终产生同一结果;丢失更新、脏读、不可重复的读、幻像都不会发生。使用可重复的读隔离级别的事务可以多次检索同一行集,并可以对它们执行任意次操作,直到由提交或回滚操作终止事务;不允许其它事务执行插入、更新或删除操作,因为这些操作会在隔离事务存在期间影响正在被使用的行集。
该隔离级别最高,在该隔离级别下,对当前操作的表会完全锁定,只有当前的终端才能够执行增、删、查、改操作,其它的任何终端都不能对该表进行操作,直到当前终端执行了 rollback 或者是 commit 。
补充说明:
===================================================================================================
1、这里用了两个终端来进行两个事务的模拟,正确的说法应该是第一个事务和第二个事务。
2、commit 和 rollback
commit 是事务提交,执行该操作,则至上一个 commit 或 rollback 开始的前面所有的对数据的修改都会被提交。
rollback 是事务回滚,执行该操作,则至上一个 commit 或 rollback 开始的前面所有的对数据的修改都会被放弃。
阅读(1948) | 评论(0) | 转发(0) |