2

考虑两个这样的表:

TABLE: current
 -------------------
| id | dept | value |
|----|------|-------|
|   4|    A |    20 |
|   5|    B |    15 |
|   6|    A |    25 |
 -------------------

TABLE: history
 -------------------
| id | dept | value |
|----|------|-------|
|   1|    A |    10 |
|   2|    C |    10 |
|   3|    B |    20 |
 -------------------

这些只是简单的例子......在实际系统中,两个表都有更多的列和更多的行(当前为 10k+ 行,历史为 1M+ 行)。

客户端应用程序不断地(每秒几次)将新行插入到当前表中,并将旧的现有行从当前表“移动”到历史记录(在单个事务中删除/插入)。

在不阻止此活动中的客户端的情况下,我们需要在两个表中为每个部门获取一致的值总和。

将事务隔离级别设置为 REPEATABLE READ 我们可以这样做:

SELECT dept, sum(value) FROM current GROUP BY dept;

其次是

SELECT dept, sum(value) FROM history GROUP BY dept;

并将两组结果相加。但是每个查询都会阻止其各自表上的插入。

将隔离级别更改为 READ COMMITTED 并执行相同的两个 SQL 将避免阻塞插入,但现在如果在查询时将条目从当前移动到历史记录,则存在重复计算条目的风险(因为每个 SELECT 都会创建自己的快照)。

那么问题来了....如果我执行UNION,隔离级别READ COMMITTED会发生什么:

SELECT dept, sum(value) FROM current GROUP BY dept
UNION ALL
SELECT dept, sum(value) FROM history GROUP BY dept;

MySQL 会同时为两个表生成一致的快照(从而消除重复计算的风险)还是仍然先对一个表进行快照,然后再对第二个表进行快照?

4

1 回答 1

1

我还没有找到任何确凿的文件来回答我的问题,所以我开始尝试证明它。虽然不是科学意义上的证明,但我的发现表明为 UNION 查询中的所有表创建了一致的快照

这就是我所做的。

创建表

DROP TABLE IF EXISTS `current`;

CREATE TABLE IF NOT EXISTS `current` (
  `id` BIGINT NOT NULL COMMENT 'Unique numerical ID.',
  `dept` BIGINT NOT NULL COMMENT 'Department',
  `value` BIGINT NOT NULL COMMENT 'Value',
  PRIMARY KEY (`id`));


DROP TABLE IF EXISTS `history`;

CREATE TABLE IF NOT EXISTS `history` (
  `id` BIGINT NOT NULL COMMENT 'Unique numerical ID.',
  `dept` BIGINT NOT NULL COMMENT 'Department',
  `value` BIGINT NOT NULL COMMENT 'Value',
  PRIMARY KEY (`id`));

创建一个在当前表中设置 10 个条目(id = 0, .. 9)的过程,然后位于一个紧密循环中,将 1 个新行插入到当前表中,并将最旧的行从当前表“移动”到历史记录。每次迭代都在一个事务中执行,因此当前表保持稳定的 10 行,而历史表快速增长。在任何时间点 min(current.id) = max(history.id) + 1

DROP PROCEDURE IF EXISTS `idLoop`;

DELIMITER $$
CREATE PROCEDURE `idLoop`()
BEGIN

DECLARE n bigint;

-- Populate initial 10 rows in current table if not already there
SELECT IFNULL(MAX(id), -1) + 1 INTO n from current;
START TRANSACTION;
WHILE n < 10 DO
  INSERT INTO current VALUES (n, n % 10, n % 1000);
  SET n = n + 1;
END WHILE;
COMMIT;

-- In tight loop, insert new row and 'move' oldest current row to history
WHILE n < 10000000 DO
  START TRANSACTION;
  -- Insert new row to current
  INSERT INTO current values(n,  n % 10, n % 1000);
  -- Move oldest row from current to history
  INSERT INTO history SELECT * FROM current WHERE id = (n - 10);
  DELETE FROM current where id = (n - 10);
  COMMIT;
  SET n = n + 1;
END WHILE;

END$$
DELIMITER ;

开始运行此过程(此调用在一段时间内不会返回 - 这是有意的)

call idLoop();

在同一数据库的另一个会话中,我们现在可以在我的原始帖子中尝试 UNION ALL 查询的变体。

我已将其修改为 (a) 减慢执行速度,并且 (b) 返回一个简单的结果集(两行),指示在查询运行时是否有任何条目“移动”已被遗漏或重复计算。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

SELECT 'HST' AS src, MAX(id) AS idx, COUNT(*) AS cnt, SUM(value) FROM history WHERE dept IN (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
UNION ALL
SELECT 'CRT' AS src, MIN(id) AS idx, COUNT(*) AS cnt, SUM(value) FROM current WHERE dept IN (0, 1, 2, 3, 4, 5, 6, 7, 8, 9);

sum(value)和只是在where dept in (...)那里为查询添加工作并减慢它的速度。

积极结果的指示是两个 idx 值是否相邻,如下所示:

+-----+--------+--------+------------+
| src | idx    | cnt    | SUM(value) |
+-----+--------+--------+------------+
| HST | 625874 | 625875 |  312569875 |
| CRT | 625875 |     10 |       8795 |
+-----+--------+--------+------------+
2 rows in set (1.43 sec)

我仍然很乐意听到有关此的任何权威信息。

于 2014-12-11T17:43:21.067 回答