文章目录
基本概念
-
事务的四大特性:
- 原子性(A):事务的所有操作要么全部执行成功,要么全部执行失败,不会出现部分成功或者部分失败的情况
- 一致性©:事务在执行之前和执行之后,数据始终处于一致的状态
- 隔离性(I):并发执行的两个事务之间互不干扰,一个事务在执行过程中不能看到其他事务运行过程的中间状态。Mysql通过锁和MVCC机制来保证事务的隔离性
- 持久性(D):事务提交完成之后,此事务对数据的更改操作会被持久化到数据库中,并且不会被回滚
-
事务类型
- 扁平事务:所有操作要么全部成功,要么全部失败。无法回滚提交或者回滚整个事务中的部分事务
- 有保存点的扁平事务:带有保存点的扁平事务,可以在事务内部设置保存点(savepoint),可以达到将当前事务回滚到此位置的目的
- 链式事务:在带有扁平事务的基础上,自动将当前事务的上下文隐式地传递给下一个事务。即一个事务的提交操作和下一个事务的开始操作具备原子性,上一个事务的处理结果对下一个事务是可见的,事务与事务之间就像链条一样传递下去
- 嵌套事务:有多个事务处于嵌套状态,共同完成一项任务的处理,整个任务具备原子性
- 分布式事务:不同服务或数据库节点。整体事务中的所有分支事务要么全部成功,要么全部失败
本地事务
- 本地事务的执行流程
- 客户端开启一个连接会话
- 开始会话后,客户端发起开启事务的指令
- 事务开启后,客户端发送各种SQL语句处理数据
- 正常情况下,客户端会发起提交事务指令,异常情况下,会发起回滚事务指令
- 客户端关闭会话
- 优点
- 严格支持ACID特性
- 相对可靠,异常情况很少
- 执行效率高
- 事务状态可以只在数据库中维护,应用无需关心
- 编程简单,不涉及复杂的网络通信
- 缺点
- 不具备分布式事务的处理能力
- 一次事务过程只能连接一个支持事务的数据库,不能用于多个事务性数据库
并发事务带来的问题
- 多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)
- 并发可能导致的问题
- 脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,根据脏数据所做的操作可能是不正确的。
- 丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
- 不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
- 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
- 不可重复度和幻读区别:
- 不可重复度重点在于更新和删除操作,幻读重点在于插入操作、幻读无法通过行级锁来避免。需要使用串行化的事务隔离级别。Mysql在可重复读级别下,通过间隙锁在某种程度下可以解决幻读问题
- 同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 :事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导致A再读自己的工资时工资变为 2000;这就是不可重复读。
- 同样的条件, 第1次和第2次读出来的记录数不一样 :假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。
事务隔离级别
-
SQL 标准定义了四个隔离级别:
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
-
表格
隔离级别 脏读 不可重复读 幻读 READ-UNCOMMITTED √ √ √ READ-COMMITTED × √ √ REPEATABLE-READ × × √ SERIALIZABLE × × × -
与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读) 事务隔离级别下,允许应用使用 Next-Key Lock 锁算法来避免幻读的产生。这与其他数据库系统(如 SQL Server)是不同的。所以说虽然 InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读),但是可以通过应用加锁读(例如
select * from table for update
语句)来保证不会产生幻读,而这个加锁度使用到的机制就是 Next-Key Lock 锁算法。从而达到了 SQL 标准的 SERIALIZABLE(可串行化) 隔离级别。 -
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)**并不会有任何性能损失
实验数据准备
-
SQL脚本
DROP TABLE IF EXISTS `t_test`; CREATE TABLE `t_test` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `a` varchar(10) CHARACTER SET utf8 NOT NULL, `b` varchar(10) CHARACTER SET utf8 NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; BEGIN; INSERT INTO `t_test` VALUES (1, 'a1', 'b1'); INSERT INTO `t_test` VALUES (2, 'a1', 'b2'); INSERT INTO `t_test` VALUES (3, 'a2', 'b3'); INSERT INTO `t_test` VALUES (4, 'a3', 'b4'); INSERT INTO `t_test` VALUES (5, 'a1', 'b3'); COMMIT;
RC隔离级别
RC
的本质就是事务中每一条SELECT
语句均可以看到其他已提交事务对数据的修改,那么只要该事物已经提交其结果就是可见的,与这两个事务开始的先后顺序无关,不完全适用于 MVCC 读。
实验一:不可重复读
-
事务A第二次查询的结果是什么?
-
实验结果:事务A读取到事务B已经提交的数据。RC隔离级别下,会出现不可重复读。RC的本质就是事务中每一条 SELECT语句均可以看到其他已提交事务对数据的修改
实验二:幻读
-
事务A第二次查询获取的结果是什么?
-
实验结果:事务A读取到事务B已经提交的插入的数据。在RC隔离级别下会出现幻读,幻读重点在insert,不可重复读重点在Update和Delete
RR隔离级别
RR
的本质是从第一个SELECT
语句生成ReadView
开始,任何已经提交过的事务的修改均可见
实验一:可重复读
-
事务A第二次查询获取的结果是什么?(B事务未提交)。事务A第三次查询获取的结果是什么?(B事务已经提交)
-
实验结果:无论事务B是否提交,都不影响事务A的查询结果。
-
思考:如果想在事务A中获取最新的结果,该如何操作呢?
实验二:验证ReadView
-
验证ReadView是否是事务开启时生成的?
-
实验结果:从第一个
SELECT
语句生成ReadView
开始,任何已经提交过的事务的修改均可见。事务A select和事务B提交的顺序不同就会导致不同的数据
实验三:幻读
-
事务A是否能读取到事务B插入的数据呢?
-
实验结果:事务A读取不到事务B插入的数据,没有幻读发生
-
在快照读情况下,mysql通过mvcc(多版本并发控制)来避免幻读
-
思考:
-
如果事务A最后的查询加上for update是否可以查询出最新数据?(可以,查询出三条数据,for update强制将快照读变为当前读,即产生了幻读)
-
如果事务A执行完for update后再次使用普通查询是否可以查询到最新数据?(不可以,再次的普通查询只能查询出2条数据)
-
实验四:幻读(当前读)
-
验证实验三的思考问题,RR隔离级别下当前读产生的幻读问题?
-
实验结果:如果是当前读,会出现幻读问题
当前读与快照读
-
在RR级别中,数据是可重复读,对于这种读取历史数据的方式,叫做快照读(snapshot read),读取数据库当前版本数据的方式,叫当前读 (current read)。
-
快照读就是普通的select
- select * from table
-
当前读:特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁
- select * from table where xxx lock in share mode;
- select * from table where xxx for update
- insert into
- update
- delete
-
在快照读情况下,mysql通过mvcc(多版本并发控制)来避免幻读。在当前读读情况下,mysql通过next-key来避免幻读。
select * from t where a=1;属于快照读 select * from t where a=1 lock in share mode;属于当前读 在RR级别下,如果是快照读,那么其实是可以避免幻读的。如果是快照读+当前读,那么其实是不能避免幻读的