質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.35%

MySQLでオートインクリメントの値が意図した通りにならない件の解決法について

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 4,024

zico_teratail

score 467

現在MySQL 5.5.45、InnoDBで使っています。
様々な場面で、AUTO_INCREMENTを設定した主キーの値が意図したとおりの動作にならずに困ることがあります。

特に多いのが、

  • INSERT...ON DUPLICATE KEY UPDATE
  • INSERT IGNORE
  • REPLACE

などを使った場合のインクリメントの仕方。

「SELECTしてその値が無かったらINSERT」は多くの人が望んでいる、「よくある動作」かと思われます。
一般的な人間の思考でいくと綺麗な連番になっていてほしいものですが、実際は値が飛び飛びになっていたりすることが多いですよね。

REPLACEなどは挙動をよく理解しておかないと致命的なことになりかねません。特にオートインクリメントしたidをサロゲートキーとして使っている場合は。「マニュアル読め」と言われればそれまでですが、一般的な人間の感覚からはだいぶズレた「MySQL(DB全般?)特有の動作」と言っていいのではないでしょうか。


参考:【MySQL】AUTO_INCREMENTの値が増える、飛ぶ、欠番が発生する at softelメモ

参考:insert duplicate インクリメント - Google 検索

参考:サロゲートキー - Google 検索


たとえば
id AUTO_INCREMENT 主キー
name unique
みたいなカラムがあるテーブルに「田中、鈴木、田中、山田」というデータを入れるために

INSERT IGNORE INTO `member` (`name`) VALUES(:name);


を投げると、id値は「1,2,4」となります。
実用上はそれでも問題ないのかもしれませんが、人間の心情的にはidを連続した値にしてほしいところです。

こういうとき、地道にSELECTして値の存在を確認してからINSERT・・・などと回りくどいことをせずに一発でスマートに(連番で)解決する方法は存在するのでしょうか?
それともデータベースとはそういうものなのだ、と諦めるしかないのでしょうか?


参考:kamipo TRADITIONALでは防げないINSERT IGNOREという名の化け物 | おそらくはそれさえも平凡な日々

参考:songmuさんのツイート: "我々は何故2015年にもなってSELECTして無かったらINSERTする場合のベストパターンを確立できていないのか。"

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+3

それともデータベースとはそういうものなのだ、と諦めるしかないのでしょうか?

諦めましょう。
同じような質問はteratailにいくつかありますので、参考にしてください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/12/26 12:39

    やはりそういうものですかね。

    キャンセル

+2

auto_incrementに対するそもそもの考え方が間違えていると思います
auto_incrementはレコードをconstに特定するためだけに利用すべきものです
したがって連番であるということは不要ですし、むしろ忘れてください
原則SQLにはレコード同士の間に相関関係はありません。
したがってデータが連続しているという考え方がSQL的発想ではありません。

なんらかの連番処理が必要であればSQL側でランキングを計算する手法が
幾つか用意されています。(ただし効率は悪いです)

 sample

create table tbl (id int not null unique key,val varchar(20));
insert into tbl values(1,'aaa'),(2,'bbb'),(4,'ccc'),(50,'ddd'),(999,'eee');
create view vw as select id,(select count(*)+1 from tbl as t2 where t1.id>t2.id) as renban,val from tbl as t1;

表示

select * from vw;

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/12/26 21:08

    超絶な横槍ですが。

    プログラムの世界は理論から成り立ってるので、
    直感には反することがあるのは仕方ないと受け止めるのが吉です。

    直感通りに動かないとどうしても気に入らない、
    要件を満たせないのであれば、結局自作するほかありません。

    個人的にはデータベースのオートインクリメントやシーケンスの仕組みは、
    アクセスのあったかという事実をトリガーに動作するものという認識なので、
    言うほど違和感はないかなと思ってます。

    考えようによれば、
    キー重複による試行失敗分が記録として分かるため、
    それはそれはありかなと思いますし。

    キャンセル

  • 2016/12/27 13:32

    >Panzer_vorさん
    >キー重複による試行失敗分が記録として分かる

    なるほど、それは確かにメリットともいえますね。

    キャンセル

  • 2016/12/29 06:42

    > zico_teratailさん
    当方のDBのオートインクリメントに考え方についは上記の通りですが、
    オートインクリメント絡みはDBを跨いだデータ移行なとで、ちょくちょくトラブルを生むことがあるのでご注意を。

    それもあり移植性と連番の正確性を重視すると、
    テーブル操作が1つ余計には増えますが、採番テーブルを用意するのが堅実かもしれませんね。

    採番テーブルでの発番から、
    本テーブル登録時の採番までを1つのトランザクションとしたストアドを作れば使い回しも効きますし。

    キャンセル

+1

簡単なサンプルをストアドで。
考慮すべきことはまだあるし、不十分ですが。

テーブル定義

CREATE TABLE `Sample` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(16) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ストアド

DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `updateOrInsert`(IN id INT, IN txt VARCHAR(16))
BEGIN

UPDATE Sample SET `name` = txt WHERE id = id;
IF (0 = ROW_COUNT()) THEN
    INSERT INTO Sample (id, `name`) VALUES (id, txt);
END IF;

END;;
DELIMITER ;

実行

CALL updateOrInsert(1, 'aaa');

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/12/26 12:39

    ありがとうございます。
    参考にさせていただきます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る