了解 pdo mysql 事务

Posted

技术标签:

【中文标题】了解 pdo mysql 事务【英文标题】:Understanding pdo mysql transactions 【发布时间】:2016-09-30 03:21:09 【问题描述】:

php Documentation 说:

如果您以前从未遇到过交易,他们提供 4 个主要 特点:原子性、一致性、隔离性和持久性(ACID)。在 外行的术语,在交易中进行的任何工作,即使它是 分阶段进行,保证应用于数据库 安全,并且不受其他连接的干扰,当它是 承诺。

问题:

这是否意味着我可以让两个单独的 php 脚本同时运行事务而不会相互干扰?


详细说明我的意思干扰

假设我们有以下employees 表:

 __________________________
|  id  |  name  |  salary  |
|------+--------+----------|
|  1   |  ana   |   10000  |
|------+--------+----------|

如果我有两个代码相似/相同的脚本并且它们同时运行:

script1.phpscript2.php(都有相同的代码):

$conn->beginTransaction();

$stmt = $conn->prepare("SELECT * FROM employees WHERE name = ?");
$stmt->execute(['ana']);
$row = $stmt->fetch(PDO::FETCH_ASSOC);

$salary = $row['salary'];
$salary = $salary + 1000;//increasing salary

$stmt = $conn->prepare("UPDATE employees SET salary = $salary WHERE name = ?");
$stmt->execute(['ana']);

$conn->commit(); 

并假设事件的顺序如下:

script1.php 选择数据

script2.php 选择数据

script1.php更新数据

script2.php更新数据

script1.php commit() 发生

script2.php commit() 发生

在这种情况下,ana 的最终工资是多少?

会是 11000 吗?那么这是否意味着一个事务将与另一个事务重叠,因为信息是在任一提交发生之前获得的?

会是 12000 吗?这是否意味着无论数据更新和选择的顺序如何,commit() 函数都会强制这些单独发生?

请随意详细说明事务和单独的脚本如何相互干扰(或不干扰)。

【问题讨论】:

我很确定其中一个脚本会弹出SQLSTATE[23000]: Integrity constraint violation 错误。问题是如果它们同时运行,是哪一个。 【参考方案1】:

你不会在 php 文档中找到答案,因为这与 php 或 pdo 无关。

mysql 中的 Innodb 表引擎提供了 4 个所谓的isolation levels 符合 sql 标准。与阻塞/非阻塞读取结合的隔离级别将决定上述示例的结果。您需要了解各种隔离级别的含义并根据您的需要选择合适的级别。

总结一下:如果您使用可串行化隔离级别并关闭自动提交,则结果将为 12000。在所有其他隔离级别和可串行化并启用自动提交时,结果将为 11000。如果您开始使用锁定读取,那么在所有隔离级别下,结果可能是 12000。

【讨论】:

【参考方案2】:

根据给定的条件(单独的 DML 语句)判断,这里不需要事务,而是表锁。这是一个很常见的混淆。

如果您需要确保您的所有 DML 语句都正确执行或根本没有执行,您需要一个事务。

意思

您不需要任何数量的 SELECT 查询的事务 如果只执行一个 DML 语句,则不需要事务

尽管正如 Shadow 的出色回答中指出的那样,您可能在此处使用具有适当隔离级别的事务,但这会相当混乱。您需要的是 table locking。 InnoDB 引擎让您lock particular rows 而不是锁定整个表,因此应该是首选。

如果您希望薪水为 1200 - 然后使用表锁。

或者 - 一种更简单的方法 - 只需运行 atomic 更新查询:

UPDATE employees SET salary = salary + 1000 WHERE name = ?

在这种情况下,所有工资都将被记录。

如果你的目标不同,最好明确表达。

但同样:您必须了解,一般事务与单独的脚本执行无关。关于您的竞态条件主题,您不是对事务感兴趣,而是对事务感兴趣在表/行锁定中。这是一个很常见的混淆,你最好直接了解它:

事务是为了确保一个脚本中的一组 DML查询成功执行。 表/行锁定是为了确保其他脚本执行不会干扰。

事务和锁定干扰的唯一主题是deadlock,但同样 - 仅在事务使用锁定的情况下。

【讨论】:

我认为这个问题旨在理解来自 ACID 的字母 I,而不是以特定方式执行特定操作。至少我是这样解释的。我不同意你写的关于表锁定的内容。在 innodb 中,您可以使用 select 语句进行行级锁定,因此您不必锁定整个表以最终获得 12000。我同意在上面的代码中不需要选择,因为整个操作可以在一次更新中执行。但是,如果这只是一个旨在理解事务隔离的示例,那么它就可以达到目的。 我使用“表锁定”作为一个通用术语,同时注意到使用 innodb 应该首选行级锁定。因此,我认为您的评论是一个术语问题。在问题含义方面,我可能是理解问题方面最好的人,已经回答了 15 年。 ACID 问题作为 OP 的 XY 问题,他们刚刚在研究他们的特定问题时遇到了这个问题。无论哪种方式,我认为我们的两个答案都相互印证,对读者来说更好。【参考方案3】:

唉,“无干扰”需要程序员的帮助。它需要BEGINCOMMIT 来定义“事务”的范围。还有……

你的例子不充分。第一条语句需要SELECT ... FOR UPDATE。这告诉事务处理,SELECT 获取的行可能会出现UPDATE。该警告对于“防止干扰”至关重要。现在时间线如下:

script1.php 开始 script2.php 开始 script1.php 选择数据 (FOR UPDATE) script2.php 选择数据被阻塞,所以等待 script1.php 更新数据 script1.php commit() 发生 script2.php 选择数据(并将获取新提交的值) script2.php 更新数据 script2.php commit() 发生

(注意:这不是“死锁”,只是“等待”。)

【讨论】:

以上是关于了解 pdo mysql 事务的主要内容,如果未能解决你的问题,请参考以下文章

PHP中使用PDO操作事务的一些小测试

PDO 事务函数与 MySQL 事务语句?

PDO类基本应用二

PHP PDO MySQL 以及它如何真正处理 MySQL 事务?

PDO事务处理

事务、存储过程和 PDO