MySQL 更新查询 - 竞争条件和行锁定会遵守“where”条件吗? (PHP、PDO、MySQL、InnoDB)

2023-12-30

我正在尝试建立一个先到先得的模型销售页面。我们有 n 个相同类型的物品。我们希望将这 n 个项目分配给前 n 个发出请求的用户。每个项目对应有一个数据库行。当用户按下购买按钮时,系统会尝试查找尚未出售的条目(reservationCompleted = FALSE)并更新用户 ID 和设置reservationCompleted为真。

由于我使用的数据库引擎是 InnoDB,因此我知道有一个内部锁定机制,不允许两个进程同时对同一行进行更新。

我的问题是,

如果我使用的语句如下,如果两个请求同时到达,这是否会导致不同的用户被分配到同一行?

$query = "UPDATE available_items
    SET assignedPhone=".$user->phone.",
        reservationCompleted = TRUE,
        assignmentCreatedTimestamp =".time()."
    WHERE id=".$itemListing['id']."
    AND reservationCompleted=FALSE";
$stmt = $pdo->prepare($query);
$stmt->execute();

考虑以下情况。

两个不同的进程获取同一行(例如 id=5)并尝试更新数据库条目。但其中一个人得到了锁。它更新项目并释放锁,下一个进程获得锁。那么,在执行更新之前它会再次验证where条件吗?


在比赛情况下,where 条件将受到尊重,但您必须小心检查谁赢得了比赛。

请考虑以下演示,了解其工作原理以及为什么必须小心。

首先,设置一些最小的表。

CREATE TABLE table1 (
`id` TINYINT UNSIGNED NOT NULL PRIMARY KEY,
`locked` TINYINT UNSIGNED NOT NULL,
`updated_by_connection_id` TINYINT UNSIGNED DEFAULT NULL
) ENGINE = InnoDB;

CREATE TABLE table2 (
`id` TINYINT UNSIGNED NOT NULL PRIMARY KEY
) ENGINE = InnoDB;

INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);

id扮演的角色id在你的桌子上,updated_by_connection_id行为就像assignedPhone, and locked like reservationCompleted.

现在让我们开始比赛测试。您应该打开 2 个命令行/终端窗口,连接到 mysql 并使用在其中创建这些表的数据库。

连接1

start transaction;

连接2

start transaction;

连接1

UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;

查询正常,1 行受影响(0.00 秒) 匹配的行:1 更改:1 警告:0

连接2

UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;

连接 2 正在等待

连接1

SELECT * FROM table1 WHERE id = 1;
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      1 |                        1 |
+----+--------+--------------------------+
commit;

此时,连接2被释放以继续并输出以下内容:

连接2

查询正常,0 行受影响(23.25 秒)匹配的行:0 更改:0 警告:0

SELECT * FROM table1 WHERE id = 1;
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      1 |                        1 |
+----+--------+--------------------------+
commit;

一切看起来都很好。我们看到,是的,WHERE 子句在竞争情况下得到了尊重。

我之所以说你必须小心,是因为在实际应用程序中事情并不总是这么简单。您可能在交易中进行其他操作,这实际上可能会改变结果。

让我们使用以下命令重置数据库:

delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);

现在,考虑这种情况,其中 SELECT 在 UPDATE 之前执行。

连接1

start transaction;

SELECT * FROM table2;

空集(0.00 秒)

连接2

start transaction;

SELECT * FROM table2;

空集(0.00 秒)

连接1

UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;

查询正常,1 行受影响(0.00 秒) 匹配的行:1 更改:1 警告:0

连接2

UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;

连接 2 正在等待

连接1

SELECT * FROM table1 WHERE id = 1;
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      1 |                        1 |
+----+--------+--------------------------+
1 row in set (0.00 sec)
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      1 |                        1 |
+----+--------+--------------------------+
1 row in set (0.00 sec)
commit;

此时,连接2被释放以继续并输出以下内容:

查询正常,0 行受影响(20.47 秒)匹配的行:0 更改:0 警告:0

好吧,让我们看看谁赢了:

连接2

SELECT * FROM table1 WHERE id = 1;
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      0 |                     NULL |
+----+--------+--------------------------+

等等,什么?为什么是locked 0 and updated_by_connection_id NULL??

这就是我说的小心。罪魁祸首实际上是由于我们一开始就做了选择。为了获得正确的结果,我们可以运行以下命令:

SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      1 |                        1 |
+----+--------+--------------------------+
commit;

通过使用 SELECT ... FOR UPDATE 我们可以获得正确的结果。这可能会非常令人困惑(就像我最初一样),因为 SELECT 和 SELECT ... FOR UPDATE 给出了两个不同的结果。

发生这种情况的原因是由于默认的隔离级别READ-REPEATABLE。当第一个 SELECT 被执行时,就在start transaction;,创建快照。所有未来的非更新读取都将从该快照完成。

因此,如果你在更新后天真地选择,它将从原始快照中提取信息,即before该行已更新。通过执行 SELECT ... FOR UPDATE,您可以强制它获取正确的信息。

然而,在实际应用中这可能会成为一个问题。例如,您的请求被包装在事务中,并且在执行更新后您想要输出一些信息。收集和输出信息可以由单独的、可重用的代码处理,您不想“以防万一”用 FOR UPDATE 子句乱扔垃圾。由于不必要的锁定,这会导致很多挫败感。

相反,你会想走不同的路。您在这里有很多选择。

一是确保更新完成后提交事务。在大多数情况下,这可能是最好、最简单的选择。

另一种选择是不尝试使用 SELECT 来确定结果。相反,您可以读取受影响的行,并使用它(更新 1 行与更新 0 行)来确定 UPDATE 是否成功。

另一种选择,也是我经常使用的一种选择,因为我喜欢将单个请求(如 HTTP 请求)完全封装在单个事务中,是确保事务中执行的第一个语句是 UPDATE 或 SELECT ... FOR UPDATE。这将导致在允许连接继续之前不会拍摄快照。

让我们再次重置测试数据库,看看它是如何工作的。

delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);

连接1

start transaction;

SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      0 |                     NULL |
+----+--------+--------------------------+

连接2

start transaction;

SELECT * FROM table1 WHERE id = 1 FOR UPDATE;

连接 2 现在正在等待。

连接1

UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;

查询正常,1 行受影响(0.01 秒) 匹配的行:1 更改:1 警告:0

SELECT * FROM table1 WHERE id = 1;
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      1 |                        1 |
+----+--------+--------------------------+
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      1 |                        1 |
+----+--------+--------------------------+
commit;

连接 2 现已发布。

连接2

+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      1 |                        1 |
+----+--------+--------------------------+

在这里,您实际上可以让服务器端代码检查此 SELECT 的结果并知道它是准确的,甚至不需要继续后续步骤。但是,为了完整起见,我将像以前一样完成。

UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;

查询正常,0 行受影响(0.00 秒) 匹配行:0 更改:0 警告:0

SELECT * FROM table1 WHERE id = 1;
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      1 |                        1 |
+----+--------+--------------------------+
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
|  1 |      1 |                        1 |
+----+--------+--------------------------+
commit;

现在您可以看到,在连接 2 中,SELECT 和 SELECT ... FOR UPDATE 给出了相同的结果。这是因为 SELECT 从中读取的快照直到连接 1 提交后才创建。

所以,回到你原来的问题:是的,在所有情况下,WHERE 子句都会由 UPDATE 语句检查。但是,您必须小心可能执行的任何 SELECT,以避免错误地确定 UPDATE 的结果。

(是的,另一个选择是更改事务隔离级别。但是,我对此并没有真正的经验,也没有任何可能存在的问题,所以我不打算讨论它。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MySQL 更新查询 - 竞争条件和行锁定会遵守“where”条件吗? (PHP、PDO、MySQL、InnoDB) 的相关文章

  • 图像随机损坏(但刷新后加载)并显示“资源解释为图像但使用 MIME 类型 text/html 传输”

    我目前正在开发一个简单的 php 网站 问题是 我的整个网站中的图像 发生在所有 php 文件中 随机损坏并显示错误资源解释为图像 但以 MIME 类型 text html 传输但是 如果我尝试多次刷新页面 可以再次加载图像并且错误消失 我
  • 是否可以将路由参数传递给 Laravel 中的控制器构造函数?

    是否可以将路由参数 或路由段 注入到控制器构造函数中 您找到一些代码来澄清我的问题 class TestController protected param public function construct paramFromRoute
  • Flask-login:无法理解它是如何工作的

    我试图理解如何Flask Login https flask login readthedocs org en latest works 我在他们的文档中看到他们使用预先填充的用户列表 我想使用数据库存储的用户列表 但是 我不明白其中的一些
  • CakePHP 视图包括其他视图

    我有一个 CakePHP 应用程序 在某些时候会显示带有产品媒体 图片或视频 的视图 我想知道是否有某种方式可以包含另一个威胁视频或威胁图片的视图 具体取决于标志 我想将这些 小视图 用于其他几个目的 所以它应该 像 蛋糕组件一样 以便重用
  • 如何将表中不存在但原始SQL中存在的实体字段设置为别名?

    假设我们有一个这样的查询 SELECT CUSTOM EXPRESSION as virtualfield FROM users 用户的实体本身具有 虚拟字段 但映射注释没有 因为表没有该字段 假设它作为原始 SQL 执行 我们如何使用上面
  • 覆盖 FOS 用户包中的“更改密码”模板

    我做了一些研究 遗憾的是找不到任何帮助 因此 我将 FOSUserBundle ChangePasswordAction 渲染到我的模板中 但它显示供应商提供的默认模板 我的渲染控制器的模板 block body h2 Einstellun
  • 使用值填充的 Symfony2 自定义字段类型

    这是先前问题的后续问题Symfony2 自定义表单类型或扩展 https stackoverflow com questions 24079288 symfony2 custom form type or extension 我正在尝试为订
  • docker 中的 php Curl 冲突 CURLOPT_FILE 和 CURLOPT_RETURNTRANSFER

    当我使用curl时CURLOPT FILE and CURLOPT RETURNTRANSFER选项 文件为空 没有任何curl错误 fp fopen saveTo w ch curl init fileUrl curl setopt ch
  • Facebook PHP-SDK 页面刷新后似乎丢失了 userID

    我似乎登录工作正常 我可以登录 接受应用程序 第一次 然后显示用户信息 例如姓名 图片 等 然而 当我刷新页面时 userid 又回到 0 我必须再次登录 我不确定问题是什么 我必须在每次页面加载时重新启动它还是什么 我不知道 我会发布一些
  • Facebook API sdk 4.0 - 将照片发布到 Facebook

    我正在尝试创建一个应用程序 用户可以在其中浏览照片并将其从计算机提交到 Facebook 为此 他们首先必须将照片上传到服务器 然后使用 Facebook 请求将此图像发布到 Facebook 我正在使用多部分 表单数据 这就是我到目前为止
  • 如何在 Windows 上安装 Zend 框架

    安装 Zend Framework 就是这么简单 是的 对 好吧 我正在写一本初学者的书 有一件不太详细的事情是最重要的部分 安装该死的东西 浏览了几个小时的快速入门指南后 它只说 下载 Zend 添加包含目录 bla bla 然后就完成了
  • PHP 编码风格回归;在开关/外壳中

    我们正在尝试为我们的团队实施新的编码风格指南 当未找到 break 时 php codeniffer 会在 switch case 语句上打印警告 如下所示 switch foo case 1 return 1 case 2 return
  • 如何使用更新资源控制器 laravel 4?

    我有带有索引 编辑 更新方法的客户控制器 Route resource customer CustomerController 控制器方法更新 public function update id echo id 我的 HTML 表单
  • 一次从多个表中删除行

    我正在尝试将 2 个查询合并为一个这样的查询 result db gt query DELETE FROM menu WHERE name new or die db gt error result db gt query DELETE F
  • 从 Laravel 4 输入生成新数组

    我使用 Input all 从动态生成的表单中获取一些输入 我使用 jQuery 来允许用户添加字段 字段名称为 first names last names 和 emails input 变量现在看起来像这样 array size 4 t
  • PHP 接口有属性吗?

    PHP 中的接口有属性 还是只有方法 您可以在 DocBlock 中为接口声明属性 然后 IDE 将提示接口的这些属性 PhpStorm 会这样做 但这不会强制在实现类中实际实现这些字段 例如 property string passwor
  • Jquery一键提交多个同名表单

    我有动态创建的循环表单 我需要一键提交所有表单 我正在遵循下面的代码 你能建议我怎么做吗 谢谢
  • 使用 Vue 的多模式组件

    我在 Vue 中实现动态模式组件时遇到问题 A common approach I follow to display a set of data fetched from the db is I dump each of the rows
  • 在 MySQL 中存储表情符号的编码问题:如何使用 Prisma ORM 在 NodeJS 中定义字符排序规则?

    亲爱的 Nodejs 专家和数据库专家 我们在 MySQL 数据库中存储表情符号和其他特殊字符时遇到问题 我们使用 Prisma 得到一个错误 这是我们使用的 ORM 参数无法从排序规则 utf8 general ci 转换为 utf8mb
  • 如何从日期中查找该月的最后一天?

    如何在 PHP 中获取该月的最后一天 Given a date 2009 11 23 我要2009 11 30 并给出 a date 2009 12 23 我要2009年12月31日 t返回给定日期所在月份的天数 请参阅的文档date ht

随机推荐

  • 获取带有泛型的 java.lang.Class

    我是一名学习 Java 的 C 人员 我正在尝试了解 Java 中的泛型如何工作 给定一个类或接口 SomeThing 我知道我可以这样做来获取该类型的类 Something class 现在 给定一个通用接口 我很想写 GenericIn
  • 用于查找和替换的 Xpath?

    各位论坛成员 我是 Xpath 新手 有以下问题 例如 假设我有 300 个单独的 XML 文件 并且我需要进行全局文本更改 该更改可能仅影响其中的 40 个 XML 文件 是否可以使用 Xpath 在所有 300 个 XML 文件中执行查
  • 多维 javascript 数组中的 For 循环

    从现在开始 我使用这个循环来迭代数组的元素 即使我将具有各种属性的对象放入其中 它也可以正常工作 var cubes for i in cubes cubes i dimension cubes i position x ecc 现在 假设
  • Firebase如何检查交易成功或失败?

    我正在尝试更新事务中的 firebase 节点 简单的事情 按照文档 https www firebase com docs ios guide saving data html https www firebase com docs io
  • 系统启动时哪个核心首先初始化?

    我想知道CPU启动时多核处理器的哪个核心首先初始化 我的意思是在引导加载程序级别 是第一个核心 还是随机核心 您想要阅读本地 apic 您可以在 卷 2a 中阅读 http www intel com content www us en p
  • 如何在R编程中显示同一坐标中的总数

    更新2017年9月11日问题 这是我在 R 中集群 kmode 的代码 library klaR setwd D kmodes data to cluster lt read csv kmodes csv header TRUE sep c
  • 射线和椭球相交精度提高

    我需要提高我的一个功能的精度大气散射 GLSL 片段着色器 https stackoverflow com a 19659648 2521214它计算单射线和轴对齐椭球体之间的交集 这是矿山大气散射着色器的核心功能 旧的原始着色器已打开fl
  • 对 favicon 静态图像的哈希进行摩卡测试失败

    我正在尝试使用 mocha 请求和 SHA1 哈希来编写集成测试 以确认 Express 提供的图标与文件系统上的图标相同 我得到两个不同的哈希值 但不明白为什么 编码有可能改变吗 process env NODE TLS REJECT U
  • ArrayBuffer 到 blob 的转换

    我有一个项目需要在浏览器中显示 djvu 架构 我发现这个老Github 上的库 https github com lebedkin minidjvu js据我了解 它将 djvu 文件转换为 bmp 然后将它们放入 canvas 元素中
  • 如何在 Android 4.2.2 上启用 JavaScript 控制台

    我正在尝试启用 JavaScript 控制台来调试运行 4 2 2 的 Samsung Galaxy S4 上的原生 Android 浏览器中的网页 在 S3 上 我只需在地址栏中输入 about debug 就会出现 但它在 S4 上不起
  • WPF 双向绑定 XML

    我正在努力掌握 WPF 更具体地说 是执行 xml 文件的双向绑定 我应该使用 XMLDataProvider 还是他们的另一个 更好 选择 数据显示正常 但当我更改条目时 更改不会反映在 xml 文件中 The XML
  • Jenkins 中颠覆轮询失败的电子邮件通知

    由于密码更改 我们在 Jenkins 中的一项工作失败了 它的颠覆轮询超过 24 小时 当这种情况发生时 并不是立即显而易见的 除非您注意到作业没有运行并实际检查日志 构建不会失败 因为它从未启动 有没有人找到解决方案来通知 Jenkins
  • Spark中如何获取数组列的所有组合?

    假设我有一个数组列group ids user id group ids 1 5 8 3 1 2 3 2 1 4 Schema root user id integer nullable false group ids array null
  • Composer 未下载包的 src 目录

    我正在使用 Laravel 和 Composer 构建一个网络应用程序 在我的本地计算机上 我在composer json 文件中设置了其要求 并且一切正常 我正在使用 Github 推送到生产服务器 但是 然后我运行composer in
  • 我能否获取 C#/WPF 中绑定对象的 Type()(即使绑定值为 null)?

    我与未知来源有绑定 我所拥有的只是绑定 我没有其他方法来查看绑定对象 我需要找出绑定对象的类型 即使该值为空 这就是我的问题所在 我通过绑定到一个对象然后使用该对象作为获取类型的方式来评估绑定 但即使该值为 null 我也需要知道该类型 例
  • 使用 pytest 时如何组织装置

    固定装置往往较小且可重复使用 鉴于特定装置可以依赖其他装置 pytest fixture def Account db memcache 我想在模块中组织我的装置 并将它们导入到特定的测试文件中 如下所示 例如 from fixtures
  • 从 XP 中的隐藏或剪切窗口复制内容?

    我需要将隐藏窗口 BitBlt 的内容复制到另一个窗口 问题是 一旦我隐藏源窗口 我得到的设备上下文就不再被绘制 你需要的是打印窗口 http msdn microsoft com en us library ms535695 aspx从
  • airodump-ng 使用 python subprocess.Popen 通信方法输出

    嗨 我正在尝试从中获得连续输出airodump ng mon0 因此 我试图读取的输出airodump ng mon0经过一段时间与 Popen communicate 但仍然无法得到任何东西 import subprocess airod
  • 如何在backbone.js中创建基本视图?

    我需要创建一个基本视图 我的所有视图都会扩展 我不太确定何时何地声明这个观点 基本上 我需要注入global variables我的所有模板 但我不会在每个模板中都这样做render 方法 这是我现在的树结构 main js app js
  • MySQL 更新查询 - 竞争条件和行锁定会遵守“where”条件吗? (PHP、PDO、MySQL、InnoDB)

    我正在尝试建立一个先到先得的模型销售页面 我们有 n 个相同类型的物品 我们希望将这 n 个项目分配给前 n 个发出请求的用户 每个项目对应有一个数据库行 当用户按下购买按钮时 系统会尝试查找尚未出售的条目 reservationCompl