MariaDB Galera 集群:INSERT ... SELECT 锁定损坏?
MariaDB Galera Cluster: INSERT ... SELECT locking broken?
我发现常规 MariaDB 安装和 MariaDB Galera 集群之间的行为不一致。对于 Galera 集群的 INSERT ... SELECT 语句,锁定无法按预期工作。这会导致我们的应用程序中出现重复的 ID。
所有连接都使用隔离级别 REPEATABLE-READ(默认),通过此查询验证:
SELECT * FROM information_schema.session_variables WHERE variable_name = 'tx_isolation';
测试设置:
CREATE TABLE `TestTab` (`id` int(10) unsigned NOT NULL, `name` varchar(100) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;
预期行为:两个并行连接应该在 'id' 字段中创建两个具有两个不同值的记录。
演示常规 MariaDB 安装(非集群)的预期行为的命令序列:
Connection 1 Connection 2
SET autocommit=0;
START TRANSACTION;
INSERT INTO TestTab (`id`, `name`)
SELECT IFNULL(max(id)+1, 1), 'insert 1' FROM TestTab Limit 0,1;
SET autocommit=0;
START TRANSACTION;
INSERT INTO TestTab (`id`, `name`)
SELECT IFNULL(max(id)+1, 1), 'insert 2' FROM TestTab Limit 0,1;
-- *** GOOD *** INSERT blocks (ensures repeatable read for the SELECT)
COMMIT;
-- only now connection 2 completes the the INSERT
COMMIT;
SELECT * FROM TestTab;
SELECT * FROM TestTab;
+----+----------+
| id | name |
+----+----------+
| 1 | insert 1 |
| 2 | insert 2 |
+----+----------+
演示 MariaDB Galera 集群问题的命令序列:
Connection 1 Connection 2
SET autocommit=0;
START TRANSACTION;
INSERT INTO TestTab (`id`, `name`)
SELECT IFNULL(max(id)+1, 1), 'insert 1' FROM TestTab Limit 0,1;
SET autocommit=0;
START TRANSACTION;
INSERT INTO TestTab (`id`, `name`)
SELECT IFNULL(max(id)+1, 1), 'insert 2' FROM TestTab Limit 0,1;
-- *** BAD *** INSERT completes immediately, ignoring connection 1
COMMIT;
COMMIT;
SELECT * FROM TestTab;
SELECT * FROM TestTab;
SELECT * FROM TestTab;
+----+----------+
| id | name |
+----+----------+
| 1 | insert 1 |
| 1 | insert 2 | -- !!! same id for both records !!! --
+----+----------+
显然,Galera 集群的问题在于两条记录在 'id' 字段中获得相同的值。 在 Galera 集群上,INSERT ... SELECT 生成唯一 ID 的模式已损坏。
对我来说,这看起来像是一个错误,或者至少是非常不受欢迎和意外的行为。
是否可以通过 Galera 集群的不同配置来解决这个问题?
我尝试将两个会话的隔离级别提高到 SERIALIZABLE,这解决了问题。但这是一个非常不受欢迎的解决方法,性能对我们来说很重要。看来我还是很幸运:SERIALIZABLE 隔离级别仅在同一节点上发出的事务之间受到尊重,因此应该避免。 (quote from the Galera Cluster documentation)
您能想到什么更好的解决方法?
我应该尝试将此报告为 MariaDB 的错误吗?
请注意,Galera 在 COMMIT
之前不会与其他节点签入。同时,MAX(id)
很高兴地得到了一个旧值,并且没有做任何事情来阻止其他人查看 MAX(id)
.
可能的解决方案(我没有测试任何这些,也没有检查相同的问题和解决方案是否可以在单个服务器上发生而不加莱拉。):
UNIQUE(id)
-- 这应该会导致 COMMIT
中止。
- 使用
READ-COMMITTED
查看最新的MAX(id)
。
- 将
SELECT
移出 INSERT
并使用 FOR UPDATE
.
我发现常规 MariaDB 安装和 MariaDB Galera 集群之间的行为不一致。对于 Galera 集群的 INSERT ... SELECT 语句,锁定无法按预期工作。这会导致我们的应用程序中出现重复的 ID。
所有连接都使用隔离级别 REPEATABLE-READ(默认),通过此查询验证:
SELECT * FROM information_schema.session_variables WHERE variable_name = 'tx_isolation';
测试设置:
CREATE TABLE `TestTab` (`id` int(10) unsigned NOT NULL, `name` varchar(100) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;
预期行为:两个并行连接应该在 'id' 字段中创建两个具有两个不同值的记录。
演示常规 MariaDB 安装(非集群)的预期行为的命令序列:
Connection 1 Connection 2
SET autocommit=0;
START TRANSACTION;
INSERT INTO TestTab (`id`, `name`)
SELECT IFNULL(max(id)+1, 1), 'insert 1' FROM TestTab Limit 0,1;
SET autocommit=0;
START TRANSACTION;
INSERT INTO TestTab (`id`, `name`)
SELECT IFNULL(max(id)+1, 1), 'insert 2' FROM TestTab Limit 0,1;
-- *** GOOD *** INSERT blocks (ensures repeatable read for the SELECT)
COMMIT;
-- only now connection 2 completes the the INSERT
COMMIT;
SELECT * FROM TestTab;
SELECT * FROM TestTab;
+----+----------+
| id | name |
+----+----------+
| 1 | insert 1 |
| 2 | insert 2 |
+----+----------+
演示 MariaDB Galera 集群问题的命令序列:
Connection 1 Connection 2
SET autocommit=0;
START TRANSACTION;
INSERT INTO TestTab (`id`, `name`)
SELECT IFNULL(max(id)+1, 1), 'insert 1' FROM TestTab Limit 0,1;
SET autocommit=0;
START TRANSACTION;
INSERT INTO TestTab (`id`, `name`)
SELECT IFNULL(max(id)+1, 1), 'insert 2' FROM TestTab Limit 0,1;
-- *** BAD *** INSERT completes immediately, ignoring connection 1
COMMIT;
COMMIT;
SELECT * FROM TestTab;
SELECT * FROM TestTab;
SELECT * FROM TestTab;
+----+----------+
| id | name |
+----+----------+
| 1 | insert 1 |
| 1 | insert 2 | -- !!! same id for both records !!! --
+----+----------+
显然,Galera 集群的问题在于两条记录在 'id' 字段中获得相同的值。 在 Galera 集群上,INSERT ... SELECT 生成唯一 ID 的模式已损坏。
对我来说,这看起来像是一个错误,或者至少是非常不受欢迎和意外的行为。
是否可以通过 Galera 集群的不同配置来解决这个问题?
我尝试将两个会话的隔离级别提高到 SERIALIZABLE,这解决了问题。但这是一个非常不受欢迎的解决方法,性能对我们来说很重要。看来我还是很幸运:SERIALIZABLE 隔离级别仅在同一节点上发出的事务之间受到尊重,因此应该避免。 (quote from the Galera Cluster documentation)
您能想到什么更好的解决方法?
我应该尝试将此报告为 MariaDB 的错误吗?
请注意,Galera 在 COMMIT
之前不会与其他节点签入。同时,MAX(id)
很高兴地得到了一个旧值,并且没有做任何事情来阻止其他人查看 MAX(id)
.
可能的解决方案(我没有测试任何这些,也没有检查相同的问题和解决方案是否可以在单个服务器上发生而不加莱拉。):
UNIQUE(id)
-- 这应该会导致COMMIT
中止。- 使用
READ-COMMITTED
查看最新的MAX(id)
。 - 将
SELECT
移出INSERT
并使用FOR UPDATE
.