2017-09-05 5 views
1

に行を挿入中に私たちは、並行処理モードでは、これらのロックmysqlの

1. Select active subscribers 
SELECT * 
    FROM TEST_SUBSCRIBERS 
    WHERE SERVICE_ID='web-sub-1' 
     and MSISDN='000000002' 
     AND STATE IN ('ACTIVE', 'INACTIVE'); 
2. If there are no such subscribers, I can insert it 
INSERT INTO TEST_SUBSCRIBERS 
        (SUBSCRIPTION_ID, MSISDN, STATE, SERVICE_ID) 
      VALUES ('web-sub-1-000000002-1504624819', '000000002', 'ACTIVE', 'web-sub-1'); 

のような(Javaで)アクションを実行する並列スレッドでテーブル

CREATE TABLE TEST_SUBSCRIBERS (
    SUBSCRIPTION_ID varchar(255) NOT NULL COMMENT 'Subscriber id in format MSISDN-SERVICE_ID-TIMESTAMP', 
    MSISDN varchar(12) NOT NULL COMMENT 'Subscriber phone', 
    STATE enum ('ACTIVE', 'INACTIVE', 'UNSUBSCRIBED_SMS', 'UNSUBSCRIBED_PARTNER', 'UNSUBSCRIBED_ADMIN', 'UNSUBSCRIBED_REBILLING') NOT NULL, 
    SERVICE_ID varchar(255) NOT NULL COMMENT 'Id of service', 
    PRIMARY KEY (SUBSCRIPTION_ID) 
) 
ENGINE = INNODB 
CHARACTER SET utf8 
COLLATE utf8_general_ci; 

を持っている2つのスレッドは、MSISDNに行を挿入しようとすることができます= "000000002"とservice-id = "web-sub-1"で、現在のタイムスタンプが異なる可能性があるため、異なるsubscriptionId。両方のスレッドが最初の選択を実行し、結果がゼロになり、両方のスレッドが挿入されます。そこで、これらの2つのクエリをトランザクションに結合しようとしますが、既存の行ではないロックの問題があります。挿入にロックが必要な場合などです。 この2つのアクションでは、すべてのテーブルをロックしたくないのです。この場合、システムの動作が遅すぎると思われるからです。 この状況のた​​めにuniqキーを作成することはできません。なぜなら、1人の主体に対して、同じunsubscribed状態の複数の行が存在する可能性があるからです。同じサービスの2人の加入者を挿入しようとすると、プライマリキーには秒単位のタイムスタンプが含まれます。 私たちはSELECT ... FOR UPDATEとSELECT ... LOCK IN SHARE MODEを使用しようとしましたが、デッドロックが発生し、データベースサーバーの操作が重いです。私たちは2つのターミナルを開いて、ステップバイステップで行った試験では

# Window 1 
mysql> start transaction; 
mysql> SELECT SUBSCRIPTION_ID FROM TEST_SUBSCRIBERS s 
      WHERE s.SERVICE_ID="web-sub-1" AND s.MSISDN="000000002" FOR UPDATE; 

# Window 2 
start transaction; 
mysql> SELECT SUBSCRIPTION_ID FROM TEST_SUBSCRIBERS s 
      WHERE s.SERVICE_ID="web-sub-1" AND s.MSISDN="000000002" FOR UPDATE; 

# Window 1 
mysql> INSERT INTO TEST_SUBSCRIBERS 
      (SUBSCRIPTION_ID, MSISDN, STATE, SERVICE_ID) 
     VALUES('web-sub-1-000000002-1504624818', '000000002', 'ACTIVE', 'web-sub-1'); 

# Window 2 
mysql> INSERT INTO TEST_SUBSCRIBERS 
       (SUBSCRIPTION_ID, MSISDN, STATE, SERVICE_ID) 
      VALUES('web-sub-1-000000002-1504624819', '000000002', 'ACTIVE', 'web-sub-1'); 
ERROR 1213 (40001): Deadlock found when trying to get lock; 
        try restarting transaction 

はデッドロックせずに、完全なテーブルをロックすることなく、このような操作を行うための方法はありますか?我々が分析した他の変種は: 1.別表 2.不要な行の挿入と削除。

答えて

0

プランA.これは、(必要な場合)を挿入または黙って何もしないだろう、次のいずれか

INSERT INTO ... 
    (...) 
    ON DUPLICATE KEY UPDATE 
    ...; 

計画:

INSERT IGNORE ...; 

プランB.何も "更新" を必要としないので、これはやりすぎかもしれC.この文は、主にIODKUによって置き換えられます。

REPLACE ... (same syntax as INSERT, but it does a silent DELETE first) 

AとB(そしておそらくC)は、そうTHER "アトミック" ですeはデッドロックの可能性はありません。

+0

再生いただきありがとうございます。しかし、現在のタイムスタンプが異なる可能性があるため、同時実行モードの2つのスレッドでは、msisdn = "000000002"とservice-id = "web-sub-1"と異なるsubscriptionIdを持つ行を挿入しようとします。両方のスレッドが最初の選択を実行し、結果がゼロになり、両方のスレッドが挿入されます。だから、私たちがチェックできる2行については同じユニキーキーはありません。 –

0

@RickJamesからの回答に続きます。

プランD.使用READ-COMMITTED

ウィンドウ1

mysql> set tx_isolation='READ-COMMITTED'; 
mysql> start transaction; 
mysql> SELECT SUBSCRIPTION_ID FROM TEST_SUBSCRIBERS s 
     WHERE s.SERVICE_ID="web-sub-1" AND s.MSISDN="000000002" FOR UPDATE; 

ウィンドウ2

mysql> set tx_isolation='READ-COMMITTED'; 
mysql> start transaction; 
mysql> SELECT SUBSCRIPTION_ID FROM TEST_SUBSCRIBERS s 
     WHERE s.SERVICE_ID="web-sub-1" AND s.MSISDN="000000002" FOR UPDATE; 

ウィンドウ1

mysql> INSERT INTO TEST_SUBSCRIBERS (SUBSCRIPTION_ID, MSISDN, STATE, SERVICE_ID) 
    VALUES('web-sub-1-000000002-10', '000000002', 'ACTIVE', 'web-sub-1'); 

ウィンドウ2

mysql> INSERT INTO TEST_SUBSCRIBERS (SUBSCRIPTION_ID, MSISDN, STATE, SERVICE_ID) 
    VALUES('web-sub-1-000000002-10', '000000002', 'ACTIVE', 'web-sub-1'); 

<begins lock wait> 

ウィンドウ1つの

mysql> commit; 

ウィンドウ2

<lock wait ends immediately> 

ERROR 1062 (23000): Duplicate entry 'web-sub-1-000000002-10' for key 'PRIMARY' 

重複キーエラーはデッドロックではありませんが、それはまだエラーです。しかし、トランザクション全体をロールバックするわけではなく、試行された挿入をキャンセルするだけです。保留中のまま正常に実行された他の変更とのアクティブなトランザクションはまだあります。

計画E.を使用する代わりに、データベースに挿入同時Javaスレッドを有するのキュー

は、単にJavaスレッドは、メッセージキュー(例えば、ActiveMQの)にアイテムを入力有します。次に、1つのJavaスレッドを作成して、キューからアイテムを取り出してデータベースに挿入します。これにより、データベースに挿入するスレッドが1つしかないため、デッドロックが防止されます。

プランF.は、彼らが発生したときにのみ、それらを処理することができ、デッドロック

あなたはデッドロックのすべてのタイプを防ぐことはできませんを受け入れます。並行システムは、いくつかのデッドロックを予想し、必要に応じて操作を再試行するように設計する必要があります。

+0

リプレイをありがとう。私は私の質問を編集しました - それは私が見るようにクリアされませんでした。 subscriptionIDには現在のタイムスタンプが含まれているため、2つのスレッドはmsisdn = "000000002"とservice-id = "web-sub-1"と異なるsubscriptionId(プライマリキー、MSISDN-SERVICE-TIMESTAMP)を含む行を挿入しようとする可能性があります。私たちが必要とするのは、選択を実行するだけで、終了するまで、選択の結果は変更されません。それが変更された場合は、ロールバックする必要があります。私は理解していない、なぜ私たちはデッドロックを取得します。 READ COMMITEDを使用すると、デッドロックは発生しませんが、両方の行が挿入されます。 –

+0

@ValentinaChumak REPEATABLE-READに必要な「ギャップロック」のためにデッドロックが発生します。 https://www.percona.com/blog/2012/03/27/innodbs-gap-locks/またはhttps://dev.mysql.com/doc/refman/5.7/en/innodb-locking.htmlをご覧ください。 #innodb-gap-locksまたは私のプレゼンテーション[InnoDB Locking Explained With Stick Figures](https://www.slideshare.net/billkarwin/innodb-locking-explained-with-stick-figures)を参照してください。 –

+0

ありがとう、ビル。 (プランEでは、Javaクライアントマシンは1台しか想定していません)。 –

関連する問題