【Mysql-InnoDB 系列】锁定读

本文涉及的产品
RDS PostgreSQL Serverless,0.5-4RCU 50GB 3个月
推荐场景:
对影评进行热评分析
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS SQL Server,基础系列 2核4GB
简介: 锁定读,是相对于一致(非锁定)读来说的。当我们在同一个事务(T1)中先读数据,然后执行插入或更新相关数据时,普通的SELECT语句并不能给予足够的保护。其他事务也可能更新或删除我们在T1事务中查询的相同行。InnoDB支持两种类型的锁定读,来提供额外的保护

系列文章:

【Mysql-InnoDB 系列】InnoDB 架构

【Mysql-InnoDB 系列】锁

【Mysql-InnoDB 系列】事务模型

【Mysql-InnoDB 系列】关于一致读

一 概念

锁定读,是相对于一致(非锁定)读来说的。

当我们在同一个事务(T1)中先读数据,然后执行插入或更新相关数据时,普通的SELECT语句并不能给予足够的保护。其他事务也可能更新或删除我们在T1事务中查询的相同行。InnoDB支持两种类型的锁定读,来提供额外的保护:

1、SELECT ... FOR SHARE

在所有读取的行上设置一个共享模式锁。其他会话可以读这些行,但在你的事务提交之前都不能进行修改。如果这些行中有被其他未提交的事务(T2)修改的记录,你的查询需要等到T2事务结束之后并使用最新的值。

注意:

SELECT ... FOR SHARESELECT ... LOCK IN SHARE MODE的替代,但LOCK IN SHARE MODE 保持向后兼容。这两个语句是等价的。不过,FOR SHARE支持 OF table_name, NOWAITSKIP LOCKED选项。

MySQL 8.0.22版本之前SELECT ... FOR SHARE 需要 SELECT权限,以及DELETE、LOCK TABLES 或 UPDATE这三个权限中的至少一个。从MySQL 8.0.22开始,只需要SELECT权限。

MySQL 8.0.22开始,SELECT ... FOR SHARE语句不再需要获取MySQL授权表中的读锁权限。

2、SELECT ... FOR UPDATE

对于搜索到的索引记录,锁住行和任意与之关联的索引项,这与你在那些行上执行一个UPDATE语句操作时相同。在特定的事务隔离级别下,其他事务无法更新这些行,无法执行SELECT ... FOR SHARE,或读取数据。一致读取忽略在“读取”视图中存在的记录上设置的所有锁。(记录的旧版本无法被锁;它们是通过在记录的内存副本上应用撤消日志来重建的)

这些子句主要用于处理树结构或图形结构的数据(在单个表中或拆分到多个表中)。您可以从一个地方到另一个地方遍历边或树枝,同时保留返回并更改这些“指针”值的权利。提交或回滚事务时,将释放由FOR SHARE和FOR UPDATE查询设置的所有锁。

注意:

锁定读只有在禁用自动提交时才可能(或者在事务开始时使用START_TRANSACTION或设置autocommit=0)。

外层语句中的locking read子句不会锁定嵌套子查询中表的行,除非在子查询中也指定了locking read子句。例如,下面的语句不会锁定表t2中的行:

SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2) FOR UPDATE;

如果要锁住表t2中的行,需要在子查询中增加一个锁定读子句:

SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2 FOR UPDATE) FOR UPDATE;

二 锁定读示例

假设你想要在表child中插入一个新行,并确保child中的一条记录在表parent中有一个parent记录。你的应用程序代码可以确保整个操作序列中的引用完整性。

首先,使用一个一致读来查询表PARENT 并 验证parant记录存在。你能否确保安全地把这条child记录插入到表child中?答案是否定的,因为某些其他的会话,可能会在你的SELECT语句和INSERT语句执行的期间删除掉这条parent记录,而不会通知你。

为了避免这个潜在的问题,在执行SELECT时使用FOR SHARE:

SELECT * FROM parent WHERE NAME = 'Jones' FOR SHARE;

在FOR SHARE查询返回parent 'Jones'之后,你可以安全第一添加child记录到child表中,然后提交事务。任何试图在parent表的适用行中获取独占锁的事务都会等待,直到你的操作完成,也就是说,直到所有表中的数据处于一致状态。

另一个例子,考虑child_codes  表中的一个integer类型的计数器字段 ,用于为添加到表child的每个子级分配唯一标识符。不要使用一致读取或共享模式读取来读取计数器的当前值,因为数据库的两个用户可以看到计数器的相同值,如果两个事务尝试向child表中添加具有相同标识符的行,则会发生重复键错误。

这里,FOR SHARE并不是好的解决方法,因为如果两个用户同时读取计数器,则至少有一个用户在尝试更新计数器时会陷入死锁。

为了实现读和counter自增,首先使用FOR UPDATE来实现一个计数器字段的锁定读,然后自增计数器字段。例如:

SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;

SELECT ... FOR UPDATE读取最新的可用数据,在它读的任一行上设置排它锁。因此,它设置的锁与搜索到的SQL更新对行设置的锁相同。

前面的描述仅仅是SELECT ... FOR UPDATE如何执行的一个示例。在MySQL中,生成唯一标识符的特定任务实际上可以只通过对表的一次访问来完成:

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();

SELECT语句仅检索标识符信息(特定于当前连接)。它不访问任何表。

三 锁定读并发-使用NOWAIT 和 SKIP LOCKED

   如果一行记录被某个事物T1锁住,另一个SELECT ... FOR UPDATE 或 SELECT ... FOR SHARE 的事务请求同一个被锁住的行时,必须等待直到阻塞中的事务T1释放这个行锁。这个行为组织了其他事务更新或删除被其他事务查询并即将修改的行。如果希望查询在请求的行被锁定时立即返回,或者从结果集中排除锁定的行是可以接受的,则无需等待释放行锁定。

为了避免等待其他事务释放行锁,NOWAIT和SKIP LOCKED选项可以与SELECT ... FOR UPDATE 或 SELECT ... FOR SHARE 这些锁定读语句组合使用。

  • NOWAIT

   使用了NOWAIT的锁定读不会等待获取行锁。查询会立即执行,当请求的行被锁住时立即返回失败。

  • SKIP LOCKED

   使用SKIP LOCKED的锁定读也不会等待获取行锁。与NOWAIT不同的是,查询也会立即返回,但如果存在锁住的行时,会把这些行从结果集中移除(即只返回没有被锁住的行)。

注意:

   使用SKIP LOCKED的查询返回的是不一致的数据视图。SKIP LOCKED因此并不适合一般事务使用。不过,当多个会话访问同一个类似队列的表时,它可以用来避免锁争用。

NOWAIT 和 SKIP LOCKED仅适用于行级锁。

使用NOWAIT 或 SKIP LOCKED的语句对基于语句的复制不安全。

下面的示例演示NOWAIT 和 SKIP LOCKED。 会话1开启事务,在一条记录上获取行锁;会话2尝试在同一行记录上使用NOWAIT选项进行锁定读。由于请求的行被会话1锁住,会话2的请求立即返回失败。 在会话3中,使用SKIP LOCKED的锁定读返回了除去被会话1锁住的行之外的结果集。

【注】注意mysql版本,MySQL 8.0.22之后才行。如果是在前面的版本,使用时会报语法错误,因为版本还没有支持NOWAIT 和 SKIP LOCKED语法。

# Session 1:
mysql> CREATE TABLE t (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
mysql> INSERT INTO t (i) VALUES(1),(2),(3);
mysql> START TRANSACTION;
mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE;
+---+
| i |
+---+
| 2 |
+---+
# Session 2:
mysql> START TRANSACTION;
mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE NOWAIT;
ERROR 3572 (HY000): Do not wait for lock.
# Session 3:
mysql> START TRANSACTION;
mysql> SELECT * FROM t FOR UPDATE SKIP LOCKED;
+---+
| i |
+---+
| 1 |
| 3 |
+---+

参考资料:

https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
相关文章
|
存储 前端开发 Java
揭秘!如何用Spring Boot轻松打造动态二维码生成器?一键解锁无限可能,你的创意将无处不在!
【8月更文挑战第29天】在数字化时代,二维码成为信息快速传递的关键工具,广泛应用于支付、身份验证和产品追溯等场景。本文将指导你如何利用Spring Boot框架和Google的ZXing库,搭建一个动态生成二维码的Web服务。首先,通过Spring Initializr创建项目并配置相关依赖;接着,编写二维码生成逻辑和服务类;最后,在Controller中整合这些功能,提供RESTful接口供外部调用。通过访问`/generate-qrcode?text=你的内容`即可测试API并获取二维码图片。这为开发者提供了强大的工具,未来还可进一步优化存储和提升性能。
573 3
|
Java Maven
IDEA 2018 2020 2021 2022 各版本对Maven版本兼容问题汇总
Maven3.6.3版本兼容问题 错误信息如下: 测试范围如下图: 注意:针对一些老项目 还是尽量采用 3.6.3版本,针对idea各个版本的兼容性就很兼容 0.IDEA 2022 兼容maven 3.8.1及之前的所用版本 1.IDEA 2021 兼容maven 3.8.1及之前的所用版本 2.IDEA 2020 兼容Maven 3.6.3及之前所有版本 3.IDEA 2018 兼容Maven3.6.1及之前所有版本
5087 0
IDEA 2018 2020 2021 2022 各版本对Maven版本兼容问题汇总
|
8月前
|
人工智能 弹性计算 运维
|
网络协议 Linux
Linux 网络配置
了解基本命令与权限后,如何让Linux系统联网?可通过编辑`/etc/sysconfig/network-scripts/`下的`ifcfg-ethX`文件配置网卡,其中`ethX`代表第X块网卡。对于DHCP自动获取或静态IP,需设置`BOOTPROTO`参数,并指定IP、子网掩码和网关等。配置完成后,运行`/etc/init.d/network restart`重启网络。DNS可在`/etc/resolv.conf`中设置,添加`nameserver`行即可,无需重启网卡。配置好后,可用`ifconfig`查看IP信息,并通过远程工具如SecureCRT连接服务器。
391 0
|
11月前
|
数据挖掘 数据安全/隐私保护
抖音运营:解锁流量增长密码
在短视频盛行的时代,抖音成为流量蓝海,众多创作者和品牌竞相涌入。要在激烈竞争中脱颖而出,除了创作优质内容和巧妙运营外,数据分析至关重要。精准定位目标受众,挖掘创意与热门趋势,优化视频制作、剪辑节奏及发布时间,积极互动并分析关键数据指标(如播放量、点赞数、完播率等),不断优化运营策略,才能实现流量快速增长和账号的长期发展。
1196 11
|
消息中间件 缓存 Java
【Java】全套云HIS(医院信息管理系统)可对接医保 采用云端SaaS模式部署
总体框架: SaaS应用,全浏览器访问 前后端分离,多服务协同 服务可拆分,功能易扩展
460 1
【Java】全套云HIS(医院信息管理系统)可对接医保 采用云端SaaS模式部署
|
安全 Java Apache
如何安装与使用Spring Boot 2.2.x、Spring Framework 5.2.x与Apache Shiro 1.7进行高效开发
【7月更文第1天】在现代Java Web开发领域,Spring Boot以其简化配置、快速开发的特点备受青睐。结合Spring Framework的成熟与Apache Shiro的强大权限控制能力,我们可以轻松构建安全且高效的Web应用。本篇文章将指导你如何安装并使用Spring Boot 2.2.x、Spring Framework 5.2.x以及Apache Shiro 1.7来构建一个具备基础权限管理功能的项目。
406 0
后端测试------使用Postman统一添加请求头
后端测试------使用Postman统一添加请求头
|
安全 调度 Python
什么是Python中的事件驱动编程?如何使用`asyncio`模块实现异步事件处理?
【2月更文挑战第4天】【2月更文挑战第9篇】什么是Python中的事件驱动编程?如何使用`asyncio`模块实现异步事件处理?
350 0
|
SQL 存储 关系型数据库
数据库优化——慢查询MySQL定位优化流程
如何定位并优化慢查询SQL?如何使用慢查询日志?本文带来慢查询例子演示,新手都能看懂!那查询语句慢怎么办?explain带你分析sql执行计划!当主键索引、唯一索引、普通索引都存在,查询优化器如何选择?本文带你一探究竟!
985 0
数据库优化——慢查询MySQL定位优化流程