mysql/innodb事务

bvjxkvbb  于 2021-06-20  发布在  Mysql
关注(0)|答案(1)|浏览(366)

我做了很多研究,发现了很多关于所有相关主题的信息。然而,我不相信我现在明白了,如何把所有这些信息正确地放在一起。
这个应用程序是用php编写的。
对于查询,我使用pdo。
mysql数据库配置为innodb。

我需要什么

SELECT ... FROM tableA;

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

条件:
插入必须是原子的。如果其中一个失败了,我想退回去。
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;
}

我觉得还有很多事情可以做得更好,但我不知道怎么做:/

为什么会这样?

如果插入失败,我需要某种事务来执行回滚。
我需要锁定tablea以确保没有其他插入或更新发生。
事务和表锁不能很好地协同工作(https://dev.mysql.com/doc/refman/8.0/en/lock-tables-and-transactions.html)
我想在应用程序的其余部分使用autocommit作为标准,这就是为什么我在最后将其设置回“1”。
我真的不确定这一点:但我在某处发现,在锁定一个表之后,我只能(从当前连接中)查询这个表,直到我解锁它(这对我来说没有意义)。这就是为什么我也锁了tableb,尽管我不需要锁。

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

在php、mysql、pdo和innodb框架条件下,我愿意接受任何建议。
谢谢您!
编辑1(2018-06-01)
我觉得我的问题需要进一步澄清。

起点:

如果有两个表,t1和t2。
t1有多列非唯一值。
t2的细节与这个问题无关。

我想做的是:

一步一步地:
从t1中选择多个列和行。
在php中分析检索到的数据。根据这一分析的结果整理出一个数据集。
将数据集的一部分插入t1,另一部分插入t2。
其他信息:
两个表中的插入必须是原子的。这可以通过事务来实现。
在步骤1和步骤3之间,不允许发生来自不同连接的插入。这一点非常重要,因为t1中的每一次插入都必须完全了解表的当前状态。我最好更详细地描述一下。为了让事情更容易理解,我暂时不谈t2。
想象一下这个事件序列(连接con1和con2):
con1:选择。。。从t1开始,其中xyz;
con1:php处理信息。
con2:选择。。。从t1开始,其中uvw;
con2:php处理信息。
con1:插入t1。。。;
con2:插入t1。。。;
所以两个连接都看到t1处于相同的状态。但是,他们选择了不同的信息。con1获取收集到的信息,对其进行一些逻辑处理,然后将数据插入t1中的新行。con2也有同样的功能,但使用的信息不同。
问题是这样的:两个连接都是基于计算插入数据的,而没有考虑到另一个连接插入t1的任何内容,因为当它们从t1读取数据时,这些信息并不存在。
con2可能在t1中插入了一行,该行符合con1的select语句的where条件。换句话说:如果con2早些时候插入了它的行,con1可能已经创建了完全不同的数据来插入t1。这就是说:这两个插入可能会使其他插入完全失效。
这就是为什么我要确保一次只有一个连接可以处理t1中的数据。在当前连接完成之前,不允许其他连接写入,但也不允许其他连接读取。
我希望这能澄清一些事情……:/

想法:

我的想法是:
我需要在两个表中插入原子。-->我将使用事务处理。像这样:

try {
  $pdo->beginTransaction();
  // INSERT INTO t1 ...
  // INSERT INTO t2 ...
  $pdo->commit();
}
catch (Exception $e) {
  $pdo->rollBack();
  throw $e;
}

我需要确保,没有其他连接写入或读取t1。这是我决定我需要锁表的地方。
假设我必须使用锁表,我会遇到锁表不支持事务的问题。所以我决定采用这里提出的解决方案(https://dev.mysql.com/doc/refman/8.0/en/lock-tables-and-transactions.html)还有关于stackoverflow的多个答案。
但我不满意代码的样子,这就是为什么我来这里问这个(同时相当长的)问题。
编辑2(2018-06-01)
此进程不会经常运行。因此,不需要高性能和高效率。当然,这也意味着其中两个过程相互推断的可能性非常小。不过,我想确保什么都不会发生。

a9wyjsp7

a9wyjsp71#

案例1:

BEGIN;
INSERT ..
INSERT ..
COMMIT;

其他连接只有在提交之后才能看到插入的行。也就是说, BEGIN...COMMIT 使两个插入“原子”。
如果什么都失败了,你仍然需要尝试/抓住来处理它。
不要使用 LOCK TABLES 在innodb表上。
别管这个了 autocommit ; BEGIN..COMMIT 覆盖它。
我的陈述适用于(可能)所有框架(但有些人没有“尝试”和“抓住”。)
案例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的内容。这是一种在封面下使用的通用技术,用于让一个连接在某个时刻看到表的值,即使其他连接正在修改表。
“防止插入”--使用mvcc,如果启动 SELECT ,这就像是在你所看到的一切的时间里得到一个快照。你不会看到 INSERTs 直到您完成交易后 SELECT 是在。你也可以吃蛋糕。也就是说,插入看起来好像被阻塞了,但是您可以从并行执行中获得性能优势。魔法。

相关问题