INT AUTO_INCREMENT PRIMARY KEY
と
UNIQUE KEY
が存在する単一のテーブルでした。
このテーブルで動作するクエリは、ほぼすべて
INSERT ... ON DUPLICATE KEY UPDATE
タイプ(以降
INSERT ODKU
ます)で、
INSERT
リストされた列は
UNIQUE KEY
列に対応していました。 また、1秒あたり約1500〜2000リクエストの頻度で、24時間連続して実行されました。 数学が得意であれば、おそらく何が問題なのかすでに推測しているでしょう。
議論のために、状況の説明として次の表を使用します。
CREATE TABLE update_test ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, username VARCHAR(20) NOT NULL, host_id TINYINT UNSIGNED NOT NULL, last_modified TIMESTAMP NULL DEFAULT NULL, PRIMARY KEY(id), UNIQUE KEY(username) ) ENGINE=InnoDB;
次の一連のイベントを想像してください。
(root@localhost) [test]> INSERT INTO update_test (username, host_id, last_modified) VALUES ('foo',3,NOW()); Query OK, 1 row affected (0.00 sec) (root@localhost) [test]> select * from update_test; +----+----------+---------+---------------------+ | id | username | host_id | last_modified | +----+----------+---------+---------------------+ | 1 | foo | 3 | 2012-10-05 22:36:30 | +----+----------+---------+---------------------+
普通のことは何もありませんよね? 空のテーブルに1つの行を挿入し、
SHOW CREATE TABLE
を実行すると、
AUTO_INCREMENT
カウンターの値が
2
なったことがわかります。 このテーブルで
INSERT ODKU
を実行すると、次のように表示されます。
(root@localhost) [test]> insert into update_test (username,host_id) values ('foo',1) on duplicate key update last_modified=NOW(); Query OK, 2 rows affected (0.00 sec) (root@localhost) [test]> select * from update_test; +----+----------+---------+---------------------+ | id | username | host_id | last_modified | +----+----------+---------+---------------------+ | 1 | foo | 3 | 2012-10-05 22:58:28 | +----+----------+---------+---------------------+ 1 row in set (0.00 sec)
そして今、新しい行を挿入しなかった場合でも、
AUTO_INCREMENT
カウンターは
3
増えました。 これは実際、予想される動作です。 InnoDBは、定義された順序で制約をチェックし、
PRIMARY KEY
常に最初に移動します。 したがって、MySQLは
INSERT
チェックし、次の
AUTO_INCREMENT
値が使用可能であることを確認して使用しますが、
UNIQUE KEY
をチェックして違反を見つけるため、
INSERT
ではなく
UPDATE
ます。
handler status
カウンターを見ると、失敗した挿入要求が1つ、成功した更新要求が1つあることがわかります(これは、1行ではなく2行が変更された理由です)。
(root@localhost) [test]> show status like 'handler%'; *** some rows omitted *** +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Handler_commit | 1 | | Handler_rollback | 0 | | Handler_update | 1 | | Handler_write | 1 | +----------------------------+-------+
この時点で、「だから何?」と思うかもしれません。 顧客に戻りましょう。 1秒あたり1,500の
INSERT ODKU
1日24時間継続的に。 それらのテーブルの
PRIMARY KEY
、デモテーブルで使用したものと同じです(
INT UNSIGNED
。 数えます。
INT UNSIGNED
の最大値は
4294967295
です。 1秒あたり1500リクエストで割り、86400で割ります。これは1日の秒数であり、33.1日、つまり4週間強になります。 偶然? そうは思いません それでは、意味を超えたときに正確に何が起こるのでしょうか? いくつかの行動はあなたを驚かせるかもしれません。 デモテーブルに戻って、
AUTO_INCREMENT
列の最大値を持つ行を挿入し、別の行を挿入します。
(root@localhost) [test]> insert into update_test (id,username,host_id) values (4294967295, 'bar', 10); Query OK, 1 row affected (0.00 sec) (root@localhost) [test]> flush status; (root@localhost) [test]> insert into update_test (username,host_id) values ('baz', 10); ERROR 1062 (23000): Duplicate entry '4294967295' for key 'PRIMARY' (root@localhost) [test]> show status like 'handler%'; *** some rows omitted *** +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Handler_rollback | 1 | | Handler_write | 1 | +----------------------------+-------+
そのため、行を挿入しようとしましたが、うまくいきませんでした。
AUTO_INCREMENT
すでに最大値があり、要求は失敗しました。 しかし、
INSERT ODKU
を実行しようとするとどう
INSERT ODKU
ますか? まず、テーブルにあるものを見てみましょう。
(root@localhost) [test]> select * from update_test; +------------+----------+---------+---------------------+ | id | username | host_id | last_modified | +------------+----------+---------+---------------------+ | 1 | foo | 3 | 2012-10-05 22:58:28 | | 4294967295 | bar | 10 | NULL | +------------+----------+---------+---------------------+ 2 rows in set (0.00 sec) (root@localhost) [test]> INSERT INTO update_test (username, host_id) VALUES ('foo', 7) ON DUPLICATE KEY UPDATE host_id=7, last_modified=NOW(); Query OK, 2 rows affected (0.00 sec)
元気そう? 2行が変更されました
username = "foo"
条件に
host_id
するシリーズについては、
host_id
と
last_modified
が更新されたことは明らかであり、喜ぶことができます。 残念ながら、これはそうではありません。
(root@localhost) [test]> select * from update_test; +------------+----------+---------+---------------------+ | id | username | host_id | last_modified | +------------+----------+---------+---------------------+ | 1 | foo | 3 | 2012-10-05 22:58:28 | | 4294967295 | bar | 7 | 2012-10-05 23:24:49 | +------------+----------+---------+---------------------+ 2 rows in set (0.00 sec)
おっと、最後の行が更新されました。その
id
は
AUTO_INCREMENT
最大値に等しく、
username
列の
UNIQUE KEY
は無視されました。
データベースがこの投稿のインスピレーションとなったクライアントの問題を簡単に理解できるようになりました。 同じ行をブロックして更新しようとする1秒あたり1500件の要求は、何の改善にもつながりません。 もちろん、簡単な解決策があります
AUTO_INCREMENT
カラムのデータ型を
INT
から
BIGINT
ます。
この動作は文書化されていることがわかります 。 マニュアルには、いくつかの一意のインデックスを持つテーブルの
INSERT ODKU
は
UPDATE update_test SET host_id = 7, last_modified = NOW() WHERE id = 4294967295 OR username = "foo" LIMIT 1
と同等であり、もちろんオプティマイザーは
PRIMARY
ではなく
PRIMARY
を選択すると述べています
UNIQUE
ここで何を学びましたか?
-
AUTO_INCREMENT
よりもずっと簡単です。 実際の顧客テーブルには、50万行未満が含まれていました。 -
AUTO_INCREMENT
SIGNED
型をAUTO_INCREMENT
は、ほとんど常に悪い考えです。 使用可能な値の範囲の半分が失われます。 - 物理学の法則のように、直観はしばしば極端な状況で失敗します。