一直没有搞清楚数据库事务隔离级别到底是怎么回事,看了《高性能MySQL》后还是一脸懵逼,自己动手实验后才算粗浅的解了。

事务

‘事务’是具有‘原子性’操作的一组SQL语句,可以看做是一个工作单元。要么里面的SQL语句全部执行,要么里面的SQL语句全部不执行。

我们举个“银行转账”的经典例子: 账户A_account转账到账户B_account 1000CNY,那么转账系统需要至少三步操作:

  1. 检查A_account 的余额是否大于1000CNY
  2. 从A_account 的账户余额中减去 1000CNY
  3. 在 B_account 的账户中加上 1000CNY

将3步打包在一个事物里面,当3步骤中的任何一步出现了错误,就应该回滚所有已完成的操作。我们可以上述3步转换成SQL语句,首先我们,用start transation开启一个事物:

1
2
3
4
start transaction;
select balance from A_account where customer_id = 16058521;
update A_account set balance = balance - 1000 where customer_id = 16058521;
update B_account set balance = balance + 1000 where customer_id = 16058522;

事务的ACID

  • A(Atomicity)原子性
  • C(Consistency)一致性
  • I(Isolation)隔离性
  • D(Durability)持久性

原子性很好理解,要么执行,要么不执行。持久性也好理解,事物一旦提交,那么数据的改变是永久的。而一致性是啥意思呢?下面这句话是原话,各位客官自己理解吧= =

一种一致性状态转移到另一种一致性转态。
隔离性也就是今天的正题了,不同的隔离级别隔离强度不同,咱们下面细讲。

隔离级别

SQL标准定义了4种隔离级别,用来限定事务内外的哪些变化是可见的,哪些是不可见的。为了下面四个隔离级别实现的正常进行,我们先来看看怎么设置这四个级别:

  • 查看隔离级别
1
2
select @@tx_isolation;	// 当前会话隔离级别
select @@global.tx_isolation; // 全局隔离级别

MySQL的InnoDB引擎默认隔离级别是REPEATABLE READ

1
2
3
4
5
6
7
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
  • 设置隔离级别
1
2
set session transaction isolation level READ UNCOMMITTED;	// 设置当前会话隔离级别为 未提交读
set global transaction isolation level READ UNCOMMITTED; // 设置全局隔离级别为 未提交读
  • 取消自动提交事务
1
2
SHOW VARIABLES LIKE 'AUTOCOMMIT';
set autocommit=0; // 取消自动提交事务
  • 创建实验表,最后实验表结果如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mysql> desc customer;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(20) | NO | MUL | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> select * from customer;
+----+-------------+
| id | name |
+----+-------------+
| 15 | Dasx |
| 19 | GapLockTest |
| 5 | Heikki |
| 20 | John |
| 21 | John |
+----+-------------+
5 rows in set (0.00 sec)

我们会使用两个session链接模拟两个用户多数据库操作,分别为session A 和 session B。

READ UNCOMMITTED(未提交读)

首先我们在两个会话的isolation level均设置为READ UNCOMMITTED,或者你也可以在全局设置。
A:

1
2
3
4
5
6
7
8
9
10
11
12
13
mysql> start transaction;

mysql> select * from customer;
+----+-------------+
| id | name |
+----+-------------+
| 15 | Dasx |
| 19 | GapLockTest |
| 5 | Heikki |
| 20 | John |
| 21 | John |
+----+-------------+
5 rows in set (0.00 sec)

然后再B:

1
2
3
mysql> start transaction;

mysql> update customer set name='Johnson' where id=21;

此时,B的事务尚未结束(使用commit提交事务),我们在A中再次查询:

1
2
3
4
5
6
7
8
9
10
mysql> select * from customer;
+----+-------------+
| id | name |
+----+-------------+
| 15 | Dasx |
| 19 | GapLockTest |
| 5 | Heikki |
| 20 | John |
| 21 | Johnson |
+----+-------------+

我们发现,在A中,已经能读取在B中未提交事务的变化。它会有个问题!当B的事务rollback了(John的名字没有修改),那么A读取的数据就是脏数据,也就是出现脏读(Dirty Read)

READ COMMITTED(已提交读)

在此级别,上述脏读问题就没有了,因为B只能读取到A的事务提交了之后数据。但是还是有个问题= =

首先我们将isolation level设置为READ COMMITTED
set session transaction isolation level READ COMMITTED;

A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from customer;
+----+-------------+
| id | name |
+----+-------------+
| 15 | Dasx |
| 19 | GapLockTest |
| 5 | Heikki |
| 20 | John |
| 21 | Johnson |
+----+-------------+
5 rows in set (0.00 sec)

B:

1
2
3
mysql> start transaction;update customer set name='Dashi' where id=15;

mysql> commit;

此时B的事务已经提交,所以当A再去查询时,就会出现与第一次查询不同的结果(在我们看来感觉是合理的= =,B事务都提交之后A再查询当然会看到已经被B修改过的数据 = =),这就是不可重复读
A:

1
2
3
4
5
6
7
8
9
10
mysql> select * from customer;	// Dasx变成了Dashi
+----+-------------+
| id | name |
+----+-------------+
| 15 | Dashi |
| 19 | GapLockTest |
| 5 | Heikki |
| 20 | John |
| 21 | Johnson |
+----+-------------+

这貌似很合乎逻辑啊。但它也会有问题,因为在A两次查询出现的结果是不同的。

REPEATABLE READ(可重复读)

此级别,也是MySQL默认的事务隔离级别,上述不可重复读问题就没有了,但理论上还是会有幻读(Phantom Read)的可能性。什么是幻读(Phantom Read),即:
PhantomRead.png

但是innodb的RR级别,使用GAP锁是解决了幻读的问题的(这个问题超出范围,下次再解释)。

SERIALIZATION(可串行化)

SERIALIZATION是最高的隔离级别,它通过强制事务排序,解决幻读问题。它会在每个读的数据上加排斥锁,让其他事务等待,但是这样做势必对性能造成影响。

同样,我们将A会话的事务隔离级别设置为serializable并开启事务。
A:

1
2
3
set session transaction isolation level serializable;
start transaction;
select * from customer;

B:

1
2
3
4
mysql> begin;insert into customer(id,name) values(25,'Wangxb');
Query OK, 0 rows affected (0.00 sec)

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

很明显,B事务出现了超时,只有当A在timeout时间内提交事务,B才会成功。

Comments

⬆︎TOP