2017-09-05 61 views
1

插入行期间,我们有表锁定在MySQL

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; 

在我们执行操作(在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'); 

在并发模式并行线程2个线程可以尝试与MSISDN插入行=“000000002”和service-id =“web-sub-1”和不同的subscriptionId,因为当前时间戳可能不同。两个线程都执行第一次选择,获得零结果并插入。所以我们试图将这两个查询加入到tranaction中,但是对于不存在的行锁定存在问题 - 当我们需要锁定插入或类似的东西时。 而我们不想在这2个动作中锁定所有的表格,因为我们假设我们的系统在这种情况下工作速度太慢。 我们不能为这种情况创建uniq键,因为对于一个abonent可以有多个具有相同unsubscribed状态的行。如果我们尝试为同一服务插入2个订户,则主键可以包含具有不同秒数的时间戳。 我们尝试使用SELECT ... FOR UPDATE和SELECT ... LOCK进入共享模式,但是我们遇到了死锁问题,并且它对数据库服务器来说操作很繁琐。

为了测试我们打开​​了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 IGNORE ...; 

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

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

计划C.本声明主要由IODKU取代:

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

A和B(也可能是C)是 “原子”,所以疗法e没有僵局的机会。

+0

感谢您的重播。但在我们的并发模式下,2个线程可以尝试插入带有msisdn =“000000002”和service-id =“web-sub-1”和不同subscriptionId的行,因为当前时间戳可能不同。两个线程都执行第一次选择,获得零结果并插入。因此,我们可以检查的2行没有uniq键。 –

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的)。然后创建一个Java线程,除了从队列中提取项目并将其插入数据库之外,什么也不做。这可以防止死锁,因为只有一个线程插入数据库。

计划F.拥抱死锁

你不能阻止所有类型的死锁,发生时你只能处理它们。并发系统的设计应能预测一些死锁,并在必要时重试操作。

+0

感谢您的重播。我已经编辑了我的问题 - 正如我所看到的那样,它没有被清除。 2个线程可以尝试使用msisdn =“000000002”和service-id =“web-sub-1”和不同subscriptionId(主键,MSISDN-SERVICE-TIMESTAMP)插入行,因为subscriptionId包含当前时间戳,并且它可以不同。我们需要的只是执行选择,并确保,直到我们完成tranaction选择的结果将不会改变。如果它改变了,我们应该回滚。我不明白,为什么我们会陷入僵局。 如果我们使用READ COMMITED,我们不会死锁,但是两行都会被插入。 –

+0

@ValentinaChumak由于可重复读取所需的“间隙锁定”而发生死锁。请查看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用简笔画解释](https://www.slideshare.net/billkarwin/innodb-locking-explained-with-stick-figures)。 –

+0

谢谢,比尔。 (Plan E只假设一个Java客户端机器。) –