先了解一下事务的四大特性: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、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。