1. 隔离级别概述
隔离级别用于限定事务内外的哪些改变是可见的,哪些是不可见的。
MySQL实现了标准定义的4类隔离级别。
隔离级别从低到高依次是:Read Uncommitted, Read Committed, Repeatable Read, Serializable
低级别的隔离一般支持更高的并发,并且系统开销更低。
MySQL的默认事务隔离级别是 Repeatable Read。
文中针对每一种隔离级别包含实战部份,涉及的表结构和数据如下:
create table product(
id bigint not null auto_increment,
name varchar(100) not null,
stock int not null default 0,
primary key (id),
index product_name(name)
);
insert into product(id,name,stock) values(1,'Java并发编程的艺术',100);
需要用到的一些命令的说明
# 查看当前会话事务隔离级别
select @@tx_isolation;
# 查看全局的事务隔离级别
select @@global.tx_isolation;
# 设置事务全局隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
# 设置事务会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABL;
2. Read Uncommitted (读取未提交内容)
概念:所有事务都可以看到其它未提交事务的执行结果。
问题:脏读
实战:
step1: 会话1
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select stock from product where id = 1;
+-------+
| stock |
+-------+
| 100 |
+-------+
1 row in set (0.00 sec)
mysql>
setp2: 会话2
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select stock from product where id = 1;
+-------+
| stock |
+-------+
| 100 |
+-------+
1 row in set (0.00 sec)
mysql> update product set stock=99 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
setp3: 回到会话1
mysql> select stock from product where id = 1;
+-------+
| stock |
+-------+
| 99 |
+-------+
1 row in set (0.00 sec)
可以看到, 事务隔离级别为 READ UNCOMMITTED
时,会话1
读取到了会话2
未提交的库存数据修改。
3. Read Committed (读取已提交内容)
概念:一个事务只能看到已经提交事务所做的改变。
问题:一个事务中,同一个select可能返回不同的结果。(其它事务提交,可能修改了数据)
实战:
step1: 会话1
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set, 1 warning (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select stock from product where id = 1;
+-------+
| stock |
+-------+
| 100 |
+-------+
1 row in set (0.00 sec)
mysql>
setp2: 会话2
mysql> set autocommit = 1;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set, 1 warning (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update product set stock=99 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql>
step3: 会话1
mysql> select stock from product where id = 1;
+-------+
| stock |
+-------+
| 100 |
+-------+
1 row in set (0.00 sec)
step4: 会话2
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
step5: 会话1
mysql> select stock from product where id = 1;
+-------+
| stock |
+-------+
| 99 |
+-------+
1 row in set (0.00 sec)
可以看到,事务隔离级别为 READ COMMITTED
时,会话1
只有在会话2
提交后,才能读到会话2
对库存数据的修改。
4. Repeatable Read (可重读)
概念:MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行.
问题:幻读 (指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。)
InnoDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。(见本文后面章节)
实战:
step1: 会话1
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select stock from product where id = 1;
+-------+
| stock |
+-------+
| 100 |
+-------+
1 row in set (0.00 sec)
step2: 会话2
mysql> set autocommit =0;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update product set stock=99 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
step3: 会话1
mysql> select stock from product where id = 1;
+-------+
| stock |
+-------+
| 100 |
+-------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select stock from product where id = 1;
+-------+
| stock |
+-------+
| 99 |
+-------+
1 row in set (0.00 sec)
可以看到,事务隔离级别为 REPEATABLE READ
时,会话1
在执行过程中,反复的读取的库存数据都是一致的,即使这个过程中,会话2
提交了库存数据修改事务。
5. Serializable (可串行化)
概念:强制事务排序,使之不可能相互冲突,从而解决幻读问题。
问题:在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
实战:
setp1: 会话1
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select stock from product where id = 1;
+-------+
| stock |
+-------+
| 99 |
+-------+
1 row in set (0.00 sec)
step2: 会话2
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update product set stock = 98 where id =1;
上面更新语句被卡住,在等待锁
setp3: 会话1
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
step4: 会话2
Query OK, 1 row affected (5.46 sec)
Rows matched: 1 Changed: 1 Warnings: 0
可以看到,事务隔离级别为 SERIALIZABLE
时,会话1
,会话2
事务串行执行。
6. MVCC
多版本并发控制 (Multiversion Concurrency Control)
MVCC
是行级锁的一个变种,它在很多情况下避免了加锁操作,所以开销更低。
MVCC
只在 REPEATABLE READ
和 READ COMMITTED
两个隔离级别下工作,其它两个隔离级别与MVCC
不兼容。
MVCC
通过保存数据在某个时间点的快照来实现的,不管事务要执行多长时间,每个事务看到的数据都是一致的。
实现机制:
在每行记录后面添加两个隐藏的列:创建时版本号,删除时版本号
每开始一个新的事务,系统版本号都会自动递增,将自增后的系统版本号作为该事务的事务版本号。
SELECT
根据下面2个条件检查每行记录:
a. 只查找早于当前事务版本号的数据行(即行的创建时版本号
小于或等于当前事务的版本号)。这样可以确保事务事务读取的行,要么是在事务开始前已经存在,要么是事务自身插入或者修改过的
b. 行的删除时版本号要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除。
INSERT
新插入的每一行保存当前事务版本号作为行版本号(创建时版本号字段)。
DELETE
为删除的每一行保存当前事务版本号做为删除标识(删除时版本号字段)
UPDATE
插入一行新记录,保存当前事务版本号作为行版本号(创建时版本号字段),同时保存当前事务版本号到原来的行,作为行删除标识。(删除时版本号字段)
保存这两个额外系统版本号,使大多数读操作都可以不用加锁。这样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。