了解 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.php 和 script2.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】:唉,“无干扰”需要程序员的帮助。它需要BEGIN
和COMMIT
来定义“事务”的范围。还有……
你的例子不充分。第一条语句需要SELECT ... FOR UPDATE
。这告诉事务处理,SELECT
获取的行可能会出现UPDATE
。该警告对于“防止干扰”至关重要。现在时间线如下:
FOR UPDATE
)
script2.php 选择数据被阻塞,所以等待
script1.php 更新数据
script1.php commit() 发生
script2.php 选择数据(并将获取新提交的值)
script2.php 更新数据
script2.php commit() 发生
(注意:这不是“死锁”,只是“等待”。)
【讨论】:
以上是关于了解 pdo mysql 事务的主要内容,如果未能解决你的问题,请参考以下文章