注意这里讨论的是抽象意义上的数据库,mysql数据库有自己特有的实现。
事务隔离级别 | 更新丢失 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
未提交读RU(Read Uncommited) | 避免 | 发生 | 发生 | 发生 |
已提交读RC(Read Commited) | 避免 | 避免 | 发生 | 发生 |
可重复读RR(Repeatable Read) | 避免 | 避免 | 避免 | 发生 |
串行化(Serializable) | 避免 | 避免 | 避免 | 避免 |
事务并发访问引起的问题以及如何避免
mysql会利用锁机制创建出来不同的事务隔离级别,将按照事务隔离级别从低到高进行讲解
1. 更新丢失问题
事务并发带来的一个问题:更新丢失,即一个事务的更新覆盖了另一个事务的更新
由于目前我们主流的数据库都会为我们自动加锁来避免这种更新丢失的情况,所以在数据库层面是不好进行模拟的
mysql的所有事务隔离级别在数据库层面上均可以避免更新丢失的问题
2. 脏读问题
dirty read,一个事务读取到了另一个事务还未提交的数据
该问题可以在Read-Committed事务隔离级别以上可避免
select @@tx_isolation
现将事务隔离级别调至最低,就是未提交读 Read Uncommitted
set session transaction isolation level read uncommitted;
-- 或者
set tx_isolation = 'read-uncommitted';
在autocommit=1 即自动提交开启的情况下执行
步骤1
session1执行
START TRANSACTION;
步骤2
session2执行
START TRANSACTION;
步骤3
session1执行
UPDATE account_innodb SET balance = 1000-100 WHERE id = 1;
SELECT * FROM account_innodb WHERE id = 1;
此时session1未提交
步骤4
session2查询
SELECT * FROM account_innodb WHERE id = 1;
居然是900了
步骤5
session1 rollback
步骤6
session2并不知道session1 rollback,其继续进行操作
UPDATE account_innodb SET balance = balance + 200 WHERE id = 1
结果就是1100块(应该是1200块),这就是脏读带来的问题
将隔离级别改为Read Committed之后,重复上面的过程
会发现在步骤4,session2查询的时候,会发现其值为1000,而不是900,即未读到session1未提交的事务
3. 不可重复读问题
REPEATABLE READ事务隔离级别以上可以避免
含义是事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据做了更新并提交,导致事务A多次读取时数据不一致
在RC隔离级别下
步骤1
session1
start transaction;
步骤2
start transaction;
步骤3
session1先读取一次,是1200
session2加了300,之后commit
session1再读取一次,是1500
如果session1基于1200进行了操作,就可能造成数据紊乱的结果
将隔离级别改为REPEATABLE READ
再进行一次,会发现session1读取的结果一致都是第一次start transaction之前数据的值,在整个session过程中不变
那如果session1就在这个基础上做修改,那还是会存在问题吗?
session2 1500
session1 read 仍是1200,但其执行
UPDATE account_innodb SET balance = balance - 100 WHERE id = 1;
commit;
再查询,结果是1400,是正确的,而不是我们之前预想的1100,原因是RR下是第一次select的时候生成的一个read view,之后都是读取这个read view,但是最后执行update的时候,是会加X锁的
4. 幻读问题
phantom read,事务A读取与搜索条件相匹配的若干行,事务B已插入或删除操作修改数据A操作的结果集,导致事务A看起来像出现幻觉一样
SERIALIZABLE事务隔离级别可避免
步骤1
session1
START TRANSACTION;
之后
SELECT * FROM account_innodb LOCK IN SHARE MODE;
为表里的三条记录加行级锁
步骤2
session2
START TRANSACTION;
INSERT INTO account_innodb VALUES(4,"newman",750);
步骤3
session1
UPDATE account_innodb SET balance = 1000;
对session1来说,它本想更新自己的三条记录,结果却更新了4条,好像出现的幻觉一样
以上是我们想象中的幻读的情况,但实际上innodb在session2 insert的时候,就会block住,也就是说innodb在REPEATABLE READ级别实现了对幻读问题的处理
为了模拟上面幻读的情况,可以将事务隔离级别降级到Read Committed