【问题标题】:MySQL/InnoDB transactions with table locks in InnoDB在 InnoDB 中具有表锁的 MySQL/InnoDB 事务
【发布时间】:2018-05-31 07:36:44
【问题描述】:

我做了很多研究,发现了很多关于所有相关主题的信息。但是,我不确定我现在是否了解如何将所有这些信息正确地组合在一起。

这个应用程序是用 PHP 编写的。

对于查询,我使用 PDO。

MySQL 数据库配置为 InnoDB。

我需要什么

SELECT ... FROM tableA;

// PHP looks at what comes back and does some logic.
INSERT INTO tableA ...;
INSERT INTO tableB ...;

条件:

  • INSERT 必须是原子的。如果其中一个失败,我想回滚。
  • 不允许在 SELECT 和 INSERT from/into tableA 之间发生对 tableA 的读取和写入。

在我看来这是一个非常简单的问题。但是我无法弄清楚如何正确地做到这一点。所以我的问题是:

最好的方法是什么?

这是我当前计划的大纲,非常简化:

try {
  SET autocommit = 0;

  BLOCK TABLES tableA WRITE, tableB WRITE;

  SELECT ... FROM tableA;

  INSERT INTO tableA ...;
  INSERT INTO tableB ...;

  COMMIT;

  UNLOCK TABLES;

  SET autocommit = 1;
}

catch {
  ROLLBACK;

  UNLOCK TABLES;

  SET autocommit = 1;
}     

我觉得有很多地方可以做得更好,但我不知道如何:/

为什么会这样?

  • 如果 INSERT 失败,我需要某种事务来回滚。
  • 我需要锁定 tableA 以确保没有其他 INSERT 或 UPDATE 发生。
  • 事务和表锁不能很好地协同工作 (https://dev.mysql.com/doc/refman/8.0/en/lock-tables-and-transactions.html)
  • 我想在我的应用程序的其余部分使用自动提交作为标准,这就是我在最后将其设置回“1”的原因。
  • 对此我真的不确定:但我在某处发现,在锁定表后,我只能(从当前连接中)查询该表,直到我解锁它(这对我来说没有意义) .这也是我锁定 tableB 的原因,尽管否则我不需要。

我对完全不同的方法持开放态度

我愿意接受 PHP、MySQL、PDO 和 InnoDB 框架条件下的任何建议。

谢谢!


编辑 1 (2018-06-01)

我觉得我的问题/疑问需要进一步澄清。

起点:

如果有两个表,t1t2

t1 有多列非唯一值。

t2 的细节与这个问题无关。

我想做什么:

一步一步:

  1. t1中选择多列和多行。

  2. 在 PHP 中分析检索到的数据。根据该分析的结果整理出一个数据集。

  3. 将部分数据集插入 t1,将部分数据插入 t2

附加信息:

  • 到 2 个表中的 INSERT 必须是原子的。这可以通过交易来实现。
  • 在第 1 步和第 3 步之间不允许发生来自不同连接的 INSERT。这非常重要,因为每一次 INSERT 到 t1 都必须在完全了解表的当前状态的情况下发生.我最好更详细地描述这一点。我暂时将 t2 排除在外,以使事情更容易理解。

    想象一下这一系列事件(连接 con1 和 con2):

    1. con1: SELECT ... FROM t1 WHERE xyz;
    2. con1:PHP 处理信息。
    3. con2: SELECT ... FROM t1 WHERE uvw;
    4. con2:PHP 处理信息。
    5. con1: 插入 t1 ...;
    6. con2: 插入到 t1 ...;

    所以两个连接都看到 t1 处于相同的状态。但是,他们选择不同的信息。 Con1 获取收集到的信息,对其进行一些逻辑处理,然后将数据插入到 t1 中的新行中。 Con2 做同样的事情,但使用不同的信息。

    问题是这样的:两个连接插入的数据基于没有考虑到任何其他连接插入到 t1 的计算,因为当他们从 读取时,这些信息不存在t1.

    Con2 可能在 t1 中插入了一行,该行将满足 con1 的 SELECT 语句的 WHERE 条件。换句话说:如果 con2 之前插入了它的行,con1 可能已经创建了完全不同的数据来插入 t1。这就是说:这两个 INSERT 可能已经完全使彼此的插入无效。

    这就是为什么我要确保一次只有一个连接可以处理 t1 中的数据。在当前连接完成之前,不允许其他连接写入,也不允许其他连接读取。

    我希望这可以澄清一些事情......:/

想法:

我的想法是:

  1. 我需要将 2 个表中的 INSERT 设置为原子的。 --> 我将为此使用事务。像这样的:

    try {
      $pdo->beginTransaction();
      // INSERT INTO t1 ...
      // INSERT INTO t2 ...
      $pdo->commit();
    }
    catch (Exception $e) {
      $pdo->rollBack();
      throw $e;
    }
    
  2. 我需要确保没有其他连接写入或读取 t1。这就是我决定需要 LOCK TABLES 的地方。

  3. 假设我必须使用 LOCK TABLES,我遇到了 LOCK TABLES 不支持事务的问题。这就是为什么我决定采用此处提出的解决方案 (https://dev.mysql.com/doc/refman/8.0/en/lock-tables-and-transactions.html) 以及 stackoverflow 上的多个答案。

但我对代码的外观不满意,这就是我来这里问这个(同时相当冗长)问题的原因。


编辑 2 (2018-06-01)

此进程不会经常运行。因此,对高性能和效率没有太大的需求。当然,这也意味着其中两个过程相互推断的机会非常小。不过,我想确保不会发生任何事情。

【问题讨论】:

  • 你不是说B和C吗? SELECT from A.. INSERT INTO A 有点毫无意义。
  • 不,我的意思是 SELECT from A.. INSERT INTO A. 这些是步骤: (1.) SELECT from A. (2.) PHP 查看结果,执行一些逻辑,将一起插入数据。 (3.) INSERT INTO A. 为什么这毫无意义?我错过了什么?
  • 也许我最初的问题不清楚。我重写了我的这部分问题,以减少歧义。

标签: php mysql pdo locking innodb


【解决方案1】:

案例 1:

BEGIN;
INSERT ..
INSERT ..
COMMIT;

在提交之前,其他连接不会看到插入的行。也就是说,BEGIN...COMMIT 使这两个插入“原子”。

如果有任何失败,你仍然需要 try/catch 来处理它。

不要在 InnoDB 表上使用LOCK TABLES

不要打扰autocommitBEGIN..COMMIT 覆盖它。

我的陈述适用于(可能)所有框架。 (除了有些没有“try”和“catch”。)

案例 2:锁定一行以预期可能会对其进行修改:

BEGIN;
SELECT ... FROM t1 FOR UPDATE;
... work with the values SELECTed
UPDATE t1 ...;
COMMIT;

这使其他人远离行SELECTed,直到COMMIT之后。

案例 3:有时 IODKU 可用于在单个原子语句中完成两件事:

INSERT ...
    ON DUPLICATE KEY UPDATE ...

而不是

BEGIN;
SELECT ... FOR UPDATE;
if no row found
    INSERT ...;
else
    UPDATE ...;
COMMIT;

第 4 类:经典银行业务示例:

BEGIN;
UPDATE accounts SET balance = balance - 1000.00 WHERE id='me';
... What if crash occurs here? ...
UPDATE accounts SET balance = balance + 1000.00 WHERE id='you';
COMMIT;

如果系统在两个UPDATEs 之间崩溃,第一次更新将被撤消。这可以防止系统丢失资金转移的跟踪。

案例 5: 可能接近 OP 想要的。它主要是案例2和1的组合。

BEGIN;
SELECT ... FROM t1 FOR UPDATE;   -- see note below
... work with the values SELECTed
INSERT INTO t1 ...;
COMMIT;

案例 5 的注意事项:SELECT..FOR UPDATE 必须包含您不希望其他连接看到的任何行。这具有将另一个连接延迟到此连接COMMITs 的效果。 (是的,这感觉很像LOCK TABLES t1 WRITE。)

案例 6: 需要在 BEGIN..COMMIT 中的“处理”将花费太长时间。 (例如:典型的在线购物车。)

这需要 InnoDB 事务之外的锁定机制。一种方法(对购物车有用)是在一些额外的表格中使用一行,并让每个人都检查它。另一种方法(在单个连接中更实用)是使用 GET_LOCK('foo') 和它的朋友。

一般性讨论

以上所有示例都只锁定所涉及的行,而不是整个表。这使得操作的侵入性大大降低,并允许系统处理更多的活动。

另外,请阅读 MVCC。这是一种在幕后使用的通用技术,让一个连接在某个时刻看到表的值,即使在其他连接正在修改表时

"Prevent inserts" -- 使用 MVCC,如果您启动 SELECT,这就像获取您正在查看的所有内容的快照一样。在完成SELECT 所在的交易之前,您不会看到INSERTs。您也可以吃蛋糕。也就是说,看起来好像插入被阻塞了,但您会获得并行发生的性能优势。魔法。

【讨论】:

  • 谢谢!但是,恐怕我看不出这如何解决我的问题:/这如何确保在从 tableA 的 SELECT 和 INSERT 之间不会发生插入?我知道如何处理交易——这不是我的问题。我的问题是,如何同时实现:(1)原子插入(-->事务)和(2)防止从不同的连接插入tableA(-->表锁[?])。而且由于表锁不支持事务,所以我选择了自动提交(在 MySQL 文档中建议 - 请参阅我的问题中的链接)。
  • 另外,您能否详细说明为什么应避免在 InnoDB 表上使用 LOCK TABLES?并且:有没有办法在不使用 LOCK TABLES 的情况下防止插入表?
  • @wire417 - 我添加了更多案例,并简要概述了 MVCC,这可能是您缺少的线索。
  • 我真的非常感谢您在回答中付出的时间和精力!但是,我仍然觉得我的问题没有解决。我觉得这是两件事之一:我误解了问题或我没有看到非常明显的东西。我更新了我的原始问题,以提供有关该问题和我的想法的更多详细信息。我还将在这里评论为什么我认为您提供的案例不能解决我的问题。感谢您为此付出的所有时间!!!
  • 案例 2:我可以看到这有 2 个问题。 (1) 我不能使用 FOR UPDATE 锁定尚不存在的行。这意味着:我不能用它来防止来自不同连接的 INSERT。 (2) 即使我可以使用它来防止插入,这个其他连接(con2)仍然可以从表中的其他行(未被 con1 锁定)和稍后(在所有相关锁被释放后)读取信息插入数据基于此信息。但是,如果 con1 有数据,它可能刚刚插入的数据会改变 con2 的计算结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-18
  • 1970-01-01
相关资源
最近更新 更多