InnoDB 中 MySQL/InnoDB 个具有 table 个锁的事务
MySQL/InnoDB transactions with table locks in InnoDB
我做了很多研究,发现了很多关于所有相关主题的信息。但是,我不确定我现在是否理解如何将所有这些信息正确地放在一起。
此应用程序是在 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 之间发生读取和写入 from/to 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 以确保不会发生其他插入或更新。
- 事务和 table 锁不能很好地协同工作
(https://dev.mysql.com/doc/refman/8.0/en/lock-tables-and-transactions.html)
- 我想在我的应用程序的其余部分中使用自动提交作为标准,这就是我在最后将其设置回“1”的原因。
- 我真的不确定:但我在某处发现,在锁定 table 之后,我可以(从当前连接中)仅查询此 table 直到我解锁它(这对我来说没有意义)。这也是我锁定 tableB 的原因,否则我不需要。
我对完全不同的方法持开放态度
我愿意接受框架条件 PHP、MySQL、PDO 和 InnoDB 的任何建议。
谢谢!
编辑 1 (2018-06-01)
我觉得我的 problem/question 需要更多说明。
起点:
如果有两个table,t1和t2.
t1 有多列非唯一值。
t2 的细节与此问题无关。
我想做的事情:
一步一步:
Select 来自 t1 的多个列和行。
在PHP中分析检索到的数据。根据这个分析的结果整理了一个数据集。
将部分数据集插入 t1 并将其部分插入 t2.
附加信息:
- 2 table 中的 INSERT 必须是原子的。这可以使用事务来实现。
不允许在第 1 步和第 3 步之间发生来自不同连接的 INSERT。这非常重要,因为每个 INSERT 到 t1 都必须发生充分了解 table 的当前状态。我最好更详细地描述这一点。我暂时将 t2 排除在外,以使事情更容易理解。
想象一下这一系列事件(连接 con1 和 con2):
- con1:SELECT ...从 t1 开始,其中 xyz;
- con1: PHP 处理信息。
- con2:SELECT ...从 t1 开始,uvw;
- con2: PHP 处理信息。
- con1:插入 t1 ...;
- con2:插入 t1 ...;
所以两个连接看到 t1 处于相同的状态。然而,他们select 不同的信息。 Con1 获取收集到的信息,对其进行一些逻辑处理,然后将数据插入到 t1 中的新行中。 Con2 做同样的事情,但使用不同的信息。
问题是这样的:两个连接都根据计算插入了数据,这些计算没有考虑其他连接插入 t1 的任何内容,因为当它们插入时此信息不存在从 t1.
读取
Con2 可能已将一行插入到 t1 中,该行将满足 con1[= 的 WHERE 条件163=] 的 SELECT-语句。换句话说:如果 con2 更早插入它的行,con1 可能会创建完全不同的数据以插入到 t1.这就是说:两个 INSERT 可能已经完全使彼此的插入无效。
这就是为什么我要确保一次只有一个连接可以处理 t1 中的数据。在当前连接完成之前,不允许其他连接写入,也不允许其他连接读取。
我希望这能澄清一些事情...:/
想法:
我的想法是:
我需要将 INSERT 插入 2 tables 原子。 --> 我将为此使用事务。像这样:
try {
$pdo->beginTransaction();
// INSERT INTO t1 ...
// INSERT INTO t2 ...
$pdo->commit();
}
catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
我需要确保没有其他连接写入或读取 t1。这是我决定需要锁表的地方。
- 假设我必须使用 LOCK TABLES,我遇到了 LOCK TABLES 不是事务感知的问题。这就是为什么我决定采用此处提出的解决方案 (https://dev.mysql.com/doc/refman/8.0/en/lock-tables-and-transactions.html) 以及 Whosebug 上的多个答案。
但我对代码的外观不满意,这就是为什么我来这里问这个(同时相当冗长)问题的原因。
编辑 2 (2018-06-01)
这个过程不会 运行 经常发生。所以对高性能和效率没有太大的需求。当然,这也意味着其中两个进程相互推断的可能性很小。不过,我想确保不会发生任何事情。
案例一:
BEGIN;
INSERT ..
INSERT ..
COMMIT;
在提交之前,其他连接将看不到插入的行。也就是说,BEGIN...COMMIT
做了两个插入 "atomic".
万一失败,还需要try/catch来处理。
不要在 InnoDB table 上使用 LOCK TABLES
。
不用理会autocommit
; BEGIN..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;
Class 4:Classic 银行示例:
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 中的 "processing" 将花费太长时间。 (示例:典型的在线购物车。)
这需要 InnoDB 事务之外的锁定机制。一种方法(对购物车有用)是在一些额外的 table 中使用一行,让每个人都检查一下。另一种方法(在单个连接中更实用)是使用 GET_LOCK('foo')
和它的朋友。
一般性讨论
以上所有示例仅锁定涉及的行,而不是整个 table(s)。这使得操作的侵入性大大降低,并允许系统处理更多 activity.
另外,阅读有关 MVCC 的内容。这是一种在幕后使用的通用技术,让一个连接在某个时刻及时看到 table(s) 的值,即使其他连接正在修改 table( s).
"Prevent inserts" -- 使用 MVCC,如果您启动 SELECT
,就好像及时获取您正在查看的所有内容的快照。在完成 SELECT
所在的事务之前,您不会看到 INSERTs
。您也可以吃蛋糕。也就是说,看起来好像插入被阻塞了,但是您可以获得它们并行发生的性能优势。魔法.
我做了很多研究,发现了很多关于所有相关主题的信息。但是,我不确定我现在是否理解如何将所有这些信息正确地放在一起。
此应用程序是在 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 之间发生读取和写入 from/to 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 以确保不会发生其他插入或更新。
- 事务和 table 锁不能很好地协同工作 (https://dev.mysql.com/doc/refman/8.0/en/lock-tables-and-transactions.html)
- 我想在我的应用程序的其余部分中使用自动提交作为标准,这就是我在最后将其设置回“1”的原因。
- 我真的不确定:但我在某处发现,在锁定 table 之后,我可以(从当前连接中)仅查询此 table 直到我解锁它(这对我来说没有意义)。这也是我锁定 tableB 的原因,否则我不需要。
我对完全不同的方法持开放态度
我愿意接受框架条件 PHP、MySQL、PDO 和 InnoDB 的任何建议。
谢谢!
编辑 1 (2018-06-01)
我觉得我的 problem/question 需要更多说明。
起点:
如果有两个table,t1和t2.
t1 有多列非唯一值。
t2 的细节与此问题无关。
我想做的事情:
一步一步:
Select 来自 t1 的多个列和行。
在PHP中分析检索到的数据。根据这个分析的结果整理了一个数据集。
将部分数据集插入 t1 并将其部分插入 t2.
附加信息:
- 2 table 中的 INSERT 必须是原子的。这可以使用事务来实现。
不允许在第 1 步和第 3 步之间发生来自不同连接的 INSERT。这非常重要,因为每个 INSERT 到 t1 都必须发生充分了解 table 的当前状态。我最好更详细地描述这一点。我暂时将 t2 排除在外,以使事情更容易理解。
想象一下这一系列事件(连接 con1 和 con2):
- con1:SELECT ...从 t1 开始,其中 xyz;
- con1: PHP 处理信息。
- con2:SELECT ...从 t1 开始,uvw;
- con2: PHP 处理信息。
- con1:插入 t1 ...;
- con2:插入 t1 ...;
所以两个连接看到 t1 处于相同的状态。然而,他们select 不同的信息。 Con1 获取收集到的信息,对其进行一些逻辑处理,然后将数据插入到 t1 中的新行中。 Con2 做同样的事情,但使用不同的信息。
问题是这样的:两个连接都根据计算插入了数据,这些计算没有考虑其他连接插入 t1 的任何内容,因为当它们插入时此信息不存在从 t1.
读取Con2 可能已将一行插入到 t1 中,该行将满足 con1[= 的 WHERE 条件163=] 的 SELECT-语句。换句话说:如果 con2 更早插入它的行,con1 可能会创建完全不同的数据以插入到 t1.这就是说:两个 INSERT 可能已经完全使彼此的插入无效。
这就是为什么我要确保一次只有一个连接可以处理 t1 中的数据。在当前连接完成之前,不允许其他连接写入,也不允许其他连接读取。
我希望这能澄清一些事情...:/
想法:
我的想法是:
我需要将 INSERT 插入 2 tables 原子。 --> 我将为此使用事务。像这样:
try { $pdo->beginTransaction(); // INSERT INTO t1 ... // INSERT INTO t2 ... $pdo->commit(); } catch (Exception $e) { $pdo->rollBack(); throw $e; }
我需要确保没有其他连接写入或读取 t1。这是我决定需要锁表的地方。
- 假设我必须使用 LOCK TABLES,我遇到了 LOCK TABLES 不是事务感知的问题。这就是为什么我决定采用此处提出的解决方案 (https://dev.mysql.com/doc/refman/8.0/en/lock-tables-and-transactions.html) 以及 Whosebug 上的多个答案。
但我对代码的外观不满意,这就是为什么我来这里问这个(同时相当冗长)问题的原因。
编辑 2 (2018-06-01)
这个过程不会 运行 经常发生。所以对高性能和效率没有太大的需求。当然,这也意味着其中两个进程相互推断的可能性很小。不过,我想确保不会发生任何事情。
案例一:
BEGIN;
INSERT ..
INSERT ..
COMMIT;
在提交之前,其他连接将看不到插入的行。也就是说,BEGIN...COMMIT
做了两个插入 "atomic".
万一失败,还需要try/catch来处理。
不要在 InnoDB table 上使用 LOCK TABLES
。
不用理会autocommit
; BEGIN..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;
Class 4:Classic 银行示例:
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 中的 "processing" 将花费太长时间。 (示例:典型的在线购物车。)
这需要 InnoDB 事务之外的锁定机制。一种方法(对购物车有用)是在一些额外的 table 中使用一行,让每个人都检查一下。另一种方法(在单个连接中更实用)是使用 GET_LOCK('foo')
和它的朋友。
一般性讨论
以上所有示例仅锁定涉及的行,而不是整个 table(s)。这使得操作的侵入性大大降低,并允许系统处理更多 activity.
另外,阅读有关 MVCC 的内容。这是一种在幕后使用的通用技术,让一个连接在某个时刻及时看到 table(s) 的值,即使其他连接正在修改 table( s).
"Prevent inserts" -- 使用 MVCC,如果您启动 SELECT
,就好像及时获取您正在查看的所有内容的快照。在完成 SELECT
所在的事务之前,您不会看到 INSERTs
。您也可以吃蛋糕。也就是说,看起来好像插入被阻塞了,但是您可以获得它们并行发生的性能优势。魔法.