Mysql学习笔记----事务

520 阅读8分钟

前言
学习并记录下Mysql事务的相关知识点。

Mysql事务定义

事务就是一组原子性的SQL命令,这些SQL命令要么全部成功,要么全部失败。
如果没有显式地开启事务,那么默认会为每条SQL都自动提交(mysql中autocommit默认配置为true)。

事务四大特性(ACID)

事务拥有四大特性,分别是原子性、一致性、隔离性和持久性。原子性、隔离性和持久性都是为了保证最终一致性。

原子性(Atomicity)

事务是原子的、不可分割地,一个事务中的所有操作要么全部成功,要么全部失败。
事务执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。

一致性(Consistency)

事务执行前后,数据库总是从一个一致性状态转化为另一个一致性状态。

隔离性(Isolation)

一个事务在提交前,所作的修改对其它事务都是不可见的。多个事务之间可以并发执行,而不破坏数据库的完整性。
Mysql存在多种隔离级别,在不同的级别间,事务的并发程度不同。

持久性(Durability)

事务一旦提交,对数据库的修改就要永久保存,不能被回滚,即便数据库宕机所作的修改也不会丢失。

事务问题:脏读、不可重复读和幻读

前面提到Mysql中存在多种隔离级别,不同隔离级别存在一些不同的事务问题,先介绍几个事务问题。

  • 脏读:一个事务可以读取到其它未提交事务修改的数据。
  • 不可重复读:要求只能读取到已提交事务的修改,但这可能导致当前事务对同一个数据多次读取结果不同,因为中间有其它事务提交了数据,使得旧的数据不能被重复读取(或者说再次读取时数据发生变化)。
  • 幻读:当一个事务读取某一范围的数据时,另一个事务在该范围内增加或删除了一条数据,使得之前的数据再次读取该范围数据时发现时,会产生幻行(多了一行或者少了一行)。

说明:不可重复读幻读区别:不可重复读是多次读取同一条记录,数据发生变化;而幻读是多次读取某个范围内数据,数据记录数发生变化。

事务隔离级别

Mysql中定义了四种事务隔离级别,分别是未提交读提交读可重复读可串行化。这四个隔离级别由低到高,相应事务并发程度也逐渐降低。

  • Read-Uncommitted(未提交读):在该隔离级别,事务可以读取到其它事务未提交的修改,出现脏读问题。
  • Read-Committed(提交读):在该隔离级别下,事务只能读取到已提交事务的修改。解决了脏读,但存在不可重复读问题。
  • Repeatable-Read(可重复读):在该隔离级别下,同一个事务多次读取同一个记录得到相同结果。解决了不可重复读问题,但存在幻读。(InnoDB引擎的默认隔离级别)
  • Serializable(可串行化):最高的隔离级别,在该级别下,对读取的每一行都进行了加锁(并不是表级锁),可能导致大量锁竞争和超时问题。实际中使用场景较少。
隔离级别脏读不可重复读幻读
Read-Uncommitted存在存在存在
Read-Committed不存在存在存在
Repeatable-Read不存在不存在存在
Serializable不存在不存在不存在

测试不同隔离级别中的事务问题

1. Mysql事务相关命令

事务命令

// 开启事务
start transaction;
// 回滚
rollback;
// 提交事务
commit;

查看设置隔离级别命令

select @@session.tx_isolation;
set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;

其它事务相关命令参考:

https://www.runoob.com/mysql/mysql-transaction.html

2. 数据库表准备

CREATE TABLE `account` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `money` int(11) NOT NULL DEFAULT '0',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8

3. 验证Read Uncommitted级别下脏读问题

(1)设置隔离级别为未提交读;

(2)事务1开启事务,并查询数据

(3)事务2开启事务,并修改记录(两个事务都需要设置下隔离级别)

(4)事务1再次查询记录,发现数据改变,出现脏读

(5)事务2回滚对id=2记录的修改

(6)事务1发现记录再次变化

结论

从上面事务1先后查询结果来看,在Read Uncommitted隔离级别,其它事务的未提交修改(包括回滚操作的影响)都可以被读取到。

4. 验证Read Committed级别下,解决了脏读问题,仍存在不可重复读问题

(1)两个事务分别修改事务隔离级别

(2)事务1开启事务并查询数据

(3)事务2开启事务并修改数据

(4)事务1再次查询数据,发现数据未变化

(5)事务2提交

(6)事务1重新查询数据,发现数据变化

结论

从事务1先后3次查询结果变化来看,Read Committed解决了脏读问题,但是存在不可重复读问题(记录id=2的money=1600数值不能被重复读取)

5. 验证Repeatable Read解决了不可重复读问题

(1)两个事务分别修改隔离级别

(2)事务1开启事务并查询数据

(3)事务2开启事务、修改数据后并提交

(4)事务1再次查询数据,发现数据没有变化,可重复读取

结论

由(4)查询结果可见,Repeatable Read级别解决了不可重复读问题。

(5)尝试在事务1中修改事务2中被修改的数据,当前事务money=1700,事务2提交后money=1800,在事务1中增加100,发现money=1900

可见在进行写操作时,读取了其它事务已提交的数据,并且在新数据基础上进行了写操作。(应该是针对写操作做的处理)

6. 验证Repeatable隔离级别下幻读问题

(1)修改事务隔离级别
(2)事务1开启事务,并查询范围数据

image.png

(3)事务2开启事务,并增加一条记录

image.png

(4)事务1再次查询范围数据,发现没有出现幻读现象
  • 没有出现幻读原因:MVCC 机制在Repeatable-read 级别生效,事务1使用了快照读,不会读取到事务2中新增加的版本不一致的行,所以不会有幻读。
(5)事务1执行insert操作后再次读取范围数据,发现出现幻读
  • 这里出现幻读的原因应该是 insert 操作只能使用当前读,而不能使用快照读。
    image.png
结论1

Innodb引擎下Repeatable Read隔离级别,MVCC 并未解决幻读问题。

  • 如果事务只使用查询,应该是不会出现幻读的。
(6)事务1尝试增加相同id记录,发现会一直等待,直到事务2提交后,并且事务1无法执行成功。(可见这里写操作对行进行了加锁的动作)

结论2

Repeatable Read级别对写操作进行了加锁动作,写写互斥,需要等待其它执行写操作的事务提交。

7. 验证可串行化级别

(1)修改隔离级别

(2)事务1开启事务并查询记录数

(3)事务2开启事务并增加记录,发现事务2一直处于等待,直到事务1提交(这点与Repeatable Read级别不同,事务1只是读取两颗数据、未进行修改,事务2这时修改数据也发生了锁竞争)

结论1

由(3)结果现象可见,在可串行化级别,读写操作都进行了加锁动作,写读互斥。

(4)验证Serializable级别是对整个表进行加锁,还是对事务内所涉及的行都进行了加锁

事务1开启事务,并且查询id>9的记录。(此时事务1已经对id>9的记录都加了行锁)

事务2开启事务,并且更新id=2的记录,可见成功更新,但在更新id=10的记录时等待锁。

结论2

由(4)现象,Serializable级别是对事务中每个访问的行都加锁,而不是对整个表加锁。

小结

本文先是介绍了事务的基本含义,然后介绍了事务的特性,然后主要针对事务的隔离性,探究了在不同隔离级别下存在的事务问题,并实际验证了相应的问题。

参考资料

https://juejin.cn/post/6844903994188365831
https://juejin.cn/post/6844903661131268104
https://www.runoob.com/mysql/mysql-transaction.html