在 mysql 中插入行期间锁定

Val*_*mak 5 mysql concurrency innodb locking

我们有桌子

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;
Run Code Online (Sandbox Code Playgroud)

在并行线程中,我们执行如下操作(在java中)

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');
Run Code Online (Sandbox Code Playgroud)

在并发模式下,2个线程可以尝试插入msisdn =“000000002”和service-id =“web-sub-1”以及不同subscriptionId的行,因为当前时间戳可能不同。两个线程都执行第一个选择,得到零结果,然后都插入。因此,我们尝试将这两个查询连接到事务中,但是当我们需要插入锁或类似操作时,锁定不存在的行会出现问题。我们不想在这两个操作期间锁定所有表,因为我们认为在这种情况下我们的系统会运行得太慢。我们无法为这种情况创建 uniq key,因为对于一个 abonent,可能有多行具有相同的取消订阅状态。如果我们尝试插入同一服务的 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
Run Code Online (Sandbox Code Playgroud)

有没有办法做到这一点而不会出现死锁并且不会锁定整个表?我们分析的其他变体是: 1. 单独的表 2. 插入和删除不需要的行。

Ric*_*mes 3

计划 A. 这将插入(如果需要)或默默地不执行任何操作:

INSERT IGNORE ...;
Run Code Online (Sandbox Code Playgroud)

B 计划。这可能有点矫枉过正,因为没有什么需要“更新”:

INSERT INTO ...
    (...)
    ON DUPLICATE KEY UPDATE
    ...;
Run Code Online (Sandbox Code Playgroud)

Plan C。该语句大部分被IODKU替代:

REPLACE ... (same syntax as INSERT, but it does a silent DELETE first)
Run Code Online (Sandbox Code Playgroud)

A 和 B(可能还有 C)是“原子的”,因此不会出现死锁。