事务
事务就是一组原子性的SQL查询,或者说一个独立的工作单元
事务的ACID
A Atomicity 原子性
一个事务必须被视为一个不可分割的最小工作单位,整个事务中所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中一部分
C Consistency 一致性
事务总是从一个一致性状态转移到另一个一致性状态
例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不会变的
I Isolation 隔离性
一个事务所做的修改在最终提交之前,对其他事务是不可见的
D Durability 持久性
一旦事务提交,则其所做的修改就会永远保存到数据库中,即使数据库发生故障也不会对其有任何影响
并发事务可能带来的问题
脏读
事务A在访问数据并对数据做了修改后,但是还没提交事务,此时事务B也在访问这个数据,并使用了事务A修改后但未提交的数据
丢失修改
不可重复读
事务A多次读同一个数据时,在这个事务未结束前,事务B也读了该数据,如果事务B对数据进行了修改,就会导致事务A两次读到的数据不一致
幻读
事务A读取了几行数据,事务B插入了一些数据,随后的查询中,事务A发现多了一些原本不存在的数据,就像产生了幻觉一样
事务隔离机制
事务的隔离级别
Level 1 Read Uncommited(读未提交)
事务A修改了数据,但未提交前,事务B可以读取到事务A修改的数据
Level 2 Read Commited(读提交)
事务A修改了数据,但未提交前,事务B不可以读取到事务A修改的数据,只有在事务A提交后,事务B才可以读取到修改的数据
Level 3 Repeatable Read / RR(可重复读取)
事务A修改了数据,无论是否已经提交,事务B整个过程中都无法读取到事务A修改的数据,只有重新开一个事务才可以读取到,事务B可以读取到自己修改的数据
Level 4 Serializable(可串行化)
要求事务串行执行,这样事务之间就完全不能产生干扰
事务的隔离级别的问题
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
| :-------------: | :--: | :--------: | :--: |
| Read Uncommited | ✔️ | ✔️ | ✔️ |
| Read Commited | ❌ | ✔️ | ✔️ |
| Repeatable Read | ❌ | ❌ | ✔️ |
| Serializable | ❌ | ❌ | ❌ |
业务场景问题
Case1
问题回顾
在此前MySQL无法获取最新数据的解决方法文章中,我们利用Flask + Websocket的长连接做了一个消息推送服务,同时数据库会不断更新数据,结果发现一旦连接上后,即使修改了数据库的数据,返回的数据始终不变
然而无论我手动登录MySQL,或者另外写个程序去读取MySQL,数据是可以看到更新的
问题排查
之前参考了CSDN上的解决方法,通过查找事务隔离级别
mysql> show variables like '%iso%';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)
可以看到,事务隔离级别是REPEATABLE-READ,也就是可重复读,根据前面的介绍
事务A修改了数据,无论是否已经提交,事务B整个过程中都无法读取到事务A修改的数据,只有重新开一个事务才可以读取到,事务B可以读取到自己修改的数据
实际就是,事务A负责搜集数据并修改数据库,虽然事务A已经进行了事务提交,但是由于之前Websocket里的写法是通过同一个事务B轮询去查找数据库,检查是否有数据更新,因此每次取到的数据都是一样的
问题处理
此前处理方法,实际是将事务隔离级别修改到了Read Commited
执行
mysql> set global TRANSACTION ISOLATION LEVEL READ COMMITTED ;
Query OK, 0 rows affected (0.00 sec)
此时再去查找
mysql> show variables like '%iso%';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)
mysql> show global variables like '%iso%';
+---------------+----------------+
| Variable_name | Value |
+---------------+----------------+
| tx_isolation | READ-COMMITTED |
+---------------+----------------+
1 row in set (0.00 sec)
在Read Commited下,事务A修改数据并提交后,事务B是可以读取到数据的
另一种方法,可以尝试重新获取事务B,然后读取数据,但这样的持续开启事务会有一定的开销,其它方案待探索