在银行系统的存取款过程中,当遇到对一个账户并发存取的时候,系统该如何处理比较好。可能不少人会想到线程同步,然而在应用层使用同步会导致对象锁定,大大影响并发效率。此时,充分利用数据库的事务隔离机制可以很好的解决这个问题。本文细扒事务隔离的详细知识。
1.数据库事务隔离级别
数据库事务隔离自低到高有4个级别,分别为:Read uncommitted(未提交读),Read committed(提交读),Repeatable read(重复读),Serializable(序列化)。低级别的隔离级一般支持高的并发处理,并拥有低的系统开销。
通俗地,结合场景描述一下各个级别含义及问题。首先我们要了解事务有ACID的特性,衍生出事务行为有start,commit,rollback过程。
1.1 Read uncommitted不同的事务A,B;A的数据尚未提交,B已经能够查询到A未提交的数据。
问题:假定并发事务A,B:A要将工资表中将一条工资5000的记录改为8000,但未commit事务前发现8000是不对的,于是ROLLBACK结束此事务;同时B在查询工资表,却显示工资是8000,并不是5000。也就是说B读取了A尚未提交的数据,而致使B读取了B不应该看到的脏数据。
MySQL示例SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED事务A更新表:
事务B并发查询:
事务A回滚及最终结果:
1.2 Read committed事务A的数据提交后,事务B才能看到事务A提交的数据。
问题:假定并发事务A,B:A要将工资表中一条工资为8000的记录扣除5000并commit,而事务B在查询工资表为8000后也要扣除5000,但此时由于A已经扣除5000,表里只剩3000,不能够再扣除5000。简化之即在事务B的单元内,两次查询的结果一次为8000,一次为3000,前后不一致(不可重复读)。
MySQL示例SET TRANSACTION ISOLATION LEVEL READ COMMITTED事务B查询:
事务A更新表并提交:
事务B再发查询提交:
1.3 Repeatable read一个事务单元内,对同一条记录的查询结果相同,并不会因为别的事务干预而导致结果不一致。即在事务查询开始,被查询的记录就会被锁住直到该事务结束。
问题:新记录的插入不会被锁而导致幻读。
MySQL示例SET TRANSACTION ISOLATION LEVEL REPEATABLE READ事务A查询:
事务B更新:
事务A再查询,发现B的更新未更新到表中,A读取的数据前后一致:
1.4 Serializable序列化是最高的事务隔离级别,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读,但代价最高,性能最低,一般很少使用。此处不多做解释。
1.5 小结汇总
2.各个数据库默认隔离级别
2.1 MySQL & Oracle
MySQL:Repeatable read
Oracle:Read committed
2.2 DB2
DB2隔离级别分为如下四种:
1. 可重复读(Repeatable Read,RR)
2. 读稳定性(Read Stability,RS):与可重复读隔离级别一样
3. 游标稳定性(Cursor Stability,CS):当使用游标稳定性隔离级别时,事务通过游标从表中检索行时,其他事务不能更新或删除游标所引用的行
4. 未提交读(Uncommitted Read,UR)
DB2默认的隔离级别为:游标稳定性(CS)
References