这篇笔记主要对mysql事务进行一些学习。
业务里一般当进行转账等操作的时候,会用到mysql事务。
mysql事务有ACID
特性:
- Atomicity,原子性。即事务里的操作要么都成功,要么都失败
- Consistency,一致性。数据库总是从一个一致性的状态转换到另外一个一致性的状态。
- Isolation,隔离性。一个事务所做的修改在最终提交前,对其他事务是不可见的。
- Durability,持久性。一旦事务提交,则其所做的修改就会永久保存到数据库中。
mysql事务有四种隔离级别
:
serializable
可串行化。强制让事务串行执行,serializable会在读取的每一行数据上都加上锁,所以可能导致大量的超时和锁竞争的问题。
当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续访问。- 这个隔离级别隔离度最高,但是性能最低。实际应用中很少使用到这个隔离级别,只有在非常需要确保数据一致性而且可以接受没有大并发的情况下,才考虑采用该级别。
repeatable read
可重复读。一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。- 这个隔离级别有
幻读
问题。幻读
是指,当某个事务读取某范围的记录时,另一个事务又在该范围插入了新的记录,当前事务再次读取该范围的记录时,会产生幻行
。
- 这个隔离级别有
read committed
读已提交。一个事务提交之后,它做的变更才会被其他事务看到。- 这个隔离级别多了个
不可重复读
问题。不可重复读
是指,两次执行同样的查询,可能会得到不一样的结果。
- 这个隔离级别多了个
read uncommitted
读未提交。一个事务还没提交时,它做的变更就能被别的事务看到。- 这个隔离级别又多了
脏读
问题。也就是说,假如当前有两个事务A和B,A修改了某个数据,B读取到了这个修改后的数据,但是A因为某些原因回滚了,那么B读到的数据其实就是脏数据。
- 这个隔离级别又多了
事务在mysql中默认采用的是自动提交模式(AUTOCOMMIT
),也就是说,如果不是显式地开始一个事务,则每个查询都被当做一个事务执行提交操作。
可以使用show variables like '%autocommit%';
来查看你自己的mysql关于自动提交的配置:
ON意味着autocommit=1,OFF意味着autocommit=0。
set autocommit=0
这个命令会关掉自动提交。也就是是说,如果你只执行一个select语句,那么事务就启动了,并且不会自动提交,这个事务会持续存在知道你主动执行commit或rollback或断开连接。
所以在实际生产环境中,建议set autocommit=1
虽然mysql规范里面制定了四种事务的隔离级别,但是真正实现事务的是具体的存储引擎。例如Innodb就支持事务,而MyISAM是不支持事务的。具体可以使用show engines
命令查看
关于这四个隔离级别,我们可以实际操作体验一下。
我的环境是Mysql 5.6.34,使用的是InnoDB引擎
首先创建一张表,表结构如下:
create table testtransaction (
`c1` int UNSIGNED NOT NULL AUTO_INCREMENT,
`c2` varchar(255),
`c3` char(11),
`c4` varchar(255),
PRIMARY KEY (`c1`)
) ENGINE=InnoDB DEFAULT CHARSET=ascii ROW_FORMAT=COMPACT;
读已提交
我们首先要设置隔离级别为读已提交: set session transaction isolation level READ COMMITTED;
在第一个事务里面我们执行如下操作:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into testtransaction values('a', 'b', 'c');
Query OK, 1 row affected (0.03 sec)
mysql> select * from testtransaction;
+----+------+------+------+
| c1 | c2 | c3 | c4 |
+----+------+------+------+
| 1 | a | b | c |
+----+------+------+------+
1 row in set (0.00 sec)
然后在第二个事务里面我们执行如下操作:
mysql> set session transaction isolation level READ COMMITTED;
Query OK, 0 rows affected (0.01 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from testtransaction;
Empty set (0.00 sec)
我们可以看到,在第二个事务里面select *
的时候,并没有读到第一个事务未提交的数据。当第一个事务提交之后,我们在第二个事务里面再次执行select * from testtransaction
的时候就不是空了,而是读到了第一个事务提交的数据。所以这就是不可重复读
问题。
读未提交
我们首先要设置隔离级别为读未提交:set session transaction isolation level READ UNCOMMITTED;
在第一个事务里面,修改某个数据
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update testtransaction set c2='hi' where c1=1;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from testtransaction where c1=1;
+----+------+------+------+
| c1 | c2 | c3 | c4 |
+----+------+------+------+
| 1 | hi | b | c |
+----+------+------+------+
1 row in set (0.00 sec)
在另外一个事务,执行以下操作:可以看到第一个事务还没提交的数据。如果直接拿查到的数据返回给用户或者进行后续的业务操作,但是第一个事务却回滚了,那么相当于拿到的就是脏数据。这就是脏读
问题。
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from testtransaction where c1=1;
+----+------+------+------+
| c1 | c2 | c3 | c4 |
+----+------+------+------+
| 1 | hi | b | c |
+----+------+------+------+
1 row in set (0.00 sec)
可串行化
我们首先要设置隔离级别为可串行化:set session transaction isolation level SERIALIZABLE;
在第一个事务里面查询c1=1的记录
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from testtransaction where c1=1;
+----+-------+------+------+
| c1 | c2 | c3 | c4 |
+----+-------+------+------+
| 1 | hi | b | c |
+----+-------+------+------+
1 row in set (0.00 sec)
在另外一个客户端里面对c1=1的记录进行修改。它会进入等待状态,如果超时会返回ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update testtransaction set c2='hello' where c1=1;
可重复读
我们首先要设置隔离级别为可重复读:set session transaction isolation level REPEATABLE READ;
在第一个事务里面,我们查询c1在(4,8)之间的记录
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from testtransaction where c1 > 4 and c1 < 8;
+----+------+------+------+
| c1 | c2 | c3 | c4 |
+----+------+------+------+
| 6 | a | s | d |
| 7 | h | j | k |
+----+------+------+------+
2 rows in set (0.00 sec)
在第二个事务里面,我们插入一条记录
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into testtransaction values(5, '5', '5', '5');
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
然后再次在第一个事务里面查询c1在(4,8)之间的记录:
mysql> select * from testtransaction where c1 > 4 and c1 < 8;
+----+------+------+------+
| c1 | c2 | c3 | c4 |
+----+------+------+------+
| 6 | a | s | d |
| 7 | h | j | k |
+----+------+------+------+
2 rows in set (0.00 sec)
MySQL的默认的隔离级别是可重复读
,但是按照文章开头的理论可重复读
是会存在幻读
问题的,照理说这里会查出来3条记录(c1=5,6,7),但我们实际只查出来2条记录,也就是说在第二个事务里面插入的数据并没有对第一个事务有影响。因为我们使用的是Innodb存储引擎,Innodb实现了间隙锁
,可以防止幻读
问题的发生。
在使用事务的时候,如果混用了存储引擎,那么要注意不同的存储引擎是否支持事务。就如同上面所说的,InnoDB是支持事务的,但是MyISAM是不支持事务的。如果是混用这两种存储引擎的表,并且因为某些原因事务回滚了,那么MyISAM里的表的变更就无法撤销了。