最近生成环境遇到了一个问题,最后定位问题怀疑是高并发导致事务的隔离级别出现问题导致的,查询了db2的事务隔离级别之后调整执行update语句时上锁的级别最后成功解决问题。
目录
db2 中基本的锁有两类:
db2 事务的隔离级别
不可重复读和幻读区别
DB2的锁
db2锁的级别
db2 中基本的锁有两类:
- 排他锁(X锁),也叫写锁,当某记录正在被修改时,其他进程不能再读取或修改
- 共享锁(S锁),也叫读锁,当某记录正在被读取时,其他进程修改
db2 事务的隔离级别
锁是为事务的隔离服务的,在这里就不得不先说一下什么是数据库的事务,数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
为什么要进行事务隔离?如果不进行事务隔离会出现什么状况?
- 脏读 (针对未提交数据)如果一个事务中对数据进行了更新,但事务还没有提交,另一个事务可以“看到”该事务没有提交的更新结果,这样造成的问题就是,如果第一个事务回滚,那么,第二个事务在此之前所“看到”的数据就是一笔脏数据。
- 不可重复读(针对其他提交前后,读取数据本身的对比)不可重复读取是指同一个事务在整个事务过程中对同一笔数据进行读取,每次读取结果都不同。如果事务1在事务2的更新操作之前读取一次数据,在事务2的更新操作之后再读取同一笔数据一次,两次结果是不同的,所以,Read Uncommitted也无法避免不可重复读取的问题。
- 幻读(针对其他提交前后,读取数据条数的对比) 幻读是指同样一笔查询在整个事务过程中多次执行后,查询所得的结果集是不一样的。幻读针对的是多笔记录。在Read Uncommitted隔离级别下, 不管事务2的插入操作是否提交,事务1在插入操作之前和之后执行相同的查询,取得的结果集是不同的,所以,Read Uncommitted同样无法避免幻读的问题。
不可重复读和幻读区别
对于不可重复读和幻读,可以借用下面的例子理解:
- 不可重复读,不可重复读的重点是修改: 同样的条件, 你读取过的数据, 再次读取出来发现值不一样了
例子:在事务1中,Kevin读取了自己的工资为10000.。
select salary from employee where empName = "kevin" and empId = "10000000" ;
在事务2中,财务人员修改了Kevin的工资为15000,并提交了事务.。
update employee set salary = 15000 where epmName = "Kevin" and epmId = "10000000";
commit;
在事务1中,Mary 再次读取自己的工资时,工资变为了15000
select salary from employee where empName = "kevin" and empId = "10000000" ;
在一个事务中前后两次读取的结果并不致,导致了不可重复读。
- 幻读,幻读的重点在于新增或者删除 (数据条数变化)
同样的条件, 第1次和第2次读出来的记录数不一样
例子:目前工资为10000的员工有10人。 事务1,读取所有工资为10000的员工。
Select * from employee where salary = 10000;
共读取10条记录
这时另一个事务向employee表插入了一条员工记录,工资也为1000
Insert into employee(empId,salary) values("xiaohei",10000);
commit;
事务1再次读取所有工资为10000的员工
Select * from employee where salary = 10000;
共读取到了11条记录,这就是幻读,像产生了幻觉一样。
db2 有避免以上三种现场对应的隔离级别 (其他数据库也有对应的级别,名称可能不一样,请注意对比 ),如下所示:
db2的隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
未提交读(Uncommitted Read) | 是 | 是 | 是 |
游标稳定性(Cursor Stability) | 否 | 是 | 否 |
读稳定性(Read Stability) | 否 | 否 | 是 |
可重复读(Repeatable Read) | 否 | 否 | 否 |
隔离级别的具体内容可能记不住,我也记不住上面的表格是百度扒出来的,但是只要理解了,就能知道这四种隔离级别是什么状态,需要应用在哪里即可,毕竟程序不是考试的标准答案。
如果还不理解,情况下面的例子:
- 未提交读(Uncommitted Read) 。
select * from xxx with ur
with ur 是什么意思,到这你可能就明白了,ur 就是 Uncommitted Read,即未提交读的隔离级别,允许脏读,不加行锁,作用就是在 select 的时候 ,不需要对 update 的数据进行等待。在下列第一个查询语句中读到‘xiaohei’的数据,第二个查询语句是无法读到的
-- +c 表示不自动提交
db2 +c "Insert into employee(empName,salary) values('xiaohei',10000); "
-- 脏读
select * from employee where empName= 'xiaohei' with ur ;
-- 游标稳定性 with cs 可以不需要加with cs 因为db2默认就是游标稳定的
select * from employee where empName= 'xiaohei' ;
- 游标稳定性(Cursor Stability)
select * from xxx with cs
- 读稳定性(Read Stability)如果使用这种隔离级,在一个事务中所有被读取过的行上都会被加上NS锁,直到该事务被提交或回滚,行上的锁才会被释放。这样可以保证在一个事务中即使多次读取同一行,得到的值不会改变。但是,如果使用这种隔离级,在一个事务中,如果使用同样的搜索标准重新打开已被处理过的游标,则结果集可能改变。(可能会增加某些行,这些行被称为幻影行(Phantom)),对应幻读。这是因为 RS 隔离级别并不能阻止通过插入或更新操作在结果集中加入新行。
- 可重复读(Repeatable Read)是最严格的隔离级别,如果使用这种隔离级,在一个事务中所有被读取过的行上都会被加上 S 锁,直到该事务被提交或回滚,行上的锁才会被释放。这样可以保证在一个事务中即使多次读取同一行,得到的值不会改变。另外,在同一事务中如果以同样的搜索标准重新打开已被处理过的游标,得到的结果集不会改变。重复读相对于读稳定性而言,加锁的范围更大。
注意:对于读可靠性,程序只对符合要求的所有行加锁,而对于重复读,程序将对所有被扫描过的行都加锁。例如,一个应用程序对一个表中的 10000 行数据进行扫描,最终找到了 100 条符合搜索条件的结果集。如果该程序使用的是读可靠性隔离级,程序将只对这符合条件的 100 行加锁;如果该程序使用的是重复读隔离级,程序将对被扫描过的 10000 行都加锁。
DB2的锁
db2支持对表空间、表、行、索引(大型机支持对数据页)的锁定。一般情况下考虑表锁与行锁。
表锁:表中的所有数据都被锁,受到同等的影响。
行锁:如果针对表的某些记录加锁,那么就应该对相应的数据行加锁。
db2支持的表锁
缩写 | 全称 | 描述 |
IN | 无意图锁(Intent Node),不需要行锁 | 拥有者可以读取包括其他事务未提交数据在内的所有数据,但不能对表中的数据作出修改 |
IS | 意图共享锁(Intent Share),需要行锁配合 | 拥有者可以在拥有相应行上的S锁时可以读取该行的数据,但不能修改数据 |
IX | 意图排他锁(Intent eXclusive),需要行锁配合 | 拥有者可以在拥有相应行上的X锁时可以修改该行的数据 |
SIX | 共享并且意图排他锁(Share with Intent eXclusive),需要行锁配合 | 拥有者可以读取表中的任何数据,如果在相应的行上可以获得X锁,可以修改该行。SIX的获取比较特殊,当程序拥有IX锁时请求S锁,或者在已经拥有S锁的时候请求IX锁时产生 |
S | 共享锁(Share),不需要行锁配合 | 可以读取表上的任何数据,如果表上被加了S锁,表上的数据只能被读取而不能做出任何修改 |
U | 更新锁(Update),不需要行锁配合 | 拥有者可以读取表中的任何数据,如果升级为X锁,则可以更改表中的任何数据,该锁是等待对数据进行修改的一种中间状态 |
X | 排他锁(eXclusive),不需要行锁配合 | 拥有者可以读取或者修改表中的任意数据,如果加上了X锁,除了未提交读事务外,其他程序都不能对表进行任何读取或者修改 |
Z | 超级排他锁(Super eXclusive),不需要行锁配合 | 该锁一般不是由 DML 产生,而是由Drop,Alter或者创建删除索引时产生的,加上Z锁后,所有程序(包括未提交读程序)都不能对表进行读取或者修改 |
db2支持的行锁
缩写 | 全称 | 需要表锁最低级别 | 描述 |
S | 共享锁(Share) | IS | 该行正在被读取,其他程序只能执行读操作 |
U | 更改锁(Update) | IX | 某个程序正在读取并有可能修改该行,其他程序只能读取该行 |
X | 排他锁(eXclusive) | IX | 该行正在被某个程序修改,其他程序不能访问该行 |
W | 弱排他锁(Weak eXclusive) | IX | 一行被插入表后,该行会加上 W 锁,只有锁的拥有者可以修改该行,与 X 锁的不同在于该锁与 NW 锁兼容 |
NS | 下一键共享锁(Next Share) | IS | 拥有者与其他程序都可以读取该行,但不能进行修改,当程序处于RS或者CS隔离级别下时,该锁可以代替S锁 |
NX | 下一键排他锁(NexteXclusive) | IX | 一行的数据被插入到索引或者从索引被删除时,该行的下一行会被加上 NX 锁,锁的拥有者可以读该行的数据但不能修改。该锁与 X 锁类似,但与 NS 锁兼容 |
NW | 下一键弱排他锁(NextWeak eXclusive) | IX | 一行的数据被插入到索引时,该行的下一行会被加上NW锁,锁的拥有者可以读但不能修改该行的数据,与X锁及 NX 锁类似,但与W锁以及 NS 锁兼容 |
db2锁的级别
当程序向数据库请求对已经加锁的对象上面再次加锁的时候,数据库会比较对象上现在的锁与所请求的锁的模式,如果所请求的锁级别更高,则把现在的锁升级为请求的锁。
锁级别比较:
表锁:IN < IS < S< IX< U< X< Z
行锁:S< U< X
注意:有一个特殊例子是,如果持有 S 锁请求 IX 锁,或者持有 IX 锁请求 S 锁,锁转换结果为 SIX 锁。
参考:https://www.ibm.com/developerworks/cn/data/library/techarticles/dm-0805qikk/