MySQL 事务并发带来的问题以及其解决方案分析
一、MySQL 事务(Transaction)及其 ACID 属性
事务是由一组 SQL 语句组成的逻辑处理单元,事务具有以下 4 个属性,通常简称为事务的 ACID 属性。
1、原子性(Atomicity)
事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
2、一致性(Consistent)
在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如 B 树索引或双向链表)也都必须是正确的。
3、隔离性(Isolation)
数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
4、持久性(Durable)
事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
二、可能会带来的问题
1、更新丢失(Lost Update)
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题:最后的更新覆盖了由其他事务所做的更新。
2、脏读(Dirty Reads)
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象的叫做“脏读”。
一句话:事务 A 读取到了事务 B 已经修改但尚未提交的数据,还在这个数据基础上做了操作。此时,如果 B 事务回滚,A 读取的数据无效,不符合一致性要求。
3、不可重复读(Non-Repeatable Reads)
一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”,重复读到的是不同的数据。
一句话:事务 A 读取到了事务 B 已经提交的修改数据,不符合隔离性。
4、幻读(Phantom Reads)
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。 一句话:事务 A 读取到了事务 B 提交的新增数据,不符合隔离性。
这里举一个简单的幻读的例子,事务 A 查询到表 T 有 1,2,3 条记录,然后事务 B 新插入的记录 4,此时如果事务 A 对记录 4 进行更新,发现竟然可以更新成功,那么就让人产生幻觉的感觉,明明表只有 1,2,3 条记录,为啥记录 4 也可以更新成功?
三、解决方案
解决更新丢失的方法有如下两个:
方法 1:使用事务+锁定读,也就是 for update,
方法 2:不使用事务,用 CAS 自旋来操作。
而脏读、不可重复读和幻读其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。那有哪些隔离级别呢,如下图:
假设现在有两个事务,事务 A 和事务 B,那么上面的四种隔离级别是什么意思呢?
1、读未提交(Read uncommitted)
字面意思是可以读到别的事务未提交的数据,也就是事务 A 可以读取事务 B 未提交的数据,这种情况肯定可能会导致脏读、不可重复度和幻读。
2、读已提交(Read committed)
字面意思是只可以读到别的事务已提交的数据,也就是事务 A 只可以读取到事务 B 已经提交了的数据,那么在 B 未提交之前的数据是读取不到的,也就不可能产生脏读,但是因为事务 B 已提交的数据是可以读取到的,所以可能会导致不可重复读和幻读。
3、可重复读(Repeatable read)
字面意思是事务可以重复读取数据,在事务期间,每次读取的数据都是一样的,也就是事务 A 开启事务后,读取了某一个表的 5 条数据,不管你事务 B 怎么对这 5 条数据修改操作,我事务 A 每次查询都是 5 条一摸一样的数据,所以是可重复读的,因此不可能导致脏读,不可重复读,但是还是可能导致幻读的。
4、可串行化(Serializable)
mysql 中事务隔离级别为 serializable 时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低,开发中很少会用到。
四、查看设置 MySQL 事务隔离级别
1、查看当前会话隔离级别
2、查看系统当前隔离级别
3、设置当前会话隔离级别
4、设置系统当前隔离级别
mysql 的默认隔离级别是 REPEATABLE-READ,也就是可重复读,这种情况下不可能产生脏读和不可重复读的问题。
五、案例分析
我们通过举个例子来测试下不同的个的隔离级别及其可以解决的并发问题,这里先建一个表:登录 mysql
这里输入密码,我的是 123456,然后执行建表语句,我这里用的数据库是 test,没有的话可以先建!
1、读未提交(Read uncommitted)级别案例分析
我们设置系统当前会话隔离级别为:Read uncommitted
我们可以看到隔离级别已经调为 read uncommitted。我们知道,这个级别是可能会发生脏读、不可重复度、幻读的,我们这里只需要举一个脏读的例子即可,毕竟如果脏读都发生了,那么不可重复读和幻读必然可能发生。
用例如下:1、事务 A 开启事务,查询 test 表 2、事务 B 开启事务,查询 test 表,后将 id 为 1 的记录修改为 A13、此时事务 B 未提交,查看事务 A 能否读取到 A1,若能读取,表明发生了脏读。
由例子可以知道,事务 B 未提交,但是事务 A 却已经读到了事务 B 修改的数据,所以发生了脏读。
2、读已提交(Read committed)级别案例分析
我们设置系统当前会话隔离级别为:Read committed
我们可以看到隔离级别已经调为 read committed。我们知道,这个级别是不可能会发生脏读,但是可能会发生不可重复度、幻读。那么我们重新按照上面的例子看看有没有发生脏读。
脏读用例如下:1、事务 A 开启事务,查询 test 表 2、事务 B 开启事务,查询 test 表,后将 id 为 1 的记录修改为 A23、此时事务 B 未提交,查看事务 A 能否读取到 A2,若能读取,表明发生了脏读。
我们可以看到,当隔离级别调成读已提交(Read committed)后,脏读就没有发生了,但是此时我们事务 B 提交,然后 A 再读,结果如下:
我们看到,事务 A 也变化了,表明发生了不可重复读,事务 A 第一次和第二次读取的结果发生了变化。
3、可重复读(Repeatable read)级别案例分析
我们设置系统当前会话隔离级别为:Repeatable read
我们可以看到隔离级别已经调为 repeatable read;。我们知道,这个级别是不可能会发生脏读,和不可重复度,但是可能发生幻读。那么我们重新按照上面的例子看看有没有发生脏读和不可重复读。
不可重复读用例如下:
1、事务 A 开启事务,查询 test 表 2、事务 B 开启事务,查询 test 表,后将 id 为 1 的记录修改为 A33、此时事务 B 未提交,查看事务 A 能否读取到 A3,若不能读取,表明未发生脏读。4、此时把事务 B 提交,查看事务 A 能否读取到 A3,若还是不能,表明未发生不可重复读。
有结果可以知道,事务 A 读取的记录一直都是 A2,就算事务 B 提交了也不会有影响,所以可重复读(Repeatable read)级别不可能会发生脏读和不可重复读。那可不可能发生幻读呢?
我们在事务 B 插入一条数据(4,D),然后提交。
然后 A 也修改 id 未 4 的数据把 D 修改未 D1,发现修改成功了,明明本来没有数据 D 的,现在突然就有了,好像出现了幻觉一样,所以这里发生了幻读。
4、可串行化(Serializable)级别案例分析
我们设置系统当前会话隔离级别为:Serializable
我们可以看到隔离级别已经调为 read committed。我们知道,这个级别是不可能会发生脏读、不可重复度和幻读。那么我们重新按照上面的例子看看有没有发生幻读。
幻读案例
1、事务 A 开启事务,查询 test 表 2、事务 B 插入一条数据(5,E)
我们发现,事务 B 直接卡住了,所以不可能发生幻读。
评论