问题描述
我创建了一个带有(date,name,id,money_spent)列的MysqL表,并以(date,name,id)作为主键(因为此组合始终是唯一的),并且InnoDb是存储引擎。
date-日期数据类型, 名称-varchar(30), id-varchar(100),
我有一个逻辑,其中在多个更新查询中并行触发。对于例如(现在考虑这两个)
T1:
update test_database.users set money_spend = 1000
where date between "2020-01-01" and "2020-01-10"
and name = "John";
T2:
update test_database.users set money_spend = 1005
where date between "2020-01-01" and "2020-01-10"
and name = "Tammy";
并行运行这2个事务会导致T2超时,因为T1获得了锁定。
事务隔离级别-可重复的读取隔离级别
InnoDb引擎状态
LOCK WAIT 19543 lock struct(s),heap size 376,1407774 row lock(s)
MysqL thread id 105402,OS thread handle 0x2ba11d68a700,query id 16862373 10.11.3.212 connect-dev Sending data
update test_database.users set money_spend = 1005 where date between "2020-01-01" and "2020-01-10" and name = "Tammy";
------- TRX HAS BEEN WAITING 2 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 154606 page no 243093 n bits 78 index `PRIMARY` of table `test_database`.`users` trx id 44023639357 lock_mode X waiting
Record lock,heap no 78 PHYSICAL RECORD: n_fields 30; compact format; info bits 0
0: len=3; bufptr=0x2b9fea4e2d5e; hex= 8fc95b; asc [;;
1: len=8; bufptr=0x2b9fea4e2d61; hex= 4170706e65787573; asc John;;
2: len=8; bufptr=0x2b9fea4e2d69; hex= 3230393835373832; asc 20985782;;
3: len=6; bufptr=0x2b9fea4e2d71; hex= 000a3f47ff22; asc ?G ";;
4: len=7; bufptr=0x2b9fea4e2d77; hex= 0f011040350d5b; asc @5 [;;
查看innoDb状态,似乎T2试图获取以“ John”为名称的记录的锁定,但是由于以“ John”为名称的记录已被T1锁定,因此T2超时。 >
问题:由于InnoDb支持行级锁定,因此transaction(T1)不应仅对日期在“ 2020-01-01”和“ 2020-01-10”之间且名称为“ John”的记录获取锁定。为什么由于索引的名称部分不同(T2以“ Tammy”作为名称)而导致T2事务超时?
解决方法
将PK重新排列为PRIMARY KEY(name,date,id)
。这样,所有需要的行(在任一查询中)都将在表中相邻。这比分散行(用(date,name,...)
)更有效。
加快查询速度将有助于一个查询在另一查询有可能与之冲突之前完成。
使行连续可以避免某些“间隙锁定”情况。 (我不知道这在这里是否相关,但似乎与此有关。)
使用=
测试的列需要在范围测试之前 。
更多索引技巧:http://mysql.rjweb.org/doc.php/index_cookbook_mysql
如果id
是AUTO_INCREMENT
,则模式是“让PK中先放置一些列以加快某些查询的速度,然后在末尾附加id
以使确保它是唯一的(我不知道那是您在做什么;但这与立即进行的讨论无关。)