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) 的相关文章

  • 如何使PHP库松耦合? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • gd 的 php 包装类

    谁能推荐一个 gd 库的包装类 我找到了一个few http www bin co com php scripts classes gd image 但它们只具有基本的图像处理功能 例如翻转 倒转等 我真的在这里画画 所以我想要所有的线 点
  • PHP exec - 检查是否启用或禁用

    有没有办法检查 php 脚本是否exec 在服务器上启用还是禁用 这将检查该功能是否确实有效 权限 权利等 if exec echo EXEC EXEC echo exec works
  • 以阿拉伯语显示日期

    这是我的代码 setlocale LC ALL ar echo strftime e b Y strtotime 2011 10 25 Output 25 Sep 2011 为什么不显示阿拉伯日期 我是否错误地使用了 strftime 在这
  • 如何将值从 javascript 传递到 php 文件

    我通过以下方式获取价值JQuery像这样的东西 var query popURL split var dim query 1 split var popWidth dim 0 split 1 Gets the first query str
  • Selenium RC:如何检查元素是否具有给定属性?

    我有一些带有onclick属性和一些没有属性 我想检查指定的元素是否具有onclick属性 我怎样才能做到这一点 getAttribute 当有属性值时返回该属性值 如果没有 它会抛出 RuntimeException 并停止测试 即使我将
  • MySQL 相当于 ORACLES 的rank()

    Oracle 有 2 个函数 rank 和dense rank 我发现它们对于某些应用程序非常有用 我现在正在 mysql 中做一些事情 想知道他们是否有与这些相同的东西 没有什么直接等效的 但你可以用一些 不是非常有效的 自连接来伪造它
  • 如何在 PHP 的 GD 库中为文本添加发光或阴影? [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 是否可以在 GD 中为文本添加发光或
  • 用户反馈系统的正确数据库模型(一个有趣的案例)

    我正在使用 PHP 和 Yii Framework 开发一个应用程序 我一直在考虑最适合给定功能的数据库结构 这就是我的想法 但我并不是 100 肯定应该这样做 因此我决定询问社区 应用程序说明 注册用户可以参加活动 每个事件都可以有一个
  • 仅显示帖子的子类别

    我有一个自定义帖子 我想显示它所属的所有类别 这有两个部分 在页面顶部 我只显示它所属的顶级类别 这就是我的做法 div class type block span class type initial span div
  • 如果 WooCommerce 购物车商品缺货,请勿使用优惠券

    到目前为止 这就是我所得到的 add filter woocommerce coupon is valid coupon always valid 99 2 function coupon always valid valid coupon
  • PhpStorm Docker PHPUnit 数据库

    I setup https blog jetbrains com phpstorm 2016 11 docker remote interpreters PhpStorm PHP PHPUnit 与 Docker 我在 PhpStorm 数
  • 在 Windows 10 中安装 laravel 安装程序时出现错误

    我正在使用 Windows 10 并且composer已安装 当我尝试安装时laravel使用此命令全局安装程序 composer global require laravel installer Composer Downloader T
  • Wordpress Cron 错误“SSL 证书:无法获取本地颁发者证书”

    我在安装 WordPress 时遇到错误 wp cron php 无法由 WordPress 执行 调试工具 Crontrol 报告错误 SSL证书 无法获取本地颁发者证书 WGET 无法访问 wp cron php 很可能是由于 SSL
  • MagicSuggest动态ajax源码

    我在用着魔法建议 https github com nicolasbize magicsuggest对于自动完成输入文本 自动完成提要非常大 因此我无法完整下载它 在他们的示例中 他们提供了以下代码 脚本语言 document ready
  • 将数组内爆为来自 mysql 查询的逗号分隔字符串

    在过去的 1 1 2 天里 我一直在尝试将 16 行 id 存储到一个字符串中 并用逗号分隔每个 id 我得到的数组来自 MySQL 我得到的错误是 implode 函数 传递了无效参数 str array string while row
  • 如何在应用程序级别管理只读数据库连接

    我们使用的是Java Spring Ibatis MySql 有没有办法利用这些技术在应用程序级别管理只读连接 我希望在只读 MySql 用户的基础上添加额外的保护层 如果 BasicDataSource 或 SqlMapClientTem
  • Laravel项目部署到Cpanel时出现404错误如何解决?

    我正在尝试将我的 laravel Laravel Framework 7 28 3 部署到 Cpanel 但出现 404 错误 我将项目上传到 public html 修改了 index php 文件以指向正确的文件 如下所示 我认为ind
  • 发送 QUERY 数据包时出错。 PID=9565

    我有两个不同的环境开发和生活几乎都是相同的 但上述 标题中 警告仅在开发模式下发生 在此警告之前 我还收到错误消息 允许的内存大小 268435456 字节已耗尽 这仅发生在开发模式下 使用 PHP 版本 5 6 和 mysql 不是 my
  • 现实世界抽象类使用简单示例

    有没有使用抽象类的现实世界简单示例 我试图进入 PHP 的 OOP 但我仍然无法理解 为什么应该使用抽象类以及何时使用 是的 我知道不可能创建抽象类实例 只能创建继承它的类的实例 也许您有一个图像类 并且有 2 个驱动程序 GD 和 Ima

随机推荐

  • 获取带有泛型的 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