https://tech.meituan.com/2014/08/20/innodb-lock.html
查看当前会话的事务隔离级别
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.03 sec)
查看全局事务隔离级别
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.04 sec)
四种bug:
(1)脏读:(不同事务间,快照解决)
例:insert into T values (4, '牛D');
,然后没commit。
一个事务内的SELECT
读取到的数据是另一个事务未commit的数据。(数据库只修改了缓存没修改外存,但是不同事务的select会读到之前另一事务update的数据)
(2)不可重复读:(同一事务,第一次select后的数据上行级锁)
例:同一个事务内,对同一行的两个相同的查询却返回了不同数据(因为中间有其他事务修改/删除了值并且提交成功)
(3)幻读:(可以加间隙锁解决)
例:某个事务在读取某个范围的数据,但是另一个事务又向这个范围的数据去insert数据,导致多次读取的时候,数据的行数不一致。(delete是不可重复读,因为读的内容少了,对同一行的查询出现了不一致,insert对同一行的查询没有不一致)
隔离级别 | 脏读 | 不可重复读 | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable) | 不可能 | 不可能 | 不可能 |
- 可重复读的目标:
(1)版本未提交,不可见;
(2)版本已提交,但是是在快照创建后提交的,不可见;
(3)版本已提交,而且是在快照创建前提交的,可见。
InnoDB事务隔离级别:
set session transaction isolation level repeatable read; // 设置当前会话隔离级别
- READ UNCIMMITTED(未提交读)
在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。
- READ COMMITTED(提交读)
就是只能读到已经提交了的内容。读操作事务要等待更新操作事务提交后才能读取数据。
为什么提交读,未提交读都没有查询加锁,但是却能够避免脏读呢?
这就要说道另一个机制-快照(snapshot)(读取比该事务早的最后一次提交值)
假设当A开启了事务,然后没有执行任何操作,这时候B insert了一条数据然后commit,这时候A执行 select,那么返回的数据中就会有B添加的那条数据。之后无论再有其他事务commit都没有关系,因为快照已经生成了,后面的select都是根据快照来的。
假设没有“快照读”,那么当一个更新的事务没有提交时,另一个对更新数据进行查询的事务会因为无法查询而被阻塞,这种情况下,并发能力就相当的差。
而“快照读”就可以完成高并发的查询,不过,“读提交”只能避免
脏读
,并不能避免“不可重复读”和“幻读”。
快照读只是对select默认开启,对于会对数据修改的操作(update、insert、delete)都是采用当前读的模式,对于修改的操作(update、insert、delete)依旧存在幻读问题。在执行这几个操作时会读取最新的记录,即使是别的事务提交的数据也可以查询到。假设要update一条记录,但是在另一个事务中已经delete掉这条数据并且commit了,如果update就会产生冲突,所以在update的时候需要知道最新的数据。
类型 | 语句 |
---|---|
快照读 | select xxx from xxx |
当前读 | (1)select xxx from xxx for update (2) select xxx from xxx lock in share mode (3) insert/delete/update ...
|
select的当前读需要手动的加锁:
select * from table where ? lock in share mode;
select * from table where ? for update;
select ... for update 会让innodb对查询结果中的每一行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。(必须开启事务)
Read Committed隔离级别:每次select都生成一个快照读。
快照读的实现方式:undolog和多版本并发控制MVCC
- REPEATABLE READ(可重复读)
普通的查询同样是使用的“快照读”,但是,和“读提交”不同的是,当事务启动时,就不允许该行进行“修改操作(Update)”了,而“不可重复读”恰恰是因为两次读取之间进行了数据的修改,因此,“可重复读”能够有效的避免不可重复读
,但却避免不了“幻读”,因为幻读是由于“插入操作(INSERT)”而产生的,而不是更新(UPDATE)
在RR隔离级别下,当事务第一次进行快照读,仅此一次创建read-view视图。
RR隔离级别采用Next-key Lock(间隙锁) 来解决幻读问题。因此 RR 下面 幻读,可重复读,脏读 三者都不会发生。
- SERIALIZABLE(可串行化)
这种级别下,事务“串行化顺序执行”,也就是一个一个排队执行。由于他大量加上锁,导致大量的请求超时,因此性能会比较底下,再特别需要数据一致性且并发量不需要那么大的时候才可能考虑这个隔离级别。
示例
(1)如果事务 B 的隔离级别是读未提交(RU),那么两次读取均读取到 x 的最新值,即 20。
(2)如果事务 B 的隔离级别是读已提交(RC),那么第一次读取到旧值 10,第二次因为事务 A 已经提交,则读取到新值 20。
(3)如果事务 B 的隔离级别是可重复读或者串行(RR,S),则两次均读到旧值 10,不论事务 A 是否已经提交。