web-dev-qa-db-ja.com

MySQL重複挿入での自動インクリメントを防止

MySQL 5.1.49を使用して、タグ付けシステムを実装しようとしています。問題は、2つの列を持つテーブルです:id(autoincrement)tag(unique varchar)(InnoDB)

クエリを使用する場合、INSERT IGNORE INTO tablename SET tag="whatever"、挿入が無視された場合でも、自動インクリメントid値が増加します。

通常、これは問題にはなりませんが、この特定のテーブルに重複を挿入しようとする多くの試みが予想されます。つまり、新しい行のidフィールドの次の値が飛びすぎます。

たとえば、3行のテーブルになりますが、idが不良です

1   | test
8   | testtext
678 | testtextt

また、INSERT IGNOREそして、通常のINSERT INTOおよびエラーを処理すると、自動インクリメントフィールドが増加するため、次の真の挿入は依然として誤った自動インクリメントです。

INSERT重複行試行がある場合に自動インクリメントを停止する方法はありますか?

私がMySQL 4.1で理解しているように、この値は増加しませんが、最後にしたいことは、タグが存在するかどうかを確認するために事前に多くのSELECTステートメントを実行するか、さらに悪いことにダウングレードすることですMySQLバージョン。

37
robert

INSERTを次のように変更できます。

INSERT INTO tablename (tag)
SELECT $tag
FROM tablename
WHERE NOT EXISTS(
    SELECT tag
    FROM tablename
    WHERE tag = $tag
)
LIMIT 1

どこ $tagは、タグが存在しない場合に追加するタグ(適切に引用されているか、もちろんプレースホルダーとして)です。このアプローチでは、タグが既に存在する場合、INSERT(およびその後の自動インクリメントの無駄)もトリガーしません。おそらくそれよりも優れたSQLを思いつくかもしれませんが、上記の方法でうまくいくはずです。

テーブルのインデックスが適切に作成されている場合、存在チェック用の追加のSELECTは高速になり、データベースはとにかくそのチェックを実行する必要があります。

ただし、このアプローチは最初のタグでは機能しません。常に使用されると思われるタグをタグテーブルにシードするか、空のテーブルに対して別のチェックを行うことができます。

22
mu is too short

V 5.5のMySQLドキュメントには次のように書かれています。

"If you use INSERT IGNORE and the row is ignored, the AUTO_INCREMENT counter 
is **not** incremented and LAST_INSERT_ID() returns 0, 
which reflects that no row was inserted."

参照: http://dev.mysql.com/doc/refman/5.5/en/information-functions.html#function_last-insert-id

バージョン5.1以降、InnoDBには自動インクリメントロックを構成できます。 http://dev.mysql.com/doc/refman/5.1/en/innodb-auto-increment-handling.html#innodb-auto-inc ...も参照してください.

回避策:オプションinnodb_autoinc_lock_mode = 0(従来)を使用します。

14

この宝石を見つけました...

http://www.timrosenblatt.com/blog/2008/03/21/insert-where-not-exists/

INSERT INTO [table name] SELECT '[value1]', '[value2]' FROM DUAL
WHERE NOT EXISTS(
    SELECT [column1] FROM [same table name]
    WHERE [column1]='[value1]'
    AND [column2]='[value2]' LIMIT 1
)

InfluenceRows = 1の場合、挿入されました。それ以外の場合、impactedRows = 0の場合、重複がありました。

13
Jamie

Muは短すぎる回答であることがわかりましたが、空のテーブルに挿入できないため制限があります。私は簡単な修正がトリックをしたことを発見しました:

INSERT INTO tablename (tag)
SELECT $tag
FROM (select 1) as a     #this line is different from the other answer
WHERE NOT EXISTS(
    SELECT tag
    FROM tablename
    WHERE tag = $tag
)
LIMIT 1

From句のテーブルを「偽の」テーブルに置き換える(select 1) as aは、その部分が挿入を実行できるレコードを返すことを許可しました。 mysql 5.5.37を実行しています。そこにほとんどの道を教えてくれてありがとう。

11
Landon

いつでも追加できますON DUPLICATE KEY UPDATEこちらをご覧ください (正確ではありませんが、問題は解決したようです)。

@raviによるコメントから

インクリメントが発生するかどうかは、innodb_autoinc_lock_mode設定に依存します。ゼロ以外の値に設定すると、ON DUPLICATE KEYが起動した場合でも、auto-incカウンターが増加します

私は同じ問題を抱えていましたが、in弾砲でハエを殺しているように感じたので、innodb_autoinc_lock_mode = 0を使用したくありませんでした。

この問題を解決するために、一時テーブルを使用することになりました。

create temporary table mytable_temp like mytable;

次に、次の値を挿入しました。

insert into mytable_temp values (null,'valA'),(null,'valB'),(null,'valC');

その後、単に別の挿入を行いますが、「not in」を使用して重複を無視します。

insert into mytable (myRow) select mytable_temp.myRow from mytable_temp 
where mytable_temp.myRow not in (select myRow from mytable);

私はこれをパフォーマンスについてテストしていませんが、それは仕事をし、読みやすいです。これは重要なことでしたが、私は絶えず更新されているデータを扱っていたため、ギャップを無視できませんでした。

1
Thomas B

受け入れられた答えは役に立ちましたが、使用中に問題が発生しました。基本的にあなたのテーブルにエントリがない場合、選択が指定されたテーブルを使用しているので動作しないため、代わりに次のものを思い付きました。テーブルは空白です。また、テーブルを2か所に挿入し、変数を1か所に挿入するだけで済みます。

INSERT INTO database_name.table_name (a,b,c,d)
SELECT 
    i.*
FROM
    (SELECT 
        $a AS a, 
            $b AS b,
            $c AS c,
            $d AS d
            /*variables (properly escaped) to insert*/
    ) i
        LEFT JOIN        
    database_name.table_name o ON i.a = o.a AND i.b = o.b /*condition to not insert for*/
WHERE
    o.a IS NULL
LIMIT 1 /*Not needed as can only ever be one, just being sure*/

あなたがそれが役に立つことを願っています

1
Joseph Bailey