数据库隔离级

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 隔离级别与间隙锁等的更多相关文章

  1. .NET:“事务、并发、并发问题、事务隔离级别、锁”小议,重点介绍:“事务隔离级别"如何影响 “锁”?

    备注 我们知道事务的重要性,我们同样知道系统会出现并发,而且,一直在准求高并发,但是多数新手(包括我自己)经常忽略并发问题(更新丢失.脏读.不可重复读.幻读),如何应对并发问题呢?和线程并发控制一样, ...

  2. Mysql隔离级别,锁与MVCC

    关键词:事务,ACID,隔离级别,MVCC,共享锁,排它锁 阅读本文前请先阅读http://hedengcheng.com/?p=771 http://www.hollischuang.com/arc ...

  3. MYSQL隔离级别 与 锁

    1.四种隔离级别下数据不一致的情况   脏读 不可重复读 幻读 RU 是 是 是 RC(快照读) 否 是 是 RC(当前读) 否 否 是 RR(快照读) 否 否 是 RR(当前读) 否 否 否 Ser ...

  4. mysql 隔离级别与锁

    1.什么是事务 事务是一条或多条数据库操作语句的组合,具备ACID,4个特点. 原子性:要不全部成功,要不全部撤销 隔离性:事务之间相互独立,互不干扰 一致性:数据库正确地改变状态后,数据库的一致性约 ...

  5. MySQl中隔离级别和悲观锁乐观锁

    1.MySql的事物支持 MySQL的事务支持不是绑定在MySQL服务器本身,而是与存储引擎相关: MyISAM:不支持事务,用于只读程序提高性能 InnoDB:支持ACID事务.行级锁.并发 Ber ...

  6. mysql隔离级别与锁,接口并发响应速度的关系(2)

    innoDB默认隔离级别 mysql> SELECT @@tx_isolation; +-----------------+ | @@tx_isolation | +-------------- ...

  7. mysql隔离级别与锁,接口并发响应速度的关系(1)

    默认隔离级别:可重复读 原始数据 | id | name | addr | | nick | NULL | 事务1 事务2 start transaction start transaction ; ...

  8. mysql隔离级别

    MySQL/InnoDB定义的4种隔离级别: Read Uncommited 可以读取未提交记录. Read Committed (RC) 针对当前读,RC隔离级别保证对读取到的记录加锁 (记录锁), ...

  9. mysql隔离级别相关

    1.原子性.隔离性.一致性.持久性 2.mysql并发控制可能出现的问题: 脏读(A事务读取到B事务未commit的数据后,B事务回滚) 不可重复读(A事务第一次读到的数据,被B事务更新数据后,第二次 ...

随机推荐

  1. MariaDB数据库(三)

    1. 基本查询 查询基本使用包括:条件.排序.聚合函数.分组和分页. 实例详解查询 1> 创建students表用作实验 MariaDB [testdb]> drop table stud ...

  2. 如何用纯 CSS 绘制一个充满动感的 Vue logo

    效果预览 在线演示 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/zaqKPx 可交互视频教 ...

  3. Juqyer:$.ajax()方法详解

    Jquery中的ajax方法参数总是记不住,这里记录一下. 最常用的属性是:url.data 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为S ...

  4. Could not connect to Redis at IP No route to host

    这个问题是在用远程去访问redis出现的 原因:是服务器新装系统  iptables这个的问题 解决办法: sudo iptables -F 轻松解决

  5. Verilog学习笔记基本语法篇(一)·········数据类型

    Verilog中共有19种数据类型. 基本的四种类型: reg型.wire型.integer型.parameter型. 其他类型:large型.medium型.small型.scalared型.tim ...

  6. 牛客网暑期ACM多校训练营(第六场) I Team Rocket(线段树)

    题意: 给定n个区间, m次询问, 每次询问给一个点, 问这个点在哪些区间内, 然后删掉这些区间. 分析: 将n个区间按L大小升序排列, 然后将这些区间视为点构建一棵n个点的线段树, 树的节点记录这个 ...

  7. JavaScript正则表达式-非捕获性分组

    非捕获性分组定义子表达式可以作为整体被修饰但是子表达式匹配结果不会被存储. 非捕获性分组通过将子表达式放在"?:"符号后. str = "img1.jpg,img2.jp ...

  8. jsonp实现跨域访问json数据

    前台js function init() { $.ajax({ url: 'http://localhost:8012/index.json', dataType: "jsonp" ...

  9. 五、人生苦短,我用python【第五篇】

    Python基本数据类型 运算符 1.算数运算: 2.比较运算: 3.赋值运算: 4.逻辑运算: 5.成员运算: 基本数据类型 1.数字 int(整型) 在32位机器上,整数的位数为32位,取值范围为 ...

  10. PHP 获取文件名和扩展名的方法

    dirname(path) path: 代表你的文件路径,必须为绝对路径,可以使用__FILE__, 表示列出当前文件的绝对路径,包含文件名 函数会返回当前文件的上一级路径,也就是除了文件名称的路径 ...