今天学习MySQL的事务隔离级别。
MySQL的事务隔离级别设置分为4种,默认为可重复读(RepeatableRead)。下面的表格是事务隔离级别与之并发事务可能产生的问题对应关系。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(Read-Uncommitted) | √ | √ | √ |
读已提交(Read-Committed) | × | √ | √ |
可重复读(Repeatable-Read) | × | × | √ |
可串行化(Serializable) | × | × | × |
了解并发事务产生的一系列问题之前,首先学习一下MySQL的事务
语句 | 说明 |
---|---|
begin | 开启事务 |
start transaction | 开启事务 |
commit | 提交事务 |
rollback | 回滚事务 |
savepoint identifier | savepoint允许在事务中创建一个保存点,一个事务中可以有多个 savepoint |
release savepoint identifier |
删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常 |
rollback to identifier | 把事务回滚到标记点 |
set transaction | 设置事务隔离级别 |
MySQL并发事务可能带来的问题
丢失更新:两个事务同时对一条数据更改,另外一个事务结果覆盖之前事务结果,造成更新丢失。比如有一条数据为count=100,A事务做count-10,B事务做count-5,正确的结果应该是count=75。但是若A和B两个事务同时执行时,若A事务先执行,count=count-10===>100-10=90,但未提交;此时事务B也正在执行,读取的count仍然为100,那么count-5=95,若事务B比事务A晚完成,那么事务B的结果将覆盖事务A的结果,使得数据库的结果为95,这个结果显然是不正确的。丢失更新的解决方法是加锁,对写入操作加锁。
脏读:一个事务读取到了另外一个事务尚未提交的修改或者删除后的数据。
不可重复读:一个事务读取到了另外一个事务已提交的修改或者删除后的数据。
幻读:一个事务读取到了另外一个事务已提交的新增数据。
丢失更新和其他三个的区别是丢失更新都是更新操作的数据问题,而其他三个是读操作和写操作之前的数据问题
我打开两个windows的控制窗口,模拟两个事务(并发事务)。
首先登陆mysql
(1)win+r打开windows窗口,
(2)输入cmd命令,
(3)打开窗口之后,输入mysql -u root -p,回车,之后录入你本地数据库密码
(4)输入“show databases”命令,展示当前本地数据库下有哪些数据库
(5)输入“use test01”,其中,“test01”是数据库名称。
(6)输入“show tables”,展示当前数据库表列表,我以student表为例,演示以下几种隔离级别可能存在的问题:
建表语句以及数据sql:
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) COLLATE utf8_bin NOT NULL COMMENT '姓名',
`score` int(11) NOT NULL COMMENT '成绩',
`classid` int(11) NOT NULL COMMENT '班级',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='学生';
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('12', 'aa', '60', '1');
INSERT INTO `student` VALUES ('13', 'bb', '99', '1');
INSERT INTO `student` VALUES ('14', 'cc', '62', '1');
INSERT INTO `student` VALUES ('15', 'dd', '88', '2');
INSERT INTO `student` VALUES ('16', 'ee', '100', '2');
查看当前数据库事务隔离级别:show variables like 'tx_isolation';设置当前事务隔离级别:set tx_solation= 'RepeatableRead';
首先展示一下当前的事务隔离级别
首先设置当前两个窗口的事务隔离级别为“read-uncommitted”。并且开启事务
注意:set tx_isolation="事务隔离级别";这个命令只对当前会话有效(即当前窗口);设置全局事务隔离级别:set global transaction isolation level 新的事务隔离级别;
一个事务读取到了另外一个事务未提交数据。
在两个事务(分别命名为A事务和B事务)中查询student数据:
在B事务中更新name="aa"的成绩+5,但不提交,之后在A事务中查询student数据
此时,若B事务因为某种情况回滚,然而A事务已经将B事务尚未提交的数据读取到了而且做了其他操作,那么A事务的数据就是错误的,这就是脏读。
事务读取到了另外一个事务已提交的修改数据。
重新开启两个事务(A和B),我将B事务的数据提交,A事务中查询student表数据中name为aa的记录中score数据是否有变化。
首先开启事务并且查询两个事务起始数据
更新B事务,name为aa的数据里面的score+5,在A事务中查询student数据:
A事务数据中name为aa的这条记录中的score也已经由65变为70,读取到了B事务已经提交的数据,这是不可重复读的现象,不符合隔离性。
事务读取到另外一个事务新增的数据。
重新开启两个事务A和B,
在B事务中添加一条name等于ff的数据之后,commit。在A中再次查询student表数据,同样也新增了一条数据:
事务A读取到了事务B的已提交的新增数据,发生了幻读现象。不符合隔离性。
看到这里的你是不是对脏读、不可重复读、幻读有些疑问呢?我做上述测试过程中也发生了一些疑问:
(1)脏读和不可重复读什么区别?
脏读是一个事务读取到另外一个事务尚未提交的修改或者删除后数据。而不可重复读是一个事务读取到另外一个事务已经提交的修改或者删除后数据。
(2)不可重复读和幻读有什么区别?
不可重复读是一个事务读取到另外一个事务已经提交的修改或者删除后的数据。幻读是一个事务读取到另外一个事务已经提交的新增数据。
接下来验证“读已提交”会不会发生脏读、不可重复读、幻读的问题
首先设置当前会话隔离级别:
set tx_isolation="read-committed";
同样开启两个事务A和B
之后修改B事务数据的name为aa的score+5=65,查看A数据对应的score数据是否发生改变:
A事务没有发生改变,没有发生脏读现象
将上个脏读事务B提交,查看A事务是否读取到了B事务已提交的修改数据:
确实是读取到了B事务已提交的数据信息,发生了不可重复读的问题,不符合隔离性。
重新开启两个事务A和B;
B事务新增数据,查看A事务是否已读取到了新增数据:
显然,A事务读取到了B事务新增数据,发生了幻读现象。
设置当前会话隔离级别:set tx_isolation="repeatable-read";
开启两个事务A和B,修改B事务的name为aa的这条记录中score数据+5,查看A事务中student数据是否发生改变:
数据并未发生改变,没有发生脏读现象。
将上面例子中的B事务提交,查看A事务中的数据是否发生改变:
A事务中的数据没有发生改变,没有产生不可重复读的问题。
重新开启两个事务A和B,在B中插入一条ff的数据,并且提交,这时候查询事务A,并没有新增的数据ff
之后,在A事务中更新ff数据中的score为90,再次查询student表:
在A事务中再次查询student表,发现之前没有的ff数据被查询出来,并且score变为了90。发生了幻读现象,不符合隔离性。
可串行化不会发生上述问题,但是性能会大大下降,因为当开启多个事务时,若修改其中某个表的数据,需要等到其他事务关闭时才能提交。因此大多数公司并不会设置此隔离级别。MySQL的事务隔离级别也是默认可重复读,没有设置串行化。
设置隔离级别:set tx_isolation = "serializable";
开启两个事务A和B,B修改数据后会等待A事务提交之后才能修改成功。写操作需要等待其他事务完成之后才能执行成功,这种就是所谓的串行化。
(1)性能:读未提交>读已提交>可重复读>可串行化
(2)MySQL默认设置的隔离级别为可重复读(Repeatable-Read)
(3)安全性:读未提交<读已提交<可重复读<可串行化
事务隔离级别的学习还有其他方面,比如锁(表锁和行级锁),可重复读中涉及到的MVCC机制等,今天事务隔离级别的学习就到这儿,如果有疑问或者更好的见解欢迎留言!