zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

数据库并发问题及四种隔离级别原理深入分析(最详细)

数据库并发原理 详细 级别 四种 隔离 深入分析
2023-09-27 14:29:07 时间

目前网上绝大部分文章只说了并发存在的问题以及隔离级别有哪些,很少有深入分析其具体的实现原理的,本文以最简单实例总结一下。


引言

数据库中事务要遵循的ACID特性:

  • 原子性(Atomicity)
  • 一致性(Consistensy)
  • 隔离性(Isolation)
  • 持久性(Duration)

其中隔离性体现的就是多个事务并发操作数据库时,应该是独立互不干扰的。实际情况中,隔离性是比较灵活的,设定了多个分层级别,主要是为了应对事务并发操作数据库时带来的几个常见问题。


并发常见问题

通常所说的并发问题包括脏读、不可重复读、幻读,这里把脏写也算上,其实和脏读是一类情况。

用两个事务的执行来举例,直观理解。

1 脏读

事务A事务B
UPDATE users SET age = 21 WHERE id = 1;

/* 还未提交 */
SELECT age FROM users WHERE id = 1;

/* age == 21是脏数据 */
ROLLBACK

事务B修改了表中某行的值,但未提交。此时事务A来了读取到了该行被事务B修改后的值,过一会事务B又回滚了。则事务A读取到的值为脏数据,用该数据做的一切操作在回滚后都无效了。

由于读到的数据不是真正写入数据库的,因此称为脏读。

2 脏写

事务A事务B
UPDATE users SET age = 21 WHERE id = 1;

/* 还未提交 */
UPDATE users SET age = 22 WHERE id = 1;

/* 读到脏数据后进行修改*/
ROLLBACK

事务B修改某行数据还未提交时,事务A也修改该行数据。然后事务B回滚了,事务A发现自己的修改无效。

由于写入的这个数据是无效的,因此称为脏写。

3 不可重复读

事务A事务B
SELECT age FROM users WHERE id = 1

/* age == 20是原数据 */
UPDATE users SET age = 21 WHERE id = 1;
COMMIT
SELECT age FROM users WHERE id = 1

/* age == 21是B修改后的数据 */

事务A先读取了表中某行的值,此时事务A还未结束。之后事务B来了修改了该值并且提交了,事务A一会又读了一遍该行值,发现前后不一样了。

由于从数据一致性的角度两次读结果不一样不符合逻辑,因此该问题称为不可重复读

4 幻读

事务A事务B
SELECT * FROM users WHERE age BETWEEN 10 AND 30;

/* A查询年龄在10到30岁之间的人 */
INSERT INTO users VALUES ( 3, ‘Bob’, 27 );

/* B插入了一条数据,年龄27 */
COMMIT
SELECT * FROM users WHERE age BETWEEN 10 AND 30;

/* A再查了一次,发现多了一条数据 */

事务A先查询了一些数据,此时事务A还未结束。事务B来了,插入了一些数据,然后事务A再按之前的条件查询了一次,发现查到的数据变多了。

由于站在事务A的角度看凭空多了一些数据,像幻象一样,因此称为幻读。


四种隔离级别实现原理

为了应对上述并发问题,InnoDB中设置四种隔离级别(Myisam不支持事务):

  • 未提交读——READ UNCOMMITED,解决脏写
  • 已提交读——READ COMMITED,解决脏读
  • 可重复读——REPEATABLE READ,解决脏读、不可重复读
  • 串行化——SERIALIZABLE,解决脏读、不可重复读、幻读

三种隔离级别依次解决每一个并发问题,下面层层递进分析每一种隔离级别的实现原理。

1 未提交读

未提交读是最松的一种并发策略,存在脏读、可重复读、幻读的问题。

如何解决脏写?
某行记录被事务A写过但未提交,则不能让事务B再写

实现措施:

  • 写数据加排他锁,禁止其他事务进行加锁读和加锁写,但允许不加锁读,以此解决脏写
  • 注意,排他锁的作用是防止其他事务再加锁,而不是不允许别人读,如果读数据不加锁,则可以读!!

2 已提交读

已提交读可以解决脏读问题,但还存在可重复读、幻读的问题。

如何解决脏读?
某行记录被事务A写过但未提交,则不能让事务B读到修改后的数据

实现措施:

  • 写数据加排他锁,读数据使用MVCC控制只能读旧版本数据,以此解决脏读
  • 关键点:每次select都获取最新readview

3 可重复读(Mysql默认隔离级别)

可重复读可以解决脏读、不可重复读问题,但还存在幻读的问题。

如何解决不可重复读?
某行记录被事务A写过,则不能让事务读到修改后的数据,不管A是否已提交

实现措施:

  • 写数据加排他锁,读数据使用MVCC控制只能读旧版本数据,以此解决不可重复读
  • 仅在事务第一次select时获取一个readview,一直到事务结束都只使用这一个readview

4 串行化

串行化可以解决脏读、不可重复读、幻读问题,所有问题都解决,但并发度最低

如何解决幻读?
注意,InnoDB的可重复读隔离级别下已经基本解决了幻读问题,通过MVCC+间隙锁的机制。串行化也能解决幻读,但其并发度很低,由于幻读是在其他行插入了新数据,因此直接锁整个表即可。

实现措施:
写数据直接使用表级锁!


总结

关于MVCC具体内容本文不再展开,知道InnoDB的隔离级别就是通过MVCC+锁机制实现的即可!