【问题标题】:Why does this select for update example work?为什么这个选择更新示例有效?
【发布时间】:2013-07-23 17:16:36
【问题描述】:

我正在运行一些简单的脚本来测试我正在解决的完整性问题的可能解决方案。假设我有一张桌子my_table

|foo     |
|1       |

我有这两个sn-ps:

// db_slow.php
<?php
$db = new PDO('mysql:host=localhost;dbname=my_playground;charset=utf8', 'root', '');
echo 'starting transaction<br />';
$db->beginTransaction();
$stmt = $db->query('select * from my_table for update');
$rows = $stmt->fetchAll();
echo 'count tables: ', count($rows), '<br />';
if (count($rows) == 1) {
    sleep(10);
    $db->query('insert into my_table(foo) VALUES(2)');
}
$db->commit();
echo 'done';

// db_fast.php
<?php
$db = new PDO('mysql:host=localhost;dbname=my_plyaground;charset=utf8', 'root', '');
echo 'starting transaction<br />';
$db->beginTransaction();
$stmt = $db->query('select * from my_table for update');
$rows = $stmt->fetchAll();
echo 'count tables: ', count($rows), '<br />';
if (count($rows) == 1) {
    $db->query('insert into my_table(foo) VALUES(3)');
}
$db->commit();
echo 'done';

db_slow.php 有 10 秒的延迟来模拟竞态条件。

据我了解,select ... for update 会锁定它选择的所有行。如果我运行db_slow,那么db_fastdb_fast 也会有10 秒的延迟,因为它正在等待db_slow,正如我所料。

但是,我没有得到的是输出:

// db_slow.php
starting transaction
count tables: 1
done

// db_fast.php
starting transaction
count tables: 2
done

还有my_table

|foo      |
|1        |
|2        |

据我了解,select ... for update 锁定了为该事务选择的所有行。所以这就是我所期望的:

  1. db_slow:选择第 1 行并锁定它
  2. db_slow: 看到只有 1 行,等待
  3. db_fast:尝试选择第1行,看到它被锁定,等待
  4. db_slow: 用 '2' 插入行
  5. db_fast:继续,因为第 1 行已解锁
  6. db_fast:只选择了 1 行,所以插入了 '3'
  7. foo: 1, 2, 3 结尾

上面描述的输出和延迟似乎确认了步骤1、2、3、4。似乎db_fast在尝试获取锁后正在运行select?我以为它会选择一行,然后锁定或等待。


有点相关的问题:

当我使用select ... lock in share mode 运行它时,我最终会得到

// db_slow.php
starting transaction
count tables: 1
done

// db_fast.php
starting transaction
count tables: 1
done

还有my_table

|foo      |
|1        |
|3        |

为什么db_slow 认为表中只有 1 行(插入行的条件)却不插入行 '2'?

【问题讨论】:

  • for update 应该在您的代码中某处吗?
  • 一个连接到 my_playground,另一个连接到 adrian_playground_alpha - 错字?
  • @sgroves 废话,之前尝试过lock in share mode,但忘了改回来。这很尴尬,但是当我将其更改为 for update 时,也会发生类似的事情。 @Alden 抱歉修复了它,它们在同一个数据库上运行,有多个脚本并忘记修复粘贴在这里的所有不一致
  • 嗯,这很有趣,我希望输出为 1、2,而不仅仅是 1、3。SELECT ... FOR UPDATE 从读取到慢速完成,速度很快,所以应该看到计数为 2。
  • 关于共享模式的问题,您是否遇到任何错误?从您的输出来看,两个脚本似乎都只计算了 1 行。他们应该都发送了他们的插入语句。其他东西正在回滚 db_slow 的更改。应该有错误信息。

标签: php mysql sql transactions data-integrity


【解决方案1】:

我认为预期的行为有点偏离。在 db_slow 提交之前,表中的所有行都被锁定。提交后,有两行。当 db_slow 提交时,db_fast 被解除阻塞。因此,行为是:

  1. db_slow:选择第 1 行并锁定它
  2. db_slow: 看到只有 1 行,等待
  3. db_fast:尝试选择第1行,看到它被锁定,等待
  4. db_slow: 用 '2' 插入行
  5. db_slow:提交
  6. db_fast:畅通并读取 2 行
  7. db_fast:什么都不做
  8. 以 foo 结尾:1, 2

【讨论】:

  • 关于步骤 3 和 6;所以如果它无法获得锁,它会在等待获得锁后重新运行整个查询吗?我希望它只是为了确保它具有最初预期的行的更新值。但如果是这样的话,这是有道理的
  • @Raekye 查询在等待时被阻止执行。实际行为不是它重新运行整个查询,而是它继续原始查询。 SQL 是一种声明性语言,而不是命令性语言。您不能将表格分成几部分。在正常(未读未提交)情况下,您不会得到诸如“更新了最初预期的行的值”之类的行为。 SQL 的重点是整个事务是原子的。它不会是原子的,它只会读取它预期应该存在的内容。
  • @Raekye 我发现更容易推断事务隔离级别可序列化。虽然含义有所不同,但在这种情况下,没有区别,所以让我们假设它是可序列化的。在可序列化中,存在一个顺序,即在第二个查询开始之前先完成一个查询。在您的情况下,行为是 db_slow 在 db_fast 启动之前完全提交。
  • 好的,感谢您的所有解释 - 我一定会阅读
  • @Raekye 很高兴知道您正在尝试了解为什么这些东西有效,而不是仅仅对您的所有查询进行 SERIALIZABLE。 =)
猜你喜欢
  • 2012-06-23
  • 1970-01-01
  • 2014-12-31
  • 2020-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多