SQL Isolation
数据库隔离性
概念
事务隔离级别是数据库事务处理的基础,ACID 中 I,即 Isolation,指的就是事务的隔离性。
隔离性是指,多个用户的并发事务访问同一个数据库时,一个用户的事务不应该被其他用户的事务干扰,多个并发事务之间要相互隔离。
sql 92 标准
sql 92 标准定义了 4 种隔离级别,读未提交、读已提交、可重复读、串行化,见下表。
- ANSI SQL 隔离级级别
Isolation Level | Dirty Read | Nonrepeatable Read | Phantom Read | Serialization Anomaly |
---|---|---|---|---|
Read Uncommitted | Possible | Possible | Possible | Possible |
Read Committed | Not possible | Possible | Possible | Possible |
Repeatable Read | Not possible | Not possible | Possible | Possible |
Serializable | Not possible | Not possible | Not possible | Not possible |
未提交读(Read Uncommitted)
允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
提交读(Read Committed)
只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
可重复读(Repeated Read)
可重复读。在同一个事务内的查询都是事务开始时刻一致的,Mysql/InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻读。
串行读(Serializable)
完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
例子解释
mysql 为例子。
mysql 相关
查看
- 在 MySQL 数据库中查看当前事务的隔离级别:
select @@tx_isolation;
设置
- 在 MySQL 数据库中设置事务的隔离级别:
set [glogal | session] transaction isolation level 隔离级别名称;
set tx_isolation=’隔离级别名称;’
脏读
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
不可重复读
是指在一个事务内,多次读同一数据。
在这个事务还没有结束时,另外一个事务也访问该同一数据。
那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。
这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
可重复读
session 1:
mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
row in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from ttd;
+------+
| id |
+------+
| 1 |
| 2 |
+------+
rows in set (0.00 sec)
session 2 :
mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
row in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into ttd values(3);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.03 sec)
session 2 提交后,查看session 1 的结果;
session 1:
mysql> select * from ttd;
+------+
| id |
+------+
| 1 | --------和第一次的结果一样,REPEATABLE-READ级别出现了重复读
| 2 |
+------+
rows in set (0.00 sec)
(commit session 1 之后 再 select * from ttd
可以看到session 2 插入的数据3)
幻读
第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。
同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。
那么,以后就会发生操作第一个事务的用户无法发现事务2中提交的事务,就好象发生了幻觉一样。
- 表
mysql>CREATE TABLE `t_bitfly` (
`id` bigint(20) NOT NULL default '0',
`value` varchar(32) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
mysql> select @@global.tx_isolation, @@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation |
+-----------------------+-----------------+
| REPEATABLE-READ | REPEATABLE-READ |
+-----------------------+-----------------+
实验一
t Session A Session B
|
| START TRANSACTION; START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| empty set
| INSERT INTO t_bitfly
| VALUES (1, 'a');
|
| SELECT * FROM t_bitfly;
| empty set
| COMMIT;
|
| SELECT * FROM t_bitfly;
| empty set
|
| INSERT INTO t_bitfly VALUES (1, 'a');
| ERROR 1062 (23000):
| Duplicate entry '1' for key 1
刚刚明明告诉我没有这条记录的,可是却。。。
如此就出现了幻读,以为表里没有数据,其实数据已经存在了,傻乎乎的提交后,才发现数据冲突了。
实验二
t Session A Session B
|
| START TRANSACTION; START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id | value |
| +------+-------+
| | 1 | a |
| +------+-------+
| INSERT INTO t_bitfly
| VALUES (2, 'b');
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id | value |
| +------+-------+
| | 1 | a |
| +------+-------+
| COMMIT;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id | value |
| +------+-------+
| | 1 | a |
| +------+-------+
|
| UPDATE t_bitfly SET value='z';
| Rows matched: 2 Changed: 2 Warnings: 0
| (怎么多出来一行)
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id | value |
| +------+-------+
| | 1 | z |
| | 2 | z |
| +------+-------+
实验三
再看一个实验,要注意,表 t_bitfly 里的 id 为主键字段。
t Session A Session B
|
| START TRANSACTION; START TRANSACTION;
|
| SELECT * FROM t_bitfly
| WHERE id [consistent read](https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html)
mysql官方文档提到串行隔离级别要在原来的基础说对每一个select操作执行 [SELECT ... LOCK IN SHARE MODE](https://dev.mysql.com/doc/refman/5.7/en/select.html)
这样就可以读取的数据加读锁了,那么其他试图写入数据都必须阻塞。那么就可以实现序列化串行了。
# 参考文档
- sql
[sql-1992](https://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt)
- 隔离级别
https://github.com/pingcap/docs-cn/blob/master/sql/transaction-isolation.md
http://www.cnblogs.com/zhoujinyi/p/3437475.html
https://www.jianshu.com/p/06f3542240b4
https://zh.wikipedia.org/wiki/%E4%BA%8B%E5%8B%99%E9%9A%94%E9%9B%A2
- mysql InnoDB LOCK
https://mp.weixin.qq.com/s/x_7E2R2i27Ci5O7kLQF0UA
http://www.cnblogs.com/zhoujinyi/p/3435982.html
http://hedengcheng.com/?p=771
https://www.jianshu.com/p/5734c143c0bc
- MVCC
http://blog.51cto.com/donghui/692586
https://draveness.me/database-concurrency-control
https://segmentfault.com/a/1190000012650596
- oracle
https://www.2cto.com/database/201306/223404.html