先了解一下事务的四大特性:ACID

原子性(Atomicity)

原子性就是不可拆分的特性,要么全部成功然后提交(commit),要么全部失败然后回滚(rollback)。MySQL通过Redo Log重做日志实现了原子性,在将执行SQL语句时,会先写入redo log buffer,再执行SQL语句,若SQL语句执行出错就会根据redo log buffer中的记录来执行回滚操作,由此拥有原子性。

一致性(Consistency)

一致性指事务将数据库从一种状态转变为下一种一致的状态。比如有一个字段name有唯一索引约束,那么在事务前后都不能有重复的name出现违反唯一索引约束,否则回滚。MySQL通过undo Log实现一致性,执行SQL语句时,会先写入undo log再写入 redo log buffer。undo是逻辑日志,会根据之前的SQL语句进行相应回滚,并且除了回滚,undo log还有一个作用是MVCC,当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可通过undo读取之前的行版本信息,实现非锁定读取。并且undo log也会产生redo log,因为undo log也需要持久性的保护。

隔离性(Isolation)

如果没有隔离性会发生如下问题:

1,数据丢失:A事务撤销时,把已经提交的B事务的更新数据覆盖了。

2,脏读:脏读主要是读取到了其他事务的数据,而其他事务随后发生回滚。MySQL通过三级封锁协议的第二级解决了脏读,在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。

3,不可重复读:不可重复读是读取到数据后,随后其他事务对数据发生了修改,无法再次读取。MySQL通过三级封锁协议的第三级解决了不可重复读。在二级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。

4,幻读:幻读是读取到数据后,随后其他事务对数据发生了新增,无法再次读取。在InnoDB引擎Repeatable Read的隔离级别下,MySQL通过Next-Key Lock以及MVCC解决了幻读,事务中分为当前读以及快照读。

  • 快照读(snapshot read) ——通过MVCC来避免幻读

    简单的select操作(不包括 select … lock in share mode, select … for update)

  • 当前读(current read) ——通过Next-Key Lock 来避免幻读 Next-Key Lock即间隙锁(Gap Lock)+行锁 (Record Lock);

持久性(Durability)

一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。具体实现原理就是在事务commit之前会将,redo log buffer中的数据持久化到硬盘中的redo log file,这样在commit的时候,硬盘中已经有了我们修改或新增的数据,由此做到持久化。

 

Mysql的四大隔离级别

区别


Mysql默认隔离级别REPEATABLE-READ


演示四大隔离级别

建表语句

DROP TABLE IF EXISTS `user_account`;

CREATE TABLE `user_account` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `user_id` int(11) NOT NULL,

  `balance` int(11) NOT NULL,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

当前环境和版本


读未提交read-uncommit

打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表user_account的初始值:


在客户端A的事务提交之前,打开另一个客户端B,更新表user_account:


此时在回到客户端A查询数据结果为:


这时候客户端B事务还没有提交,然后客户端A就能查到数据客户端B更新的数据了。那么一旦客户端B执行(ROLLBACK)回滚了事务,那么此时就会导致客户端A的数据脏读。

提交已读(read-commit)

打开一个客户端A,并设置当前事务模式为read committed(读已提交),查询表user_account的所有记录:


在客户端A的事务提交之前,打开另一个客户端B,更新表user_account并提交;


在客户端A查询表account的所有记录,与步骤(1)查询结果一致,没有出现不可重复读的问题


可重复读(repeatable-read)

打开一个客户端A,并设置当前事务模式为repeatable read,查询表user_account的所有记录


在客户端A的事务提交之前,打开另一个客户端B,更新表user_account并提交


在客户端A查询表user_account的所有记录,与客户端执行事务之前查询结果一致,没有出现不可重复读的问题


在客户端B插入一条数据


在客户端A中还是没有数据


串行化(serializable

打开一个客户端A,并设置当前事务模式为serializable,查询表user_account的初始值:


打开一个客户端B,并设置当前事务模式为serializable,插入一条记录报错,表被锁了插入失败,mysql中事务隔离级别为serializable时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低,开发中很少会用到


补充:

1、事务隔离级别为读提交时,写数据只会锁住相应的行

2、事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。

3、事务隔离级别为串行化时,读写数据都会锁住整张表

4、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

5、MYSQL MVCC实现机制参考链接:

https://blog.csdn.net/whoamiyang/article/details/51901888

6、关于next-key 锁可以参考链接:

https://blog.csdn.net/bigtree_3721/article/details/73731377

参考:https://www.cnblogs.com/huanongying/p/7021555.html