最近做的项目中出现了锁表的问题,导致表无法进行事务(DML)操作,经过排查发现,我们项目中的购物车表有个字段cart_id为uuid类型,并且未设置索引,这里的场景是根据cart_id进行更新内容,Mysql InnoDB行锁不使用索引锁表的时候会锁整张表。
通过命令查询锁日志找到相关锁的sql语句:
show engine innodb status;
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 485176, OS thread handle 140383913969408, query id 12481371 222.93.77.61 root updating
UPDATE `o_cart_detail` SET MODIFY_TIME = now() WHERE (`cart_id`='1')
------- TRX HAS BEEN WAITING 28 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2655 page no 4 n bits 144 index PRIMARY of table `o_cart_detail` trx id 5518915 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 15; compact format; info bits 0
通过执行计划分析sql语句:
通过执行计划来看,更新语句并未使用到索引。并且where条件的字段是非索引字段,此时的更新操作是全表锁。
此时我们的修改方案是先进行cart_id字段添加索引,保证业务正常进行。
添加非唯一索引后。再次进行执行计划分析sql语句。此时锁定的记录行从2058 -> 55;此时的更新操作是行锁。
当然最好还是使用唯一索引进行DML操作。
总结:InnoDB 的锁,与索引类型,事务的隔离级别相关。InnoDB 到底是行锁还是表锁取决于你的 SQL 语句。如果查询没有命中索引,也将退化为表锁。InnoDB 的行锁是实现在索引上的,而不是锁在物理行记录上。所以如果访问没有命中索引,也无法使用行锁,将要退化为表锁。