1. 数据并发问题
MySQL是一个客户端/服务器
架构的软件,可以有若干个客户端与之连接,连接上之后,就可以称之为一个会话(Session
),对于服务器来说可能同时处理多个Session
,也就是对服务器来说可能同时处理多个事务。但是由于事务的隔离性,理论上如果多个事务有对同一个表中的数据进行的操作,他们的执行顺序应该按照事务到达的顺序进行排队
(也叫序列化),但是这样对性能影响太大,比如SessionA,SessionB,SessionC都提交了一个事务,事务到达的顺序为A -> C -> B
:
对比上面的图片我们发现虽然序列化可以满足事务的隔离性问题,但是对性能影响太大
,难道就没有什么两全其美的方法吗?在解决问题之前,我们得先明白同时处理多个Session
时可能会出现一系列并发问题,具体总结为以下几种:
- 脏写
- 脏读
- 不可重复读
- 幻读
创建一个表tb_student
,数据如下:
下面我将根据该表数据进行通过举例+说明的方式进行解释。
1.1 脏写(Dirty Write)
脏写示意图:
执行次序 | Session A | Session B |
---|---|---|
1 | 事务开始 | |
2 | 事务开始 | |
3 | UPDATE tb_student SET name = ‘lisi’ WHERE stu_id = 1 | |
4 | UPDATE tb_student SET name = ‘wangwu’ WHERE stu_id = 1 | |
5 | 事务提交 | |
6 | 事务回滚 |
-
前提事件: Session A和Session B各自开启了一个事务,并且他们都修改了同一个表的同一个字段
-
附加事件: Session A在将"zhangsan"修改为"wangwu"后并提交事务,Session B却回滚了事务
-
导致结果: 本该成功修改的"wangwu"又变回了"zhangsan",由于SessionA修改了一个未提交的数据,导致SessionA修改数据失败,这就是脏写问题。
1.2 脏读(Dirty Read)
脏读示意图:
执行次序 | Session A | Session B |
---|---|---|
1 | 事务开始 | |
2 | 事务开始 | |
3 | UPDATE tb_student SET name = ‘lisi’ WHERE stu_id = 1 | |
4 | SELECT * FROM tb_student WHERE stu_id = 1;(此时name=“lisi”) | |
5 | 事务提交 | |
6 | 事务回滚 |
-
前提事件: Session A和Session B各自开启了一个事务,Session A查询数据,Session B更改数据,它们的操作都是针对同一个表的。
-
附加事件: 并且Session A在查询数据之前,Session B对数据进行了修改。此时Session A 读到的数据name值为"lisi",但是由于Session B 回滚事务。
-
导致结果: name的实际值变又回了"zhangsan",此时Session A读到了一个未提交"脏数据",这就是脏读。
1.3 不可重复读(NON-REPEATED READ)
不可重复读示意图:
执行次序 | Session A | Session B |
---|---|---|
1 | 事务开始 | |
2 | 事务开始 | |
3 | SELECT * FROM tb_student WHERE stu_id = 1;(此时name=“zhangsan”) | |
4 | UPDATE tb_student SET name = ‘lisi’ WHERE stu_id = 1 | |
5 | 事务提交 | |
6 | SELECT * FROM tb_student WHERE stu_id = 1;(此时name=“lisi”) | |
7 | 事务提交 |
前提事件: Session A和Session B各自开启了一个事务,Session A查询数据, Session B更改数据,它们的操作都是针对同一个表的
附加事件: Session A的事务中进行了两次(或多次)查询,在两次查询之间,Session B中的事务对表中的数据进行了修改。
导致结果: 两次(或多次)查询的结果不一样,这就是不可重复读问题。
1.4 幻读(Phantom)
幻读示意图:
执行次序 | Session A | Session B |
---|---|---|
1 | 事务开始 | |
2 | 事务开始 | |
3 | SELECT * FROM tb_student(此时只读到一条数据) | |
4 | INSERT INTO tb_student VALUES(2,‘lisi’,‘男’,19) | |
5 | 事务提交 | |
6 | SELECT * FROM tb_student;(此时读到了两条数据) | |
7 | 事务提交 |
前提条件: Session A和Session B各自开启了一个事务,Session A查询多条数据, Session B插入新数据,它们的操作都是针对同一个表的
附加条件: Session A的事务中进行了两次(或多次)查询,在两次查询之间,Session B中的事务在表中新增数据。
导致结果: SessionA再次读取同一个表,读到了先前没读到的记录,就像产生了幻觉,这就是幻读问题。
2. 四种隔离级别
前面所提到的并发问题亦有轻重之分:脏写 > 脏读 > 不可重复读 > 幻读
我们可以为了提高性能,根据业务的需要进行一定的取舍,这时就需要通过设置隔离级别来实现了,接下来我来为大家介绍一下SQL中的四种隔离级别,以及分别解决了前面提到的哪些并发问题。
注意:由于脏写问题太过恶劣,数据库不允许这种行为,因此所有的隔离级别都是在解决脏写问题的前提下谈的。
SQL标准中设立了下面4种隔离级别(级别从低到高):
-
READ UNCOMMITTED
:读未提交,在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读。 -
READ COMMITED
:读已提交:它满足了隔离的简单定义:一个事务只能看见已提交事务所做的改变。这是绝大多数数据库系统的隔离级别(MySQL不是)。可以避免脏读。 -
REPEATABLE READ
:可重复读,如果事务B对表中的数据进行了修改并提交,那么事务A多次读到的数据内容都相同。可以避免脏读、不可重复读,这也是MySQL中的默认隔离级别。 -
SERIALIZABLE
:可序列化,确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表进行任何操作。解决了所有并发问题,但是性能十分低下。
可总结为下面的表格:
隔离级别 | 解决脏读 | 解决不可重复读 | 解决幻读 |
---|---|---|---|
READ UNCOMMITTED | NO | NO | NO |
READ COMMITED | YES | NO | NO |
REPEATABLE READ | YES | YES | NO |
SERIALIZABLE | YES | YES | YES |