web-dev-qa-db-ja.com

MySQL AUTO_INCREMENTはROLLBACKしません

トランザクションをサポートするためにMySQLのAUTO_INCREMENTフィールドとInnoDBを使用しています。トランザクションをロールバックすると、AUTO_INCREMENTフィールドがロールバックされないことに気付きましたか?このように設計されていることがわかりましたが、これに対する回避策はありますか?

63
codegy

非常に重要なことを指摘させてください。

自動生成キーの数値機能に依存しないでください。

つまり、等号(=)または不等号(<>)を比較する以外に、他に何もすべきではありません。関係演算子(<、>)、インデックスによる並べ替えなどはありません。「追加日」で並べ替える必要がある場合は、「追加日」列を用意します。

それらをリンゴとオレンジとして扱う:Appleがオレンジと同じかどうかを尋ねるのは理にかなっていますか?はい。 Appleがオレンジより大きいかどうかを尋ねるのは理にかなっていますか?いいえ。(実際、そうですが、あなたは私の言い分を得ます。)

この規則を守れば、自動生成されたインデックスの連続性のギャップは問題を引き起こしません。

56
Tamas Czinege

そのようには機能しません。考慮してください:

  • プログラム1、トランザクションを開き、autoinc主キーを持つテーブルFOOに挿入します(任意に、キー値として557を取得すると言います)。
  • プログラム2が開始し、トランザクションを開き、テーブルFOOに挿入して558を取得します。
  • FOOへの外部キーである列を持つテーブルBARに2つの挿入をプログラムします。したがって、現在558はFOOとBARの両方にあります。
  • プログラム2がコミットします。
  • プログラム3が起動し、テーブルFOOからレポートを生成します。 558レコードが印刷されます。
  • その後、プログラム1がロールバックします。

データベースはどのように557値を回収しますか? FOOに入り、557を超える他のすべての主キーを減らしますか? BARはどのように修正されますか?レポートプログラム3の出力に印刷された558はどのように消去されますか?

Oracleのシーケンス番号も、同じ理由でトランザクションから独立しています。

この問題を一定の時間で解決できれば、データベース分野で大金を稼ぐことができると確信しています。

ここで、自動インクリメントフィールドにギャップがないことを要求する場合(たとえば、監査目的で)。その後、トランザクションをロールバックできません。代わりに、レコードにステータスフラグを付ける必要があります。最初の挿入では、レコードのステータスは「未完了」であり、トランザクションを開始し、作業を行い、ステータスを「競合」(または必要なもの)に更新します。その後、コミットすると、レコードはライブになります。トランザクションがロールバックする場合、監査のためにまだ不完全なレコードが残っています。これは他にも多くの頭痛の種になりますが、監査証跡に対処する1つの方法です。

75
jmucchiello

注文が連続している必要がある請求書の表でロールバックするためにIDが必要なクライアントがありました

MySQLでの私の解決策は、自動インクリメントを削除し、テーブルから最新のIDを取得し、1を追加してから(+1)手動で挿入することでした。

テーブルの名前が「TableA」で、自動インクリメント列が「Id」の場合

INSERT INTO TableA (Id, Col2, Col3, Col4, ...)
VALUES (
(SELECT Id FROM TableA t ORDER BY t.Id DESC LIMIT 1)+1,
Col2_Val, Col3_Val, Col4_Val, ...)
12

ロールバックされるのはなぜですか? AUTO_INCREMENTキーフィールドは意味を持たないことになっているので、実際にどの値が使用されるかを気にする必要はありません。

保存しようとしている情報がある場合は、おそらく別の非キー列が必要です。

11
Paul Lefebvre

私はそれを行う方法を知りません。 MySQLドキュメント によると、これは予想される動作であり、すべてのinnodb_autoinc_lock_modeロックモードで発生します。具体的なテキストは次のとおりです。

すべてのロックモード(0、1、および2)で、自動インクリメント値を生成したトランザクションがロールバックすると、それらの自動インクリメント値は「失われます」。自動インクリメント列に対して値が生成されると、「INSERT-like」ステートメントが完了したかどうか、および含まれているトランザクションがロールバックされたかどうかにかかわらず、ロールバックできません。そのような失われた値は再利用されません。したがって、テーブルのAUTO_INCREMENTカラムに格納されている値にギャップがある場合があります。

8
gpojd

ロールバックまたは削除後に_auto_increment_を_1_に設定すると、MySQLは_1_が既に使用されていることを確認し、代わりにMAX()値を取得します1を追加します。

これにより、最後の値を持つ行が削除された場合(または挿入がロールバックされた場合)、確実に再利用されます。

Auto_incrementを1に設定するには、次のようにします。

_ALTER TABLE tbl auto_increment = 1
_

MAX()は高額になる可能性があるため、次の数字を続けるほど効率的ではありませんが、削除/ロールバックが頻繁ではなく、最高値の再利用に取りつかれている場合、これは現実的なアプローチです。

これは、途中で削除されたレコードのギャップや、auto_incrementを1に戻す前に別の挿入が発生する場合のギャップを防ぐことはできないことに注意してください。

5
Marcus Adams

解決:

'tbl_test'をサンプルテーブルとして使用し、フィールド 'Id'にAUTO_INCREMENT属性があると仮定します。

CREATE TABLE tbl_test (
Id int NOT NULL AUTO_INCREMENT ,
Name varchar(255) NULL ,
PRIMARY KEY (`Id`)
)
;

テーブルに千行または千行がすでに挿入されていて、AUTO_INCREMENTをもう使いたくないとしましょう。トランザクションをロールバックすると、フィールド「Id」は常にAUTO_INCREMENT値に+1を追加するためです。そのため、これを回避するには、次のようにします。

  • 列 'Id'からAUTO_INCREMENT値を削除しましょう(挿入された行は削除されません)。
ALTER TABLE tbl_test MODIFY COLUMN Id int(11)NOT NULL FIRST;
  • 最後に、BEFORE INSERTトリガーを作成して、「Id」値を自動的に生成します。ただし、この方法を使用しても、トランザクションをロールバックしても、ID値には影響しません。
CREATE TRIGGER trg_tbl_test_1 
 tbl_testに挿入する前に
各行に対して
 BEGIN 
 SET NEW.Id = COALESCE((SELECT MAX(Id)FROM tbl_test)、0) + 1; 
 END;

それでおしまい!できました!

どういたしまして。
2
mld.oscar
INSERT INTO prueba(id) 
VALUES (
(SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target))

テーブルに値またはゼロ行が含まれていない場合

sELECTのエラーmysqlタイプ更新FROMのターゲットを追加

2
danis

この特定のジレンマに対する具体的な答えは(私にもありました)、次のとおりです。

1)さまざまなドキュメント(請求書、領収書、RMAなど)に対してさまざまなカウンターを保持するテーブルを作成します。ドキュメントごとにレコードを挿入し、初期カウンターを0に追加します。

2)新しいドキュメントを作成する前に、次を実行します(たとえば、請求書の場合)。

UPDATE document_counters SET counter = LAST_INSERT_ID(counter + 1) where type = 'invoice'

3)次のように、更新したばかりの最後の値を取得します。

SELECT LAST_INSERT_ID()

または、PHP(または何でも)mysql_insert_id()関数を使用して同じことを取得します

4)DBから取得したプライマリIDとともに新しいレコードを挿入します。これにより、現在の自動増分インデックスが上書きされ、レコード間にIDギャップがないことを確認します。

もちろん、このすべてをトランザクション内にラップする必要があります。このメソッドの利点は、トランザクションをロールバックすると、ステップ2のUPDATEステートメントがロールバックされ、カウンターが変更されないことです。他の同時トランザクションは、最初のトランザクションがコミットまたはロールバックされるまでブロックされるため、他のすべてのトランザクションが最初に完了するまで、古いカウンターOR新しいカウンターにアクセスできません。

0
teomor

IDをギャップのない番号順に割り当てる必要がある場合は、自動インクリメント列を使用できません。標準整数列を定義し、挿入シーケンスの次の数値を計算し、トランザクション内にレコードを挿入するストアドプロシージャを使用する必要があります。挿入が失敗した場合、次にプロシージャが呼び出されると、次のIDが再計算されます。

そうは言っても、IDが特定の順序でギャップなしであることに依存するのは悪い考えです。順序を保持する必要がある場合は、おそらく挿入時(および潜在的に更新時)に行にタイムスタンプを付ける必要があります。

0
tvanfosson