[英]Understanding pdo mysql transactions
PHP 文档说:
如果您以前从未接触过事务,它们提供了 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吗? 这是否意味着 1 个事务将与另一个事务重叠,因为信息是在任一提交发生之前获得的?
会是12000吗? 那么这是否意味着无论更新和选择数据的顺序如何, commit()
函数都会强制这些单独发生?
请随意详细说明事务和单独的脚本如何相互干扰(或不干扰)。
您不会在 php 文档中找到答案,因为这与 php 或 pdo 无关。
mysql中的innodb表引擎提供了4个所谓的符合sql标准的隔离级别。 隔离级别与阻塞/非阻塞读取结合将决定上述示例的结果。 您需要了解各种隔离级别的含义,并根据需要选择合适的级别。
总结一下:如果你在关闭自动提交的情况下使用可序列化隔离级别,那么结果将是 12000。在所有其他隔离级别和启用自动提交的可序列化级别中,结果将是 11000。如果你开始使用锁定读取,那么结果可能是在所有隔离级别下为 12000。
根据给定的条件(一个单独的 DML 语句)判断,这里不需要事务,而是需要表锁。 这是一个非常常见的混淆。
如果您需要确保所有 DML 语句都正确执行或根本没有执行,则需要一个事务。
手段
虽然,正如 Shadow 的优秀回答中所指出的,您可以在此处使用具有适当隔离级别的事务,但这会相当混乱。 您在这里需要的是表锁定。 InnoDB 引擎允许您锁定特定行而不是锁定整个表,因此应该是首选。
如果您希望工资为 1200 - 则使用表锁。
或者 - 一种更简单的方法 - 只需运行原子更新查询:
UPDATE employees SET salary = salary + 1000 WHERE name = ?
在这种情况下,所有工资都将被记录。
如果你的目标不同,最好明确表达出来。
但同样:您必须了解事务一般与单独的脚本执行无关。 关于您的竞争条件主题,您不是对事务感兴趣,而是对表/行锁定感兴趣。 这是一个非常常见的混淆,你最好直接学习:
事务和锁定干扰的唯一主题是死锁,但同样 - 只有在事务使用锁定的情况下。
唉,“无干扰”需要程序员的一些帮助。 它需要BEGIN
和COMMIT
来定义“事务”的范围。 还有...
你的例子是不够的。 第一条语句需要SELECT ... FOR UPDATE
。 这告诉事务处理对于SELECT
获取的行可能会有一个UPDATE
。 该警告对于“防止干扰”至关重要。 现在时间线是这样的:
FOR UPDATE
)(注意:这不是“死锁”,只是“等待”。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.