推荐阅读:

事前准备

为了故事的顺利发展,我们先建一个表,并向表中插入一些记录,下边是SQL语句:

CREATE TABLE hero (

number INT,

name VARCHAR(100),

country varchar(100),

PRIMARY KEY (number),

KEY idx_name (name)

) Engine=InnoDB CHARSET=utf8;

INSERT INTO hero VALUES

(1, 'l刘备', '蜀'),

(3, 'z诸葛亮', '蜀'),

(8, 'c曹操', '魏'),

(15, 'x荀彧', '魏'),

(20, 's孙权', '吴');

现在hero表中的记录情况就如下所示:

mysql> SELECT * FROM hero;

+--------+------------+---------+

| number | name | country |

+--------+------------+---------+

| 1 | l刘备 | 蜀 |

| 3 | z诸葛亮 | 蜀 |

| 8 | c曹操 | 魏 |

| 15 | x荀彧 | 魏 |

| 20 | s孙权 | 吴 |

+--------+------------+---------+

5 rows in set (0.01 sec)

现象

在小册答疑群里有一位同学提了一个问题:说是在READ COMMITTED隔离级别下发生了一件百思不得其解的事儿。好的,首先构造环境,将当前会话默认的隔离级别设置成READ COMMITTED:

mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

Query OK, 0 rows affected (0.00 sec)

事务T1先执行:

# T1中,隔离级别为READ COMMITTED

mysql> BEGIN;

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM hero WHERE country = '魏' FOR UPDATE;

+--------+---------+---------+

| number | name | country |

+--------+---------+---------+

| 8 | c曹操 | 魏 |

| 15 | x荀彧 | 魏 |

+--------+---------+---------+

2 rows in set (0.01 sec)

country列并不是索引列,所以本条语句执行时肯定是使用扫描聚簇索引的全表扫描方式来执行,EXPLAIN语句也证明了我们的想法:

mysql> EXPLAIN SELECT * FROM hero WHERE country = '魏' FOR UPDATE;

+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |

+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

| 1 | SIMPLE | hero | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 20.00 | Using where |

+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

1 row in set, 1 warning (0.02 sec)

我们之前学过MySQL语句的加锁分析,知道在READ COMMITTED隔离级别下,如果采用全表扫描的方式执行查询语句时,InnoDB存储引擎将依次对每条记录加正经记录锁,在server层测试该记录是否符合WHERE条件,如果不符合则将加在该记录上的锁释放掉。本例中使用FOR UPDATE语句,肯定加的是X型正经记录锁。只有两条记录符合WHERE条件,所以最终其实只对这两条符合条件的记录加了X型正经记录锁(就是number列值为8和15的两条记录)。当然,我们可以使用SHOW ENGINE INNODB STATUS命令证明我们的分析:

mysql> SHOW ENGINE INNODB STATUS\G

... 省略了很多内容

------------

TRANSACTIONS

------------

Trx id counter 39764

Purge done for trx's n:o < 39763 undo n:o < 0 state: running but idle

History list length 36

Total number of lock structs in row lock hash table 1

LIST OF TRANSACTIONS FOR EACH SESSION:

---TRANSACTION 281479653009568, not started

0 lock struct(s), heap size 1160, 0 row lock(s)

---TRANSACTION 281479653012832, not started

0 lock struct(s), heap size 1160, 0 row lock(s)

---TRANSACTION 39763, ACTIVE 468 sec

2 lock struct(s), heap size 1160, 2 row lock(s)

MySQL thread id 19, OS thread handle 123145470611456, query id 586 localhost 127.0.0.1 root

TABLE LOCK table `xiaohaizi`.`hero` trx id 39763 lock mode IX

RECORD LOCKS space id 287 page no 3 n bits 72 index PRIMARY of table `xiaohaizi`.`hero` trx id 39763 lock_mode X locks rec but not gap

Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0

0: len 4; hex 80000008; asc ;;

1: len 6; hex 000000009b4a; asc J;;

2: len 7; hex 80000001d3012a; asc *;;

3: len 7; hex 63e69bb9e6938d; asc c ;;

4: len 3; hex e9ad8f; asc ;;

Record lock, heap no 5 PHYSICAL RECORD: n_fields 5; compact format; info bits 0

0: len 4; hex 8000000f; asc ;;

1: len 6; hex 000000009b4a; asc J;;

2: len 7; hex 80000001d30137; asc 7;;

3: len 7; hex 78e88d80e5bda7; asc x ;;

4: len 3; hex e9ad8f; asc ;;

... 省略了很多内容

其中id为39763的事务就是指T1,可以看出它为heap no值为4和5的两条记录加了X型正经记录锁(lock_mode X locks rec but not gap)。

然后再开启一个隔离级别也为READ COMMITTED的事务T2,在其中执行:

# T2中,隔离级别为READ COMMITTED

mysql> BEGIN;

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM hero WHERE country = '吴' FOR UPDATE;

(进入阻塞状态)

很显然,这条语句也会采用全表扫描的方式来执行,会依次去获取每一条聚簇索引记录的锁。不过因为number值为8的记录已经被T1加了X型正经记录锁,T2想得却得不到,只能眼巴巴的进行阻塞状态,此时的SHOW ENGINE INNODB STATUS也能证明我们的猜想(只截取了一部分):

---TRANSACTION 39764, ACTIVE 34 sec fetching rows

mysql tables in use 1, locked 1

LOCK WAIT 3 lock struct(s), heap size 1160, 1 row lock(s)

MySQL thread id 20, OS thread handle 123145471168512, query id 590 localhost 127.0.0.1 root Sending data

SELECT * FROM hero WHERE country = '吴' FOR UPDATE

------- TRX HAS BEEN WAITING 34 SEC FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 287 page no 3 n bits 72 index PRIMARY of table `xiaohaizi`.`hero` trx id 39764 lock_mode X locks rec but not gap waiting

Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0

0: len 4; hex 80000008; asc ;;

1: len 6; hex 000000009b4a; asc J;;

2: len 7; hex 80000001d3012a; asc *;;

3: len 7; hex 63e69bb9e6938d; asc c ;;

4: len 3; hex e9ad8f; asc ;;

可以看到T2正在等待获取heap no为4的记录上的X型正经记录锁(lock_mode X locks rec but not gap waiting)。

以上是很正常的阻塞逻辑,我们都可以分析出来,不过如果在T2中执行下边的UPDATE语句:

# T2中,隔离级别为READ COMMITTED

mysql> BEGIN;

Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE hero SET name = 'xxx' WHERE country = '吴';

Query OK, 1 row affected (0.02 sec)

Rows matched: 1 Changed: 1 Warnings: 0

WTF? 竟然没有阻塞,就这么随意地执行成功了?同样的WHERE条件,同样的执行计划,怎么SELECT ... FOR UPDATE和UPDATE语句的加锁情况不一样?

原因

哈哈,是的,的确不一样。其实MySQL支持3种类型的读语句:

普通读(也称一致性读,英文名:Consistent Read)。

这个就是指普通的SELECT语句,在末尾不加FOR UPDATE或者LOCK IN SHARE MODE的SELECT语句。普通读的执行方式是生成ReadView直接利用MVCC机制来进行读取,并不会对记录进行加锁。

小贴士:

对于SERIALIZABLE隔离级别来说,如果autocommit系统变量被设置为OFF,那普通读的语句会转变为锁定读,和在普通的SELECT语句后边加LOCK IN SHARE MODE达成的效果一样。

锁定读(英文名:Locking Read)。

这个就是事务在读取记录之前,需要先获取该记录对应的锁。当然,获取什么类型的锁取决于当前事务的隔离级别、语句的执行计划、查询条件等因素,具体可参见:

半一致性读(英文名:Semi-Consistent Read)。

这是一种夹在普通读和锁定读之间的一种读取方式。它只在READ COMMITTED隔离级别下(或者在开启了innodb_locks_unsafe_for_binlog系统变量的情况下)使用UPDATE语句时才会使用。具体的含义就是当UPDATE语句读取已经被其他事务加了锁的记录时,InnoDB会将该记录的最新提交的版本读出来,然后判断该版本是否与UPDATE语句中的WHERE条件相匹配,如果不匹配则不对该记录加锁,从而跳到下一条记录;如果匹配则再次读取该记录并对其进行加锁。这样子处理只是为了让UPDATE语句尽量少被别的语句阻塞。

小贴士:

半一致性读只适用于对聚簇索引记录加锁的情况,并不适用于对二级索引记录加锁的情况。

很显然,我们上边所唠叨的例子中是因为事务T2执行UPDATE语句时使用了半一致性读,判断number列值为8和15这两条记录的最新提交版本的country列值均不为UPDATE语句中WHERE条件中的'吴',所以直接就跳过它们,不对它们加锁了。

本知识点容易被忽略,各位同学在工作过程中分析的时候别忘记考虑一下Semi-Consistent Read喔

mysql replication 读锁_MySQL介于普通读和加锁读之间的读取方式:semi-consi相关推荐

  1. mysql 终端模拟_mysql客户端模拟脏读、幻读和可重复读

    如果操作一下时报出错误:在数据库中执行 SET GLOBAL BINLOG_FORMAT = mixed; 执行后可通过SELECT * FROM information_schema.GLOBAL_ ...

  2. mysql replication 原理_MySQL Replication(复制)基本原理 | 学步园

    1.复制进程 Mysql的复制(replication)是一个异步的复制,从一个Mysql instace(称之为Master)复制到另一个Mysql instance(称之Slave).实现整个复制 ...

  3. mysql replication 配置_MySQL Replication配置主从的教程

    MySQL主从原理 主(master)在执行sql之后,记录二进制log文件(bin-log) 从(slave)连接主(master),并从主(master)获取binlog,存于本地relay-lo ...

  4. mysql replication 监控_MySQL之-Replication监控及自动故障切换的详细分析

    1.服务器规划 Master:192.168.0.152 Slave: 192.168.0.153 192.168.0.154 监控服务器: 192.168.0.154 注意:真实生产环境最好有一台单 ...

  5. mysql replication延迟_MySQL Replication--复制延迟01--源码瞎猜

    本人完全不懂MySQL源码,以下文字纯属瞎猜,如有误导,概不负责!. 源码版本:MySQL 5.6.28 在sql/rpl_slave.cc文件中,time_diff的计算代码为: /*The pse ...

  6. mysql replication 延时_MySQL:延迟的主从复制 ( Delayed Replication )

    MySQL 5.6 已经支持延迟的流复制, 可设置备节点的延迟时间, 延迟复制是有意义的,例如防止主节点数据误删,查看数据库历史状态等. 重点:延迟复制实在原有的主从复制基础上.所以先要有主从复制的环 ...

  7. MySQL怎么运行的系列(十一)快照读、锁定读、半一致性读 和 加锁语句分析

    本系列文章目录 展开/收起 MySQL怎么运行的系列(一)mysql体系结构和存储引擎 MySQL怎么运行的系列(二)Innodb缓冲池 buffer pool 和 改良版LRU算法 Mysql怎么运 ...

  8. ​​​​​​​面试官:MySql在Repeatable Read下面是否会有幻读出现?

    目录 ▎事务隔离级别定义 了解:三种读现象(Read phenomena) ▎幻读:在可重复读级别下的 "特殊场景" 所以:MySQL的 InnoDB引擎在RR级别下到底能否解决幻 ...

  9. mysql外键读锁_MySQL的锁

    1.什么是锁 锁机制用于管理对共享资源的并发访问. lock与latch latch一般称为闩锁(轻量级的锁),因为其要求锁定的时间必须非常短.若持续的时间长,则应用的性能会非常差.在InnoDB存储 ...

最新文章

  1. 程序员的求生欲有所强?用 Python 花式哄女友
  2. 032_Notification通知
  3. cae计算机仿真分析技术,厉害了 揭秘汽车设计中CAE仿真技术
  4. Leet Code OJ 219. Contains Duplicate II [Difficulty: Easy]
  5. 600分理科选计算机专业,天津600分左右,计算机或电子信息专业,怎么选院校?...
  6. l4 l7 代理_什么是四层(L4 proxy)和七层负载均衡(L7 proxy)?区别是什么? 翻译自Nginx官网...
  7. python实现多语言语种识别_用Python进行语言检测
  8. win2003 apache php5.4 mysql_【php】在Windows2003下配置Apache2.4与php5.4
  9. mysqljoin的原理和优化
  10. c语言实验二作业,C语言实验作业8 - osc_5p0xffsa的个人空间 - OSCHINA - 中文开源技术交流社区...
  11. Subway Pursuit (二分)(交互题)
  12. 基于Hadoop大数据分析应用场景与实战
  13. 在iPhone任何界面截图完整图文教程
  14. Pascal Sentences数据集预处理
  15. D5渲染器 2.0 全新升级|天气系统、路径动画、草地材质,内置海量正版素材库
  16. python源代码编译后的文件扩展名-python源代码被解释器转换后的格式是什么?
  17. Android系统开机时间优化
  18. 使用记录6_发布微信小游戏
  19. python--绘制WRF模式近地面风场以及辐射
  20. C# 后台 背景图片拼接图片与文字,文字图片生成与背景图片合成

热门文章

  1. 2018年11月26日到2019年4月26日工作汇总
  2. 服务器内存占用导致死机原因
  3. 4.5.6 Map接口
  4. python花猫_Python猫荐书系列之五:Python高性能编程
  5. 右下角托盘广告消息弹窗?如邮件图标广告等通用解决方法
  6. gnss接收机矢量跟踪环路
  7. 如何将Discord服务器连接到Twitch流或YouTube频道
  8. python炫酷可视化_Python 一行代码搞定炫酷可视化,你需要了解一下 Cuffl
  9. PostgreSQL 查找替换函数
  10. RFM数据分析法帮你的男朋友值不值得嫁