那第 2 条,不可重复读又怎么解释呢?还是用类似的例子来说明:
时间 事务 A(存款) 事务 B(取款)T1 开始事务
T2 开始事务
T3 查询余额(1000 元)
T4 查询余额(1000 元)
T5 取出 1000 元(余额 0 元)
T6 提交事务
T7 查询余额(0 元)
事务 A 其实除了查询了两次以外,其他什么事情都没有做,结果钱就从 1000 变成 0 了,这就是重复读了。可想而知,这是别人干的,不是我干的。其实这样也是合理的,毕竟事务 B 提交了事务,数据库将结果进行了持久化,所以事务 A 再次读取自然就发生了变化。
这种现象基本上是可以理解的,但在有些变态的场景下却是不允许的。毕竟这种现象也是事务之间没有隔离所造成的,但我们对于这种问题,似乎可以忽略。
最后一条,幻读。我去!Phantom 这个单词不就是“幽灵、鬼魂”吗?刚看到这个单词时,真的把我的小弟弟都给惊呆了。怪不得这里要翻译成“幻读”了,总不能翻译成“幽灵读”、“鬼魂读”吧。其实意义就是鬼在读,不是人在读,或者说搞不清楚为什么,它就变了,很晕,真的很晕。还是用一个示例来说话吧:
时间 事务 A(统计总存款) 事务 B(存款)T1 开始事务
T2 开始事务
T3 统计总存款(10000 元)
T4 存入 100 元
T5 提交事务
T6 统计总存款(10100 元)
银行工作人员,每次统计总存款,都看到不一样的结果。不过这也确实也挺正常的,总存款增多了,肯定是这个时候有人在存钱。但是如果银行系统真的这样设计,那算是玩完了。这同样也是事务没有隔离所造成的,但对于大多数应用系统而言,这似乎也是正常的,可以理解,也是允许的。银行里那些恶心的那些系统,要求非常严密,统计的时候,甚至会将所有的其他操作给隔离开,这种隔离级别就算非常高了(估计要到 SERIALIZABLE 级别了)。
归纳一下,以上提到了事务并发所引起的跟读取数据有关的问题,各用一句话来描述一下:
脏读:事务 A 读取了事务 B 未提交的数据,并在这个基础上又做了其他操作。
不可重复读:事务 A 读取了事务 B 已提交的更改数据。
幻读:事务 A 读取了事务 B 已提交的新增数据。
第一条是坚决抵制的,后两条在大多数情况下可不作考虑。
这就是为什么必须要有事务隔离级别这个东西了,它就像一面墙一样,隔离不同的事务。看下面这个表格,您就清楚了不同的事务隔离级别能处理怎样的事务并发问题:
事务隔离级别 脏读 不可重复读 幻读READ_UNCOMMITTED 允许 允许 允许
READ_COMMITTED 禁止 允许 允许
REPEATABLE_READ 禁止 禁止 允许
SERIALIZABLE 禁止 禁止 禁止
根据您的实际需求,再参考这张表,最后确定事务隔离级别,应该不再是一件难事了。
JDBC 也提供了这四类事务隔离级别,但默认事务隔离级别对不同数据库产品而言,却是不一样的。我们熟知的 MySQL 数据库的默认事务隔离级别就是 READ_COMMITTED,Oracle、SQL Server、DB2等都有有自己的默认值。我认为 READ_COMMITTED 已经可以解决绝大多数问题了,其他的就具体情况具体分析吧。
若对其他数据库的默认事务隔离级别不太清楚,可以使用以下代码来获取:
1 DatabaseMetaData meta = DBUtil.getDataSource().getConnection().getMetaData(); 2 int defaultIsolation = meta.getDefaultTransactionIsolation();