在MySQL中,行锁并不是直接锁记录,而是锁索引,而索引又分为主键索引和非主键索引两种:如果一条SQL操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。在做更新删除操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即next-key locking。
有事务锁(行锁)等待,使用以下语句定位锁:
MySQL 5.7版本:
select * from information_schema.innodb_locks;
select * from information_schema.innodb_lock_waits
MySQL 8.0版本:
select * from sys.innodb_lock_waits\G
或者
select * from performance_schema.data_lock_waits\G
select PROCESSLIST_ID,PROCESSLIST_USER,PROCESSLIST_DB from performance_schema.threads where thread_id=&BLOCKING_THREAD_ID;
--以下是MySQL 8.0版本测试
create table test2(id int,status varchar(10),time date);
create index idx_test2 on test2(status,time);
alter table test2 add primary key(id);
--插入130000条数据
drop procedure insertintotest2;
DELIMITER //
create procedure insertintotest2()
begin
declare i int default 0;
LOOP_LABLE:
loop
insert into test2 values(i,'a',sysdate()-20000);
set i=i+1;
if i>=130000 then
leave LOOP_LABLE;
end if;
end loop;
end;
//
CALL insertintotest2
//
DELIMITER ;
以下语句执行时,MySQL会使用idx_test2【status,time】索引,首先锁定相关的索引记录,因为idx_test2不是主键索引,因此MySQL还会锁定主键索引。
mysql> explain update test2 set status='aa',time=now() where status='a' and time<date_sub(now(), INTERVAL 10 minute);
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | UPDATE | test2 | NULL | index | idx_test2 | PRIMARY | 4 | NULL | 2000 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
以下语句和上一条语句几乎同时执行时,该语句会先锁定主键索引【ID】,由于需要更新status的值,所以还需要锁定idx_test2【status,time】的某些索引记录
mysql> explain update test2 set status='aaaa',time=now() where id=1;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | UPDATE | test2 | NULL | range | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
关闭自动提交情况下,第一条语句锁定了idx_test2的记录,等待主键索引;第二条语句则锁定了主键索引记录,而等待idx_test2的记录,这种情况下锁就产生了
同时执行以下两个会话
会话1
update test2 set status='aa',time=now() where status='a' and time<date_sub(now(), INTERVAL 10 minute);
会话2
update test2 set status='aaaa',time=now() where id=1;
查看锁
mysql> select * from sys.innodb_lock_waits\G
*************************** 1. row ***************************
wait_started: 2023-07-27 14:38:14
wait_age: 00:00:05 -->等待时间
wait_age_secs: 5
locked_table: `test`.`test2`
locked_table_schema: test
locked_table_schema: test -->表用户
locked_table_name: test2 -->表名称
locked_table_partition: NULL
locked_table_subpartition: NULL
locked_index: PRIMARY -->主键字段
locked_type: RECORD -->行锁
waiting_trx_id: 1246241
waiting_trx_started: 2023-07-27 14:19:06
waiting_trx_age: 00:00:08
waiting_trx_rows_locked: 1
waiting_trx_rows_modified: 0
waiting_pid: 9 -->被阻塞的线程号
waiting_query: update test2 set status='aaaa',time=now() where id=1
waiting_lock_id: 139657526383016:11:6:3:139657558024224
waiting_lock_mode: X,REC_NOT_GAP
blocking_trx_id: 1246236
blocking_pid: 8 -->阻塞的线程号
blocking_query: NULL -->阻塞者正在执行的SQL,这里是已经执行完了但是没提交
blocking_lock_id: 139657526382120:11:6:3:139657558021152
blocking_lock_mode: X
blocking_trx_started: 2023-07-27 14:19:04 -->阻塞者开始执行时间
blocking_trx_age: 00:19:15 -->持有锁时间
blocking_trx_rows_locked: 130244 -->堵塞着的事务锁住了130244行记录
blocking_trx_rows_modified: 130000 -->堵塞着的事务修改了130000行记录
sql_kill_blocking_query: KILL QUERY 8
sql_kill_blocking_connection: KILL 8 -->KILL堵塞进程语句
或者
mysql> select * from performance_schema.data_lock_waits\G
*************************** 1. row ***************************
ENGINE: INNODB
REQUESTING_ENGINE_LOCK_ID: 139657526383016:11:6:3:139657558024224
REQUESTING_ENGINE_TRANSACTION_ID: 1246241
REQUESTING_THREAD_ID: 2
REQUESTING_EVENT_ID: 43
REQUESTING_OBJECT_INSTANCE_BEGIN: 139657558024224
BLOCKING_ENGINE_LOCK_ID: 139657526382120:11:6:3:139657558021152
BLOCKING_ENGINE_TRANSACTION_ID: 1246236
BLOCKING_THREAD_ID: 48 -->和performance_schema.threads表的thread_id关联可以找到PROCESSLIST_ID
BLOCKING_EVENT_ID: 8201
BLOCKING_OBJECT_INSTANCE_BEGIN: 139657558021152
select PROCESSLIST_ID,PROCESSLIST_USER,PROCESSLIST_DB from performance_schema.threads where thread_id=48;
+----------------+------------------+----------------+
| PROCESSLIST_ID | PROCESSLIST_USER | PROCESSLIST_DB |
+----------------+------------------+----------------+
| 8 | root | test |
+----------------+------------------+----------------+
通过事务ID,可以查看事务发起的账号和主机信息,KILL这个事务ID后锁就会释放
#查看下这个事务发起的账号和主机信息
select * from information_schema.processlist where id=8;
+----+------+-----------+------+---------+-------+-------+------+----------+-----------+---------------+
| ID | USER | HOST | DB | COMMAND | TIME | STATE | INFO | TIME_MS | ROWS_SENT | ROWS_EXAMINED |
+----+------+-----------+------+---------+-------+-------+------+----------+-----------+---------------+
| 8 | root | localhost | test | Sleep | 10881 | | NULL | 10881243 | 0 | 130000 |
+----+------+-----------+------+---------+-------+-------+------+----------+-----------+---------------+
#KILL未提交的事务线程ID
mysql> kill 8
--以下是MySQL 5.7版本测试
create table test1(id int,name int,cdate date,type varchar(10));
insert into test1 values(1,1,sysdate()-2,'a');
insert into test1 values(2,2,sysdate()-1,'b');
commit;
select * from test1;
会话1
update test1 set cdate=sysdate(),name=11 and id=11 and type='aa';
会话2
update test1 set cdate=sysdate(),name=11 and id=1111 and type='aa';
会话1
update test1 set cdate=sysdate(),name=11 and id=1111 and type='aa';
结果创建的索引的语句卡住
create index IDX_TEST_ID on test(id);
1、查看当前运行的所有事务
mysql> select * from information_schema.innodb_trx\G;
*************************** 1. row ***************************
trx_id: 4904
trx_state: RUNNING -->事务状态
trx_started: 2021-11-2023:38:46
trx_requested_lock_id: NULL
trx_wait_started: NULL
trx_weight: 1
trx_mysql_thread_id: 8
trx_query: NULL
trx_operation_state: NULL
trx_tables_in_use: 0
trx_tables_locked: 1
trx_lock_structs: 1
trx_lock_memory_bytes: 1136
trx_rows_locked: 1 -->事务锁住的行数
trx_rows_modified: 0
trx_concurrency_tickets: 0
trx_isolation_level: REPEATABLEREAD
trx_unique_checks: 1
trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
trx_adaptive_hash_latched: 0
trx_adaptive_hash_timeout: 0
trx_is_read_only: 0
trx_autocommit_non_locking: 0
2、查看当前出现的锁
mysql> select * from information_schema.innodb_locks;
Empty set, 1 warning (0.00 sec)
3、如果有锁,查看锁等待的对应关系
mysql> select * from information_schema.innodb_lock_waits\G;
Empty set, 1 warning (0.00 sec)
4、查看锁情况
mysql> show status like 'innodb%lock%';
+-------------------------------+--------+
| Variable_name | Value |
+-------------------------------+--------+
| Innodb_row_lock_current_waits | 0 | --当前等待锁的数量
| Innodb_row_lock_time | 102068 | --系统启动到现在,锁定的总时间长度
| Innodb_row_lock_time_avg | 51034 | --每次平均锁定的时间
| Innodb_row_lock_time_max | 51056 | --最长一次锁定时间
| Innodb_row_lock_waits | 2 | --系统启动到现在总共锁定的次数
+-------------------------------+--------+
5、查询是否锁表:
mysql> show open tables where in_use>0;
+----------+-------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+-------+--------+-------------+
| test | test1 | 1 | 0 |
+----------+-------+--------+-------------+
6、查看锁表的进程
mysql> show processlist\G;
*************************** 1. row ***************************
Id: 6
User: root
Host: localhost:46556
db: test
Command: Query -->该线程正在执行一个查询
Time: 792
State: Waiting for table metadata lock
Info: create index IDX_TEST1_ID on test1(id) -->这个语句就是卡住的创建索引语句
*************************** 2. row ***************************
Id: 8
User: root
Host: localhost:46561
db: test
Command: Sleep -->该线程正在等待客户端向它发送执行语句
Time: 1319
State:
Info: NULL
7、尝试killId: 8的sleep的会话
kill 8;
8、创建索引的会话立刻结束
mysql> create index IDX_TEST1_ID on test1(id);
Query OK, 0 rows affected (19 min 56.63 sec)
Records: 0 Duplicates: 0 Warnings: 0