mysql 隔离级别与间隙锁等
数据库隔离级
SQL标准中DB隔离级别有:
read uncommitted:可以读到其它transaction 未提交数据
read committed:可以读到其它transaction 已提交数据
repeatable read:一个transaction中相同的查询,每次获取的结果是一样的
serialize:所有操作串行
这几种隔离级别为的是解决并发中的如下问题:
脏读
即一个transaction 可以读到另一个的未提交数据。
不可重复读
即一个transaction 中,两次相同的查询会读到不一样的结果。
幻读
一个transaction中,之前不存在的记录,突然存在了。
read uncommitted 和 serialize 基本没有数据库在用。所以我们只关注read committed 和 repeatable read, ORACLE 默认是 rc 级别,mysql默认是rr级别。我们本文的实验都是mysql rr级别下做的。
这两种隔离级别对并发冲突的解决程度如下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
RC | 不存在 | 存在 | 存在 |
RR | 不存在 | 不存在 | 存在 |
**要注意的是,sql标准中定义的RR 是存在幻读的,但实际上mysql 的RR级别不存在幻读 **
RC级别可以看到其它transaction的提交,所以它是不可重复读的,同时也会存在幻读
RR级别不可看到其它transaction的更改,所以它是可重复读的。但SQL标准定义的RR存在幻读。(虽然mysql的实现中做到了RR无幻读)。为什么SQL 标准定义的RR 会有幻读呢? 首先要进一步清晰不可重复读和幻读的区别
幻读和不可重复读
幻读和不可重复读不太好区分。不可重复读针对update/delete等操作和已有的数据, 而幻读针对的是insert类型操作。一次transaction中,之前读取过的数据被其它transaction 更改提交了,并且在本transaction中能够看到。这是不可重复读。一次transaction中,之前没有的数据,再次操作时却有了,是幻读。虽然幻读也是一种不可重复读,但还是要把它们区分开讨论。这是因为,这两种问题的解决方案很不一样。
如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。
所以幻读和不可重复读最大的区别是如何用锁来解决他们产生的问题。
MVCC
用锁解决不可重复读的问题非常简单。只需要对transaction中读取到的数据加行锁。保证读取过程中该行不被修改即可。但这样会造成部分数据的操作变成串行。主流的数据库一般不通过锁的方式来实现可重复读,它们采取的方式叫 MVCC (multi-version concurrent control),我们以mysql 的MVCC 来说明。
mysql 在每一行后面加了几个字段,来标识每一行的状态, 如 row_id 记录行的聚簇索引 key , 创建/更新事务ID, 删除事务ID,回滚ID , 如:
ROW_ID, F1, F2, ... ROW_ID, 创建事务ID, 删除事务ID,回滚ID
INSERT 操作会把创建事务ID 置为自己的transaction ID , 删除事务ID 和回滚ID 为空
UPDATE 操作会:
1. 创建该行的回滚记录
2. 更新创建事务ID 为自己的ID
3. 更新回滚ID 指向回滚记录
DELETE 操作会
1. 创建该行回滚记录
2. 更新删除事务ID为自己的transaction ID
3. 更新回滚ID
SELECT 操作会
查找删除事务ID 为空(没删除)或大于自己事务ID 的记录(自己transaction 开始之后删除的)
and
创建事务ID 小于等于自己的transaction id (确保不是自己transaction后创建的)
通过这种方式,可以发现mysql 其实实现了可重复读。并且不必加锁,因为各事务可以维护和操作不同版本的数据。
也行你认为该方式不仅解决了可重复读,还解决了幻读。 确实,假设有transaction A和B ,并且A早于B 开始,那么B中insert的记录A 是读不到的,从这个角度说是解决了幻读。但实际上并不完全对,mysql中的读其实有两种,当前读和快照读
当前读和快照读
RR模式MYSQL 中的SELECT 操作只读取某一时间点的数据,即transaction开始时刻的数据,尽管后续有transaction 对数据做出了更改,当前transaction 也看不到。这有点类似于 MYSQL 在transaction开始的时刻打了一个快照。所以这种读叫快照读。它可重复但不是实时的。
MYSQL 中还有另外一种读叫当前读(CURRENT READ). 这种读只读取表中当前最新的已提交的数据,可以理解为是一种READ COMMITTED。当前读一般发生在
UPDATE/DELETE/INSERT 以及 SELECT ... FOR UPDATE 和 LOCK ... IN SHARE MODE中。可以通过实验验证一下
transaction A
MariaDB [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test]> select * from t1;
+----+--------+
| id | number |
+----+--------+
| 1 | 11 |
| 2 | 22 |
+----+--------+
2 rows in set (0.00 sec)
transaction B 插入一条数据并commit
MariaDB [test]> select * from t1;
+----+--------+
| id | number |
+----+--------+
| 1 | 11 |
| 2 | 22 |
| 3 | 33 |
+----+--------+
transaction A 先是 select 证明select 是可重复读,快照读
MariaDB [test]> select * from t1;
+----+--------+
| id | number |
+----+--------+
| 1 | 11 |
| 2 | 22 |
+----+--------+
2 rows in set (0.01 sec)
然后 transaction A 再delete
MariaDB [test]> delete from t1 where id=3;
Query OK, 1 row affected (0.00 sec)
可以发现,有趣的是虽然SELECT 不到但delete操作显示成功的删除了该数据。这说明DELETE 可以看到其它TRANSACTION 提交的数据,是RC。
transaction A 提交后再查询,也可以发现 数据确实被删除了
这说明DML操作确实是RC, 为什么这些操作是当前读我们后面再看,但至少现在可以知道,MVCC 的方式虽然能解决快照读的不可重复与幻读问题,但不能解决当前读的。因为当前读是Read committed。那么 Mysql 如何解决当前读的幻读问题呢? 通过间隙锁
间隙锁 和 next-key 锁
用例子说明一下,假设我们有数据表如下:
其中number上有索引
start transaction;
select * from t4;
+--------+
| number |
+--------+
| 5 |
| 10 |
| 15 |
+--------+
select * from t4 where number=10 for update;
+--------+
| number |
+--------+
| 10 |
+--------+
上述语句用当前读,读取number=10 , 这种情况下要避免幻读,即接下来:
- insert 操作不会插入到 number=10 t1
- update操作不能更新 number=10 这行
- delete操作不能删除number = 10这行
mysql 所用的方式很简单,通过row锁锁住 number=10的行,阻止update/delete。 通过间隙锁锁住10 可能出现的位置
因为 number 有索引,通过索引我们知道 number = 10 可能出现的位置有两处 5-10 和 10-15 , 所以mysql 会把这两处锁住, 从其它transaction 去 insert 数据到 5-10 和10 - 15 的位置会被卡住。这就是间隙锁。我们验证一下
MariaDB [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test]> insert into t4 values(18);
Query OK, 1 row affected (0.00 sec)
MariaDB [test]> insert into t4 values(6);
^CCtrl-C -- query killed. Continuing normally.
ERROR 1317 (70100): Query execution was interrupted
MariaDB [test]> insert into t4 values(11);
^CCtrl-C -- query killed. Continuing normally.
ERROR 1317 (70100): Query execution was interrupted
除了 18 可以成功,其它两条被卡住
但要注意的是:mysql 通过间隙锁来锁住目标记录可能出现的位置,如果检索条件有索引,可以通过索引锁住目标位置,如果索引是unique 则不用锁间隙,因为不会出现间隙,如果没有索引会锁住全表
间隙锁加行锁的方式来防止当前读的幻读,在mysql中叫next key锁
为什么 DML/SELECT FOR UPDATE, SELECT FOR SHARE 是当前读
SELECT FOR UPDATE/SHARE 是当前读比较好理解。这两种读的目的就是锁住记录,不让他人更改,所以锁住快照没有意义
那么DML 为什么是当前读呢?考虑以下场景
start transaction;
select * from t4;
+--------+
| number |
+--------+
| 5 |
| 10 |
| 15 |
+--------+
select * from t4 where number=10 for update;
+--------+
| number |
+--------+
| 10 |
+--------+
这时有另外transaction 进行DML 操作,如果insert / update / delete 不是当前读, 那么 SELECT FOR UPDATE的锁仍然毫无意义..
mysql 隔离级别与间隙锁等的更多相关文章
- .NET:“事务、并发、并发问题、事务隔离级别、锁”小议,重点介绍:“事务隔离级别"如何影响 “锁”?
备注 我们知道事务的重要性,我们同样知道系统会出现并发,而且,一直在准求高并发,但是多数新手(包括我自己)经常忽略并发问题(更新丢失.脏读.不可重复读.幻读),如何应对并发问题呢?和线程并发控制一样, ...
- Mysql隔离级别,锁与MVCC
关键词:事务,ACID,隔离级别,MVCC,共享锁,排它锁 阅读本文前请先阅读http://hedengcheng.com/?p=771 http://www.hollischuang.com/arc ...
- MYSQL隔离级别 与 锁
1.四种隔离级别下数据不一致的情况 脏读 不可重复读 幻读 RU 是 是 是 RC(快照读) 否 是 是 RC(当前读) 否 否 是 RR(快照读) 否 否 是 RR(当前读) 否 否 否 Ser ...
- mysql 隔离级别与锁
1.什么是事务 事务是一条或多条数据库操作语句的组合,具备ACID,4个特点. 原子性:要不全部成功,要不全部撤销 隔离性:事务之间相互独立,互不干扰 一致性:数据库正确地改变状态后,数据库的一致性约 ...
- MySQl中隔离级别和悲观锁乐观锁
1.MySql的事物支持 MySQL的事务支持不是绑定在MySQL服务器本身,而是与存储引擎相关: MyISAM:不支持事务,用于只读程序提高性能 InnoDB:支持ACID事务.行级锁.并发 Ber ...
- mysql隔离级别与锁,接口并发响应速度的关系(2)
innoDB默认隔离级别 mysql> SELECT @@tx_isolation; +-----------------+ | @@tx_isolation | +-------------- ...
- mysql隔离级别与锁,接口并发响应速度的关系(1)
默认隔离级别:可重复读 原始数据 | id | name | addr | | nick | NULL | 事务1 事务2 start transaction start transaction ; ...
- mysql隔离级别
MySQL/InnoDB定义的4种隔离级别: Read Uncommited 可以读取未提交记录. Read Committed (RC) 针对当前读,RC隔离级别保证对读取到的记录加锁 (记录锁), ...
- mysql隔离级别相关
1.原子性.隔离性.一致性.持久性 2.mysql并发控制可能出现的问题: 脏读(A事务读取到B事务未commit的数据后,B事务回滚) 不可重复读(A事务第一次读到的数据,被B事务更新数据后,第二次 ...
随机推荐
- OJ 大整数减法
描述 求两个大的正整数相减的差. 输入 共2行,第1行是被减数a,第2行是减数b(a > b).每个大整数不超过200位,不会有多余的前导零. 输出 一行,即所求的差. 样例输入 9999999 ...
- linux运维中常用的指令
一.终端中常用的快捷键 man界面中的快捷键: ?keyword 向上搜索关键词keyword,n向下搜索,N继续向上搜索 /keyword 向下搜索关键词keyw ...
- Vue的响应式规则
对象属性的响应规则 <body> <div id="root"> {{msg}} </div> </body> <script ...
- 如何删除SQL Server 2014连接到服务器中的服务器名称
查看了网上的一些方法,均是以前版本的处理方法,不过原理都是删除"SqlStudio.bin",通过“Everything”搜索SqlStudio.bin,位置是: C:\Users ...
- 内涵段子爬取及re匹配
案例:使用正则表达式的爬虫 现在拥有了正则表达式这把神兵利器,我们就可以进行对爬取到的全部网页源代码进行筛选了. 下面我们一起尝试一下爬取内涵段子网站: http://www.neihan8.com/ ...
- 算法学习记录-图——最小生成树之Kruskal算法
之前的Prim算法是基于顶点查找的算法,而Kruskal则是从边入手. 通俗的讲:就是希望通过 边的权值大小 来寻找最小生成树.(所有的边称为边集合,最小生成树形成的过程中的顶点集合称为W) 选取边集 ...
- 【转载】C语言中的static 详细分析
原blog地址:http://blog.csdn.net/keyeagle/article/details/6708077/ google了近三页的关于C语言中static的内容,发现可用的信息很少, ...
- Android DialogFragment全屏
在onCreate里面写入代码: @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(saved ...
- java面向抽象编程样例
import java.util.*; abstract class Geometry{ public abstract double getArea(); } class Pillar ...
- HDU-4825 Xor Sum,字典树好题!
Xor Sum 一遍A了之后大呼一声好(keng)题!debug了两小时~~~~百度之星资格赛,可以. 题意:给你一个n个元素的数组,m次查询,每次输入一个数k要求从数组中找到一个数与k异或值最大,输 ...