mysql - 在 SELECT … INNER JOIN … FOR UPDATE 的情况下,字符串的顺序会被锁定,以及如何避免死锁?

标签 mysql sql deadlock percona

角色 MySQL 5.7.21-20

— Memory temporary table TQueue
CREATE TEMPORARY TABLE IF NOT EXISTS TQueue (
    ID bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    QUEUE_STATUS enum ('ADDED', 'PROCESSED', 'SUCCESS', 'ERROR') NOT NULL DEFAULT 'ADDED',
    QUEUE_TIMEOUT datetime NOT NULL,
    ACTION enum ('INSERT', 'DELETE', 'UPDATE') NOT NULL,
    REPORT_ID tinyint(4) UNSIGNED NOT NULL,
    LOGIN int(11) NOT NULL,
    `GROUP` char(16) NOT NULL,
    ENABLE int(11) NOT NULL,
    ENABLE_CHANGE_PASS int(11) NOT NULL,
    ENABLE_READONLY int(11) NOT NULL,
    ENABLE_OTP int(11) NOT NULL,
    PASSWORD_PHONE char(32) NOT NULL,
    NAME char(128) NOT NULL,
    COUNTRY char(32) NOT NULL,
    CITY char(32) NOT NULL,
    STATE char(32) NOT NULL,
    ZIPCODE char(16) NOT NULL,
    ADDRESS char(128) NOT NULL,
    LEAD_SOURCE char(32) NOT NULL,
    PHONE char(32) NOT NULL,
    EMAIL char(48) NOT NULL,
    COMMENT char(64) NOT NULL,
    ID_DOCUMENT char(32) NOT NULL,
    STATUS char(16) NOT NULL,
    REGDATE datetime NOT NULL,
    LASTDATE datetime NOT NULL,
    LEVERAGE int(11) NOT NULL,
    AGENT_ACCOUNT int(11) NOT NULL,
    TIMESTAMP int(11) NOT NULL,
    BALANCE double NOT NULL,
    PREVMONTHBALANCE double NOT NULL,
    PREVBALANCE double NOT NULL,
    CREDIT double NOT NULL,
    INTERESTRATE double NOT NULL,
    TAXES double NOT NULL,
    SEND_REPORTS int(11) NOT NULL,
    MQID int(10) UNSIGNED NOT NULL,
    USER_COLOR int(11) NOT NULL,
    EQUITY double NOT NULL,
    MARGIN double NOT NULL,
    MARGIN_LEVEL double NOT NULL,
    MARGIN_FREE double NOT NULL,
    CURRENCY char(16) NOT NULL,
    API_DATA blob DEFAULT NULL,
    MODIFY_TIME datetime NOT NULL,
    PRIMARY KEY (ID),
    INDEX IDX_JOIN USING BTREE (LOGIN, REPORT_ID)
  )
  ENGINE = MEMORY;


CREATE TABLE `MT4_USERS` (
  ID bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    REPORT_ID tinyint(4) UNSIGNED NOT NULL,
    LOGIN int(11) NOT NULL,
    `GROUP` char(16) NOT NULL,
    ENABLE int(11) NOT NULL,
    ENABLE_CHANGE_PASS int(11) NOT NULL,
    ENABLE_READONLY int(11) NOT NULL,
    ENABLE_OTP int(11) NOT NULL,
    PASSWORD_PHONE char(32) NOT NULL,
    NAME char(128) NOT NULL,
    COUNTRY char(32) NOT NULL,
    CITY char(32) NOT NULL,
    STATE char(32) NOT NULL,
    ZIPCODE char(16) NOT NULL,
    ADDRESS char(128) NOT NULL,
    LEAD_SOURCE char(32) NOT NULL,
    PHONE char(32) NOT NULL,
    EMAIL char(48) NOT NULL,
    COMMENT char(64) NOT NULL,
    ID_DOCUMENT char(32) NOT NULL,
    STATUS char(16) NOT NULL,
    REGDATE datetime NOT NULL,
    LASTDATE datetime NOT NULL,
    LEVERAGE int(11) NOT NULL,
    AGENT_ACCOUNT int(11) NOT NULL,
    TIMESTAMP int(11) NOT NULL,
    BALANCE double NOT NULL,
    PREVMONTHBALANCE double NOT NULL,
    PREVBALANCE double NOT NULL,
    CREDIT double NOT NULL,
    INTERESTRATE double NOT NULL,
    TAXES double NOT NULL,
    SEND_REPORTS int(11) NOT NULL,
    MQID int(10) UNSIGNED NOT NULL,
    USER_COLOR int(11) NOT NULL,
    EQUITY double NOT NULL,
    MARGIN double NOT NULL,
    MARGIN_LEVEL double NOT NULL,
    MARGIN_FREE double NOT NULL,
    CURRENCY char(16) NOT NULL,
    API_DATA blob DEFAULT NULL,
    MODIFY_TIME datetime NOT NULL,
    PRIMARY KEY (ID),
    UNIQUE KEY IDX_LOGIN_REPORT_ID (`LOGIN`,`REPORT_ID`)
) 
ENGINE=InnoDB 
ROW_FORMAT=COMPRESSED

数据是从内存表TQueue中的队列中选择的。

我需要插入表 MT4_USERS。现有的字符串应该更新,所以我执行 ON DUPLICATE KEY UPDATE。

为了确保不发生死锁,我尝试在 INSERT ON DUPLICATE KEY UPDATE 之前进行排他锁。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
— hold exclusive lock on MT4_USERS table
SELECT 
u.* 
FROM 
TQueue q 
INNER JOIN MT4_USERS u USING (LOGIN, REPORT_ID) 
FOR UPDATE;

— insert to MT4_USERS form TQueue ON DUPLICATE KEY UPDATE
INSERT MT4_USERS (REPORT_ID, LOGIN, `GROUP`, ENABLE …)
SELECT
    REPORT_ID, LOGIN, `GROUP`, ENABLE, ENABLE_CHANGE_PASS …
FROM TQueue
ORDER BY ID ASC
ON DUPLICATE KEY UPDATE
    `GROUP` = VALUES(`GROUP`),
    ENABLE = VALUES(ENABLE),
    ENABLE_CHANGE_PASS = VALUES(ENABLE_CHANGE_PASS),
    …
COMMIT;

理想情况下,我想为表 MT4_USERS 的字符串设置排它锁,使其与表 TQueue 的字符串及其字段(LOGIN、REPORT)一致,以便在不死锁的情况下执行 INSERT ON DUPLICATE KEY UPDATE。

但是我在并行事务中遇到死锁。它执行相同的操作,但使用表 MT4_TRADES。它还使 LEFT JOIN MT4_USERS。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
— hold exclusive lock on MT4_TRADES table
SELECT
t.*
FROM
TQueue q
INNER JOIN MT4_TRADES t USING (TICKET, REPORT_ID)
FOR UPDATE;

— insert to MT4_TRADES form TQueue ON DUPLICATE KEY UPDATE
INSERT MT4_TRADES (REPORT_ID, TICKET, LOGIN, SYMBOL, …)
SELECT
q.REPORT_ID, q.TICKET, q.LOGIN, q.SYMBOL, …
FROM
TQueue q
LEFT JOIN MT4_USERS mu USING(REPORT_ID, LOGIN)
ORDER BY
q.ID ASC
ON DUPLICATE KEY UPDATE
LOGIN = VALUES(LOGIN)
,SYMBOL = VALUES(SYMBOL)
,DIGITS = VALUES(DIGITS)
,…
COMMIT;

SHOW ENGINE INNODB STATUS
------------------------
LATEST DETECTED DEADLOCK
------------------------
2018-05-05 00:24:35 0x7fd53e5a6700
*** (1) TRANSACTION:
TRANSACTION 178168806, ACTIVE 1 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 17 lock struct(s), heap size 1136, 45 row lock(s)
MySQL thread id 17661615, OS thread handle 140555520485120, query id 4591798647 event_scheduler Sending data
SELECT u.* FROM TQueue q INNER JOIN MT4_USERS u USING (LOGIN, REPORT_ID) FOR UPDATE
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 876643 page no 46339 n bits 952 index IDX_LOGIN_REPORT_ID of table `Developer`.`MT4_USERS` trx id 178168806 lock_mode X locks rec but not gap waiting
*** (2) TRANSACTION:
TRANSACTION 178168783, ACTIVE 3 sec starting index read
mysql tables in use 6, locked 4
8870 lock struct(s), heap size 1138896, 22372 row lock(s), undo log entries 1876
MySQL thread id 17661574, OS thread handle 140553850873600, query id 4591797640 event_scheduler
INSERT MT4_TRADES
  (
    REPORT_ID, TICKET, LOGIN, SYMBOL, DIGITS, CMD, VOLUME, OPEN_TIME, OPEN_PRICE, SL, TP, CLOSE_TIME,
    EXPIRATION, REASON, CONV_RATE1, CONV_RATE2, COMMISSION, COMMISSION_AGENT, SWAPS, CLOSE_PRICE,
    PROFIT, TAXES, COMMENT, INTERNAL_ID, MARGIN_RATE, `TIMESTAMP`, MAGIC, GW_VOLUME, GW_OPEN_PRICE,
    GW_CLOSE_PRICE, MODIFY_TIME, CURRENCY, DELIMER, RATE, TICKET_STATUS
  )
  SELECT
    q.REPORT_ID, q.TICKET, q.LOGIN, q.SYMBOL, q.DIGITS, q.CMD, q.VOLUME, q.OPEN_TIME, q.OPEN_PRICE, q.SL, q.TP, q.CLOSE_TIME,
    q.EXPIRATION, q.REASON, q.CONV_RATE1, q.CONV_RATE2, q.COMMISSION, q.COMMISSION_AGENT, q.SWAPS, q.CLOSE_PRICE,
    q.PROFIT, q.TAXES, q.COMMENT, q.INTERNAL_ID, q.MARGIN_RATE, q.`TIMESTAMP`, q.MAGIC, q.GW_VOLUME, q.GW_OPEN_PRICE,
    q.GW_CLOSE_PRICE, q.MODIFY_TIME, mu.CURRENCY, mu.DELIMER, IF(mu.CURRENCY = 'USD', 1, SearchOfRate(q.REPORT_ID, mu.CURRENCY, 'USD', q.MODIFY_TIME, -1)), IF(q.`
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 876643 page no 46339 n bits 952 index IDX_LOGIN_REPORT_ID of table `Developer`.`MT4_USERS` trx id 178168783 lock_mode X locks rec but not gap
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 876643 page no 46338 n bits 952 index IDX_LOGIN_REPORT_ID of table `Developer`.`MT4_USERS` trx id 178168783 lock_mode X locks rec but not gap waiting
*** WE ROLL BACK TRANSACTION (1)

在 SELECT … INNER JOIN … FOR UPDATE 的情况下,字符串的顺序会被锁定,以及如何避免死锁?

最佳答案

死锁发生在您的 MT4_USERS 表的索引上,我相信您已经注意到了。这就是为什么两个查询显然在应用程序的不同部分发生冲突。

此查询

SELECT u.* 
  FROM TQueue q 
 INNER JOIN MT4_USERS u USING (LOGIN, REPORT_ID) 
   FOR UPDATE;

锁定整个MT4_USERS表。为什么?您可以只锁定一行吗?这是 SELECT ... FOR UPDATE 所针对的常见用例。

或者也许您应该跳过SELECT FOR UPDATE锁定和事务,而只独立执行INSERT ... ON DUPLICATE KEY UPDATE操作。如果两个不同的操作更新相同的行,那么后面的操作将确定最终值。单个查询给出一致的结果。

(我猜测了一些事情,因为您没有显示死锁中涉及的两个查询。)

关于mysql - 在 SELECT … INNER JOIN … FOR UPDATE 的情况下,字符串的顺序会被锁定,以及如何避免死锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50189097/

相关文章:

php - 如何将两个选择框值发送到php

sql - 使用 group by 和 order by 的 sql 查询问题

c++ - 如何理解 C++ Concurrency in Action 中同一类的实例导致死锁问题?

c# - 试图防止此死锁发生在我在 SQL Server 上的 .NET 代码中

PHP - timezone_name_from_abbr 显示错误的时区

MySql 读取 DaaDir 错误,因此无法访问它

mysql - 序列化连接以故障转移写入副本

php - 使用 PHP7 MySQLi 准备语句将 BIT(M) 列值插入 MySQL5.7

MySQL 范围查询很慢

deadlock - 编程中死锁问题的发生频率如何?