2

次のような 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 |
 -------------------

これらは単純な例にすぎません...実際のシステムでは、両方のテーブルにかなり多くの列と行があります (現在の行は 10,000 行以上、履歴は 100 万行以上)。

クライアント アプリケーションは継続的に (1 秒間に数回) 新しい行を現在のテーブルに挿入し、古い既存の行を現在のテーブルから履歴に "移動" します (1 回のトランザクション内での削除/挿入)。

このアクティビティでクライアントをブロックすることなく、2 つのテーブル全体で部門ごとの値の一貫した合計を取得する必要があります。

トランザクション分離レベルを REPEATABLE READ に設定すると、次のことができます。

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

に続く

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

2 つの結果セットを合計します。しかし、各クエリはそれぞれのテーブルへの挿入をブロックします。

分離レベルを READ COMMITTED に変更し、同じ 2 つの 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 は両方のテーブルの一貫したスナップショットを同時に生成しますか (それにより二重カウントのリスクを取り除きます)、それとも最初に 1 つのテーブルのスナップショットを取得し、しばらくしてから 2 番目のテーブルのスナップショットを取得しますか?

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`));

現在のテーブル (id = 0, .. 9) に 10 個のエントリを設定するプロシージャを作成し、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)クエリの実行中に「移動」したエントリが見逃されたか二重にカウントされたかどうかを示す単純な結果セット(2行)を返すように変更しました。

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 (...)、クエリに作業を追加して速度を落とすためだけに存在します。

次のように、2 つの 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 に答える