查看MySQL当前的隔离级别
/*方式一*/
select @@tx_isolation;
/*方式二*/
show variables like 'tx_isolation';
设置MySQL的隔离级别
/* read uncommitted/read committed/repeatable read/serializable */
set session transaction isolation level read uncommitted
开启事务 commit
提交事务 start transaction
回滚事务 rollback
设置事务自动提交是否开启
关闭事务自动提交:set autocommit=0;
注:值0和OFF都是一样的,当然,1也就表示ON。
查看是否开启自动提交功能:show variables like 'autocommit';
问题 | 描述 | 要求 |
---|---|---|
脏读(Dirty Reads) | B事务读到了A个事务未提交的数据。 | B事务要读取A事务已经提交的数据。 |
不可重复读(Non-Repeatable Reads) | 一个事务中两次读取的数据不一致。 | 一个事务中多次读取的数据是一致的。 |
虚/幻读(Phantom Reads) | 一个事务中两次读取到的数据的条数不一致。 | 一个事务多次读取的数据的条数是一致的。 |
隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁读 |
---|---|---|---|---|
READ UNCOMMITTED | Yes | Yes | Yes | No |
READ COMMITTED | No | Yes | Yes | No |
REPEATABLE READ | No | No | Yes | No |
SERIALIZABLE | No | No | No | Yes |
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`balance` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES ('1', 'zhangsan', '1000');
INSERT INTO `account` VALUES ('2', 'lisi', '1000');
读未提交,脏读、不可重复读、幻读都会出现。
脏读:一个事务读取到了另一个事务未提交的数据。
脏读演示
(1)打开一个客户端A,并设置当前会话事务隔离级别为read uncommitted(未提交读),开启事务,更新account表id=1的余额:
set session transaction isolation level read uncommitted;
select @@tx_isolation;
update account set balance = balance - 100 where id = 1;
(2)打开一个客户端B,并设置当前会话事务隔离级别为read uncommitted(未提交读),开启事务,查询account表id为1的余额。
(3)客户端A事务回滚
(4)客户端B读取到了脏数据,脏读出现
读已提交,可以解决脏读,但是不可重复读、幻读仍然会出现
不可重复读:一个事务中两次读取到的数据不一致。
不可重复读演示
(1)开启一个会话A,设置事务隔离级别为读已提交,开启事务,查询id=1的余额。
(2)开启一个会话B,设置事务隔离级别为读已提交,开启事务,修改id=1的余额,并提交事务。
如果想测试脏读是否解决,可以在更新余额之后,事务提交之前,在会话A中查询一次余额。
(3)在会话A的事务中,再次查询id=1的余额。不可重复读 问题出现
可重复读,可以解决脏读、不可重复读,幻读仍然会出现
读写sql操作同一数据可以并发执行,但是多个写的sql语句操作同一条数据会阻塞
(1)开启一个会话A,设置事务隔离级别为repeatable read,开启事务,查询id=1的余额。
(2)开启一个会话B,设置事务隔离级别为repeatable read,开启事务,更新id=1的余额,并提交。
(3)在会话A中再次查询id=1的余额。不可重复读问题解决
(4)在会话A中对id=1的账户减去100元,并没有变成900元,而是变成了800元。
可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)
数据准备
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50724
Source Host : localhost:3306
Source Database : tx
Target Server Type : MYSQL
Target Server Version : 50724
File Encoding : 65001
Date: 2019-05-01 16:14:42
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for tbl_tx
-- ----------------------------
DROP TABLE IF EXISTS `tbl_tx`;
CREATE TABLE `tbl_tx` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tbl_tx
-- ----------------------------
INSERT INTO `tbl_tx` VALUES ('1', 'zhangsan1');
INSERT INTO `tbl_tx` VALUES ('2', 'zhangsan2');
(1)开启一个会话A,设置事务隔离级别为repeatable read,开启事务,查询tbl_tx表。
(2)开启一个会话B,设置事务隔离级别为repeatable read,开启事务,插入一条数据,并提交。
(3)在会话A的事务中再次查询仍然是2条记录,但是当更新所有行之后会有3条数据受影响,再查询会发现出现3条数据。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于
修改
,幻读侧重于新增或删除
。解决不可重复读的问题只需锁住满足条件的行
,解决幻读需要锁表
如果使用普通的读,会得到一致性的结果,如果使用了加锁的读,就会读到“最新的”“提交”读的结果。
本身,可重复读和提交读是矛盾的。在同一个事务里,如果保证了可重复读,就会看不到其他事务的提交,违背了提交读;如果保证了提交读,就会导致前后两次读到的结果不一致,违背了可重复读。
可以这么讲,InnoDB提供了这样的机制,在默认的可重复读的隔离级别里,可以使用加锁读去查询最新的数据。
http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html
If you want to see the “freshest” state of the database, you should use either the READ COMMITTED isolation level or a locking read:
SELECT * FROM t_bitfly LOCK IN SHARE MODE;
结论:MySQL InnoDB的可重复读并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是next-key locks。
结论:mysql 的重复读解决了幻读的现象,但是需要 加上 select for update/lock in share mode 变成当前读避免幻读,普通读select存在幻读
《高性能MySQL》
MySQL的四种事务隔离级别
MySQL加锁过程详解(3)