GreatSQL 中为什么 Update 不会被锁等待
出乎意料的现象
我们有一张测试表 t1,表中有一些数据,当 session1 开启一个事务,并执行了 select for update 操作后仍未提交事务,在并发事务(如 session2)开启事务并行执行一些操作会有不同的锁现象,表现在:
- select for update 会出现锁等待
- delete 会出现锁等待
- update 执行成功,不受影响
为什么 select for update、delete 会出现预期内的锁等待,而 update 不会出现锁等待呢?
问题复现
首先在 test 库中创建一张测试表 t1
greatsql> show create table t1\G; *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` int NOT NULL, `type_id` int DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin 1 row in set (0.00 sec)
在表中插入原始数据如下
greatsql> select * from t1; +----+---------+ | id | type_id | +----+---------+ | 1 | 3 | | 2 | 3 | | 3 | 4 | | 4 | 3 | | 5 | 4 | | 6 | 4 | | 7 | 5 | | 8 | 5 | | 9 | 4 | | 12 | 4 | | 15 | 4 | +----+---------+ 11 rows in set (0.00 sec)
现在 session1 对表 t1 开启一个事务,并执行select for update
操作,保持事务,不执行 commit
greatsql> begin; Query OK, 0 rows affected (0.02 sec) greatsql> select * from t1 where type_id=4 for update; +----+---------+ | id | type_id | +----+---------+ | 3 | 4 | | 5 | 4 | | 6 | 4 | | 9 | 4 | | 12 | 4 | | 15 | 4 | +----+---------+ 6 rows in set (0.02 sec)
模拟并发 session2 开启事务,并发执行操作
greatsql> select * from t1 where type_id=3 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction greatsql> delete from t1 where type_id=3; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction greatsql> update t1 set type_id=2 where type_id=3; Query OK, 3 rows affected (0.01 sec) Rows matched: 3 Changed: 3 Warnings: 0
现象解答
其实这并非是一个问题,而是 GreatSQL READ COMMITTED 隔离级别下的一种正常现象,具体表现在
当我们使用的是 GreatSQL 的 READ COMMITTED 隔离级别
greatsql> show variables like '%iso%'; +-----------------------+----------------+ | Variable_name | Value | +-----------------------+----------------+ | transaction_isolation | READ-COMMITTED | +-----------------------+----------------+ 1 row in set (0.03 sec)
在 session1 执行 select * from t1 where type=4 for update;
后,由于表 t1 在 type_id 字段上并没有索引,这会导致全表扫描。但是呢,在 GreatSQL 的 READ COMMITTED 隔离级别下会对所有的行进行加锁,找到对应的记录后加锁,会对不符合条件的行释放锁
因此 session1 最后获取了意向排他锁(IX)和 6 条行锁(X,REC_NOT_GAP),仅仅锁住了 type_id=4 的 6 行记录。
greatsql> select ENGINE_LOCK_ID,ENGINE_TRANSACTION_ID,LOCK_MODE,LOCK_TYPE,INDEX_NAME,OBJECT_SCHEMA,OBJECT_NAME,LOCK_DATA,LOCK_STATUS,THREAD_ID from performance_schema.data_locks; +-----------------------------------------+-----------------------+---------------+-----------+------------+---------------+-------------+-----------+-------------+-----------+ | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | LOCK_MODE | LOCK_TYPE | INDEX_NAME | OBJECT_SCHEMA | OBJECT_NAME | LOCK_DATA | LOCK_STATUS | THREAD_ID | +-----------------------------------------+-----------------------+---------------+-----------+------------+---------------+-------------+-----------+-------------+-----------+ | 140547538324696:1073:140547436105584 | 2719 | IX | TABLE | NULL | test | t1 | NULL | GRANTED | 93 | | 140547538324696:11:4:6:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 3 | GRANTED | 93 | | 140547538324696:11:4:7:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 5 | GRANTED | 93 | | 140547538324696:11:4:8:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 6 | GRANTED | 93 | | 140547538324696:11:4:9:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 9 | GRANTED | 93 | | 140547538324696:11:4:10:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 12 | GRANTED | 93 | | 140547538324696:11:4:11:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 15 | GRANTED | 93 | +-----------------------------------------+-----------------------+---------------+-----------+------------+---------------+-------------+-----------+-------------+-----------+ 7 rows in set (0.00 sec)
- select for update 会出现锁等待
session2 执行 select * from t1 where type_id=3 for update;
后,由于 type_id 字段没有索引,执行全表扫描,从第一行开始,但是此时 session1 获得的行锁中第一个为 id=3 的记录,因此 session2 获得了 id=1 和 id=2 的行锁,但是当扫描到第三行时(即 id=3 这条记录),这一行已经被 session1 持有了锁,因此 session2 被阻塞了,出现了锁等待。
因此 session2 最后获取了意向排他锁(IX) 和 3 条行锁(X,REC_NOT_GAP),id=3 这一行的锁被阻塞获取不到。
greatsql> select ENGINE_LOCK_ID,ENGINE_TRANSACTION_ID,LOCK_MODE,LOCK_TYPE,INDEX_NAME,OBJECT_SCHEMA,OBJECT_NAME,LOCK_DATA,LOCK_STATUS,THREAD_ID from performance_schema.data_locks; +-----------------------------------------+-----------------------+---------------+-----------+------------+---------------+-------------+-----------+-------------+-----------+ | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | LOCK_MODE | LOCK_TYPE | INDEX_NAME | OBJECT_SCHEMA | OBJECT_NAME | LOCK_DATA | LOCK_STATUS | THREAD_ID | +-----------------------------------------+-----------------------+---------------+-----------+------------+---------------+-------------+-----------+-------------+-----------+ | 140547538325504:1073:140547436111664 | 2726 | IX | TABLE | NULL | test | t1 | NULL | GRANTED | 94 | | 140547538325504:11:4:2:140547436108560 | 2726 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 1 | GRANTED | 94 | | 140547538325504:11:4:3:140547436108560 | 2726 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 2 | GRANTED | 94 | | 140547538325504:11:4:6:140547436108904 | 2726 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 3 | WAITING | 94 | | 140547538324696:1073:140547436105584 | 2719 | IX | TABLE | NULL | test | t1 | NULL | GRANTED | 93 | | 140547538324696:11:4:6:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 3 | GRANTED | 93 | | 140547538324696:11:4:7:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 5 | GRANTED | 93 | | 140547538324696:11:4:8:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 6 | GRANTED | 93 | | 140547538324696:11:4:9:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 9 | GRANTED | 93 | | 140547538324696:11:4:10:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 12 | GRANTED | 93 | | 140547538324696:11:4:11:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 15 | GRANTED | 93 | +-----------------------------------------+-----------------------+---------------+-----------+------------+---------------+-------------+-----------+-------------+-----------+ 11 rows in set (0.00 sec)
- delete 会出现锁等待
session2 执行 delete from t1 where type_id=3;
后,由于 type_id 字段没有索引,执行全表扫描,从第一行开始,但是此时 session1 获得的行锁中第一个为 id=3 的记录,因此 session2 获得了 id=1 和 id=2 的行锁,但是当扫描到第三行时(即 id=3 这条记录),这一行已经被 session1 持有了锁,因此 session2 被阻塞了,出现了锁等待。
因此 session2 最后获取了意向排他锁(IX) 和 3 条行锁(X,REC_NOT_GAP),id=3 这一行的锁被阻塞获取不到。
greatsql> select ENGINE_LOCK_ID,ENGINE_TRANSACTION_ID,LOCK_MODE,LOCK_TYPE,INDEX_NAME,OBJECT_SCHEMA,OBJECT_NAME,LOCK_DATA,LOCK_STATUS,THREAD_ID from performance_schema.data_locks; +-----------------------------------------+-----------------------+---------------+-----------+------------+---------------+-------------+-----------+-------------+-----------+ | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | LOCK_MODE | LOCK_TYPE | INDEX_NAME | OBJECT_SCHEMA | OBJECT_NAME | LOCK_DATA | LOCK_STATUS | THREAD_ID | +-----------------------------------------+-----------------------+---------------+-----------+------------+---------------+-------------+-----------+-------------+-----------+ | 140547538325504:1073:140547436111664 | 2727 | IX | TABLE | NULL | test | t1 | NULL | GRANTED | 94 | | 140547538325504:11:4:2:140547436108560 | 2727 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 1 | GRANTED | 94 | | 140547538325504:11:4:3:140547436108560 | 2727 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 2 | GRANTED | 94 | | 140547538325504:11:4:6:140547436108904 | 2727 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 3 | WAITING | 94 | | 140547538324696:1073:140547436105584 | 2719 | IX | TABLE | NULL | test | t1 | NULL | GRANTED | 93 | | 140547538324696:11:4:6:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 3 | GRANTED | 93 | | 140547538324696:11:4:7:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 5 | GRANTED | 93 | | 140547538324696:11:4:8:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 6 | GRANTED | 93 | | 140547538324696:11:4:9:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 9 | GRANTED | 93 | | 140547538324696:11:4:10:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 12 | GRANTED | 93 | | 140547538324696:11:4:11:140547436102528 | 2719 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 15 | GRANTED | 93 | +-----------------------------------------+-----------------------+---------------+-----------+------------+---------------+-------------+-----------+-------------+-----------+ 11 rows in set (0.00 sec)
讲到这里,不得不说一下手册权威解释了,在 GreatSQL READ COMMITTED 隔离级别下,手册是这样解释的
对于 update 和 delete 操作 innodb 仅仅会锁住更新和删除行,在 GreatSQL 中根据 where 条件搜索后,会将不满足条件行的行锁进行释放,这样可以很好的降低死锁发生的概率,但也仍然可能发生。
Using READ COMMITTED has additional effects:
- For UPDATE or DELETE statements, InnoDB holds locks only for rows that it updates or deletes. Record locks for nonmatching rows are released after MySQL has evaluated the WHERE condition. This greatly reduces the probability of deadlocks, but they can still happen.
- For UPDATE statements, if a row is already locked, InnoDB performs a “semi-consistent” read, returning the latest committed version to MySQL so that MySQL can determine whether the row matches the WHERE condition of the UPDATE. If the row matches (must be updated), MySQL reads the row again and this time InnoDB either locks it or waits for a lock on it.
手册也说明了,对于 update 操作,如果某行被锁后,innodb 会执行 semi-consistent(半一致读)操作,将最新提交的版本返回给 GreatSQL ,以便 GreatSQL 来判断该行是否符合 update 的 where 条件。如果该行匹配(必须更新),GreatSQL 会再次读取该行,并且这一次 innodb 要么锁定它,要么等待它上的锁。
- update 执行成功,不受影响
这也就将要说的 session2 执行 update t1 set tpye_id=2 where type=3;
后为什么成功了。 session2 执行 update 后,innodb 会进行半一致读,获取每一行的最新提交版本,然后通过 where 条件过滤,判断该行是否能被锁定,如下图,对于 id=1,id=2 的记录符合 where 条件,可以获取到行锁,对于 id=3 的记录并不符合 where 条件记录,所以 session2 对 id=3 的行不加锁,也就说和 session1 持有的 id=3 的行锁并不冲突。继续后续扫描,对于 id=4 的记录符合 where 条件,可以获取到行锁。直到完成所有的扫描和加锁。
greatsql> select ENGINE_LOCK_ID,ENGINE_TRANSACTION_ID,LOCK_MODE,LOCK_TYPE,INDEX_NAME,OBJECT_SCHEMA,OBJECT_NAME,LOCK_DATA,LOCK_STATUS,THREAD_ID from performance_schema.data_locks; +-----------------------------------------+-----------------------+---------------+-----------+------------+---------------+-------------+-----------+-------------+-----------+ | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | LOCK_MODE | LOCK_TYPE | INDEX_NAME | OBJECT_SCHEMA | OBJECT_NAME | LOCK_DATA | LOCK_STATUS | THREAD_ID | +-----------------------------------------+-----------------------+---------------+-----------+------------+---------------+-------------+-----------+-------------+-----------+ | 140547538325504:1073:140547436111664 | 2749 | IX | TABLE | NULL | test | t1 | NULL | GRANTED | 94 | | 140547538325504:11:4:2:140547436108560 | 2749 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 1 | GRANTED | 94 | | 140547538325504:11:4:3:140547436108560 | 2749 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 2 | GRANTED | 94 | | 140547538325504:11:4:4:140547436108560 | 2749 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 4 | GRANTED | 94 | | 140547538324696:1073:140547436105584 | 2748 | IX | TABLE | NULL | test | t1 | NULL | GRANTED | 93 | | 140547538324696:11:4:6:140547436102528 | 2748 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 3 | GRANTED | 93 | | 140547538324696:11:4:7:140547436102528 | 2748 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 5 | GRANTED | 93 | | 140547538324696:11:4:8:140547436102528 | 2748 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 6 | GRANTED | 93 | | 140547538324696:11:4:9:140547436102528 | 2748 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 9 | GRANTED | 93 | | 140547538324696:11:4:10:140547436102528 | 2748 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 12 | GRANTED | 93 | | 140547538324696:11:4:11:140547436102528 | 2748 | X,REC_NOT_GAP | RECORD | PRIMARY | test | t1 | 15 | GRANTED | 93 | +-----------------------------------------+-----------------------+---------------+-----------+------------+---------------+-------------+-----------+-------------+-----------+ 11 rows in set (0.00 sec)
GreatSQL 就是利用 semi-consistent(半一致读)操作对 update 进行的优化,从而提高并发性。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
vivo 全链路多版本开发测试环境落地实践
作者:来自 vivo 互联网研发效能团队- Wang Kang 测试环境全链路多版本部署,解决多测试环境资源争抢等问题。 一、背景介绍 软件系统中全链路指的是从用户请求发起,到最终返回响应的整个过程中所涉及到的所有环节和组件。在微服务软件架构风格盛行的今天,因为微服务独立部署、松耦合等特性,往往一个业务系统由数目较多的服务组成,较多的服务往往带来一系列操作上的复杂性。 全链路部署指的是将整个软件系统的所有服务一次性部署环境中的一种部署方式,这种部署方式可以简化我们日常发布流程,确保系统的所有服务协同工作。 vivo 的 CICD 发布系统从【构建部署脚本自动化】>【持续集成平台化】>【集成更多 DevOps 功能和组件】,演进到现在更加灵活编辑的多服务编排方式。为了适应最新的微服务软件架构风格,CICD 发布系统通过中间件组件和调用链技术实现的落地,基于容器建立了全链路多版本流水线部署能力。 多版本部署则是基于灰度发布的理念,在同一时间内,将不同版本的服务部署在同一个环境中,使得不同版本的服务可以同时运行和提供服务的一种部署方式。 随着互联网发展增速,软件开发和部署需要快速...
- 下一篇
Alluxio Enterprise AI on K8s 部署教程
👉 Alluxio Enterprise AI on K8s 部署教程 👈 链接为Alluxio Enterprise AI on K8s 部署视频教程。下面内容将通过文字方式主要介绍如何通过 Operator(Kubernetes 管理应用程序的扩展)在 Kubernetes 上安装 Alluxio。 1. 系统要求 Kubernetes 至少1.19版本的 Kubernetes 集群,支持 特性门控 确保集群的 Kubernetes 网络策略允许应用程序(Alluxio 客户端)与定义端口上的 Alluxio Pods 之间建立连接 Kubernetes 集群已安装至少3.6.0版本的 Helm 3 用于存储和管理容器镜像的镜像仓库 硬件 每个节点至少 8个 CPU 和 32GB 内存 每个节点至少100GB 的存储空间 权限。参考: 使用 RBAC 授权 创建 CRD(自定义资源定义)的权限 为 Operator Pod 创建 ServiceAccount、ClusterRole 和 ClusterRoleBinding 的权限 创建 operator 所在命名空间的权限 ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6