Mysql 在选择…内部连接…进行更新时,字符串的顺序锁定了什么?如何避免死锁?

Mysql 在选择…内部连接…进行更新时,字符串的顺序锁定了什么?如何避免死锁?,mysql,sql,deadlock,percona,Mysql,Sql,Deadlock,Percona,Persona 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', Q

Persona 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
数据从内存队列表TQUUE中选择

我需要向表MT4_用户中插入。现有字符串应该更新,所以我在重复密钥更新时会这样做

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;
为了确保没有死锁发生,我尝试在重复密钥更新时插入之前进行独占锁定

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_用户的字符串设置独占锁,该锁与表TQueue的字符串一致,并带有字段LOGIN、REPORT,以便在重复密钥更新时执行INSERT,而不会出现死锁

但我在并行事务中遇到了死锁。它执行相同的操作,但使用表MT4_TRADES。它还可以让左键加入MT4_用户

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)

在选择…内部连接…进行更新时,字符串的顺序锁定了什么?如何避免死锁?

死锁位于MT4_用户表的索引中,我相信您已经注意到了。这就是为什么应用程序不同部分中的两个查询明显冲突的原因

这个问题

SELECT u.* 
  FROM TQueue q 
 INNER JOIN MT4_USERS u USING (LOGIN, REPORT_ID) 
   FOR UPDATE;
锁定整个MT4_用户表。为什么?你能只锁一排吗?这是常见的用例,选择。。。对于更新是有意的

或者,您应该跳过选择更新锁和事务,只需执行插入。。。在重复密钥更新操作上,独立。如果两个不同的操作更新相同的行,那么后面的操作将确定最终值。单个查询提供一致的结果

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

此查询将锁定整个MT4\U USERS表。可以只锁定一行吗?»我只需要对TQueue表中存在的字符串进行独占锁定。我不需要阻塞整个表,这就是为什么我要«选择。。。在«插入»之前更新»。。。重复密钥更新时»开始执行。你为什么说查询«选择。。。对于更新»阻止整个表MT4_用户»或者您应该跳过选择更新锁和事务,只需执行插入。。。在独立的重复密钥更新操作上。»无独占锁定两个并行查询«插入。。。关于重复密钥更新»获得死锁,很明显,我已经面临这种情况。这就是为什么我想使用query«SELECT。。。提前更新。TQUUE表每1个事务可以包含100到10000行。这就是为什么我不能使用简单选择…进行更新,我必须使用«选择…内部连接..进行更新»。