可串行化(SERIALIZABLE)
将事务进行串行化,也就是在一个队列中按照顺序执行,可串行化是最高级别的隔离等级,可以解决事务读取中所有可能出现的异常情况,但是它牺牲了系统的并发性。
查看MySQL的事务隔离级别
mysql> show variables like '%tx_isolation%';
mysql8 之后
之前是tx_isolation,MySQL8改成了transaction_isolation
mysql> select @@global.transaction_isolation,@@transaction_isolation; +--------------------------------+-------------------------+ | @@global.transaction_isolation | @@transaction_isolation | +--------------------------------+-------------------------+ | REPEATABLE-READ | REPEATABLE-READ | +--------------------------------+-------------------------+ 1 row in set (0.00 sec)
mysql的默认隔离级别是可重复读,但其实对高并发业务来说,可重复读并不是最合适的,最合适的是读提交,主要是因为MySQL 5.0之前,MySQL的主从复制在度提交这个隔离级别下是有bug的。
修改MySQL隔离级别命令:
修改全局隔离级别为读提交:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
修改会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
注:以上命令可大小写混用
这里要吐槽一下MySQL的命令,感觉比较混乱。比如查询出来的隔离级别是两个单词用短横线连接(READ-COMMITTED),但是设置的时候又是要把两个单词用空格分隔。风格很不统一。
MySQL客户端模拟异常
heros_temp表
开两个MySQL客户端1和2(后文简称为 C1,C2)分别都执行如下操作:
查看下当前会话隔离级别,把隔离级别降到最低
mysql> SHOW VARIABLES LIKE 'transaction_isolation'; +-----------------------+-----------------+ | Variable_name | Value | +-----------------------+-----------------+ | transaction_isolation | REPEATABLE-READ | +-----------------------+-----------------+ 1 row in set (0.01 sec) mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; Query OK, 0 rows affected (0.00 sec) mysql> SHOW VARIABLES LIKE 'transaction_isolation'; +-----------------------+------------------+ | Variable_name | Value | +-----------------------+------------------+ | transaction_isolation | READ-UNCOMMITTED | +-----------------------+------------------+ 1 row in set (0.01 sec)
MySQL默认自动提交事务,我们将autocommit参数设为0
mysql> SHOW VARIABLES LIKE 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.01 sec) mysql> SET autocommit = 0; Query OK, 0 rows affected (0.00 sec) mysql> SHOW VARIABLES LIKE 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | OFF | +---------------+-------+ 1 row in set (0.00 sec)
模拟“脏读”
session1写一个新数据,注意不要提交
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into t1 value(12,12); Query OK, 1 row affected (0.00 sec)
session2中,查看当前的数据
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from t1; +----+------+ | id | c | +----+------+ | 1 | 3 | | 2 | 2 | | 3 | 3 | | 4 | 4 | | 10 | 10 | | 6 | 6 | | 7 | 7 | | 8 | 8 | | 9 | 9 | | 0 | 0 | | 5 | 5 | | 11 | 11 | | 12 | 12 | +----+------+ 13 rows in set (0.00 sec)
发现S2中读取了S1未提交的数据“12”,实际上S1可能马上回滚,从而造成“脏读”。
模拟“不可重复读”
session1查看id=1的数据
mysql> select * from t1 where id=1; +----+------+ | id | c | +----+------+ | 1 | 1 | +----+------+ 1 row in set (0.00 sec)
session2对id=1的英雄姓名进行修改
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> update t1 set c=3 where id=1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
这时用客户端1再次进行查询
mysql> select * from t1 where id=1; +----+------+ | id | c | +----+------+ | 1 | 3 | +----+------+ 1 row in set (0.00 sec)
ssession1的同一条查询语句出现“不可重复读”。
模拟“幻读”
session1查询数据表中的所有数据:
mysql> select * from t1; +----+------+ | id | c | +----+------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | | 4 | 4 | | 10 | 10 | | 6 | 6 | | 7 | 7 | | 8 | 8 | | 9 | 9 | | 0 | 0 | +----+------+ 10 rows in set (0.00 sec)
session2,开始插入新的英雄“吕布”:
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into t1 value(5,5); Query OK, 1 row affected (0.01 sec) mysql> select * from t1; +----+------+ | id | c | +----+------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | | 4 | 4 | | 10 | 10 | | 6 | 6 | | 7 | 7 | | 8 | 8 | | 9 | 9 | | 0 | 0 | | 5 | 5 | +----+------+ 11 rows in set (0.00 sec)
再用session1重新查看:
mysql> select * from t1; +----+------+ | id | c | +----+------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | | 4 | 4 | | 10 | 10 | | 6 | 6 | | 7 | 7 | | 8 | 8 | | 9 | 9 | | 0 | 0 | | 5 | 5 | +----+------+ 11 rows in set (0.00 sec)
发现数据表多出一条数据。
总结
标准的价值在于,即使是不同的RDBMS都需要达成对异常问题和隔离级别定义的共识。这就意味着一个隔离级别的实现满足了下面的两个条件:
正确性
只要能满足某一个隔离级别,一定能解决这个隔离级别对应的异常问题。
与实现无关
实际上RDBMS种类很多,这就意味着有多少种RDBMS,就有多少种锁的实现方式,因此它们实现隔离级别的原理可能不同,然而一个好的标准不应该限制其实现的方式。
隔离级别越低,意味着系统吞吐量(并发程度)越大,但同时也意味着出现异常问题的可能性会更大。在实际使用过程中我们往往需要在性能和正确性上进行权衡和取舍,没有完美的解决方案,只有适合的。
