岩本隆史の日記帳(アーカイブ)

はてなダイアリーのサービス終了をうけて移行したものです。更新はしません。

複合UNIQUEキーでも「INSERT ... ON DUPLICATE KEY UPDATE」構文は使える

MySQLには「INSERT ... ON DUPLICATE KEY UPDATE」という便利な構文がある。INSERTの内容がUNIQUE制約に引っかかる場合に指定カラムの値をUPDATEしてくれるものだ。

この構文について「複合UNIQUEキーの場合には使えない」とする記事を見た。

注意点としては、複合UNIQUEキーを指定しているテーブルでは(column2とcolumn1の組でUNIQUEなど)、 UPDATE文が複数レコードにマッチする可能性がありますので、UNIQUEキー制約が単一カラムにしかないテーブルでのみ使用します。内部的に実行されるUPDATE文のWHERE節がUNIQUEキーのORで判定するためです。(WHERE column1=’’ OR column2=’’)複合UNIQUEキー制約があるテーブルに対しては、次のREPLACE構文が使えます。

http://www.flatz.jp/archives/76

しかし、これは誤りだと思う。MySQL 5.1のマニュアルにはこうある。

例えば、もしカラム a が UNIQUE として宣言され、それが値 1 を含んでいたら、次の2つのステートメントは同一効果を持ちます。

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

UPDATE table SET c=c+1 WHERE a=1;

もしその行が新しいレコードとして挿入されると、行に影響される値は1となり、もし既存レコードが更新されると2になります。


もしカラム b も固有であれば、INSERT は代わりにこの UPDATE ステートメントと同等になります。

UPDATE table SET c=c+1 WHERE a=1 OR b=2 LIMIT 1;

もし a=1 OR b=2 がいくつかの行とマッチすれば、1つの 行だけが更新されます。通常、複数の固有インデックスを持つテーブル上で ON DUPLICATE KEY 条項を利用するのは避けるべきです。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.2.5.3 INSERT ... ON DUPLICATE KEY UPDATE 構文

つまり、カラム a とカラム b に対してそれぞれ独立のUNIQUEインデックスが張られている場合には利用を避けよということだ。

たとえば下記の場合、最後のINSERT時に更新される行は不定となる。

CREATE TABLE table_a (
  a INT NOT NULL,
  b INT NOT NULL,
  c INT NOT NULL,
  UNIQUE (a),
  UNIQUE (b)
);
INSERT INTO table_a (a, b, c) VALUES (1, 1, 0);
INSERT INTO table_a (a, b, c) VALUES (2, 2, 0);
INSERT INTO table_a (a, b, c) VALUES (1, 2, 0) ON DUPLICATE KEY UPDATE c = c + 1;

ひるがえって、カラム a とカラム b に対して複合UNIQUEインデックスが張られており、それ以外にUNIQUEインデックスが存在しないならば、上記のような問題は起こらない。

CREATE TABLE table_a (
  a INT NOT NULL,
  b INT NOT NULL,
  c INT NOT NULL,
  UNIQUE (a, b)
);
INSERT INTO table_a (a, b, c) VALUES (1, 1, 0);
INSERT INTO table_a (a, b, c) VALUES (1, 2, 0);
INSERT INTO table_a (a, b, c) VALUES (2, 1, 0);
INSERT INTO table_a (a, b, c) VALUES (2, 2, 0);
INSERT INTO table_a (a, b, c) VALUES (1, 2, 0) ON DUPLICATE KEY UPDATE c = c + 1;

mysql> select * from table_a;
+---+---+---+
| a | b | c |
+---+---+---+
| 1 | 1 | 0 |
| 1 | 2 | 1 |
| 2 | 1 | 0 |
| 2 | 2 | 0 |
+---+---+---+
4 rows in set (0.00 sec)

複合UNIQUEキーがあっても「複数の固有インデックス」さえなければ「INSERT ... ON DUPLICATE KEY UPDATE」構文は使えるということだ。