面试系列-mysql锁机制及死锁排查

MySQL
392
0
0
2022-12-17
标签   MySQL锁

多事务更新同一行数据时加独占锁避免脏写

如果有事务在表里执行增删改操作,那在行级会加独占锁,此时其实同时会在表级加一个意向独占锁;如果有事务在表里执行查询操作,那么会在表级加一个意向共享锁。其实平时操作数据库,比较常见的两种表锁,反而是更新和查询操作加的意向独占锁和意向共享锁,但是可以忽略这个意向独占锁和意向共享锁,因为两种意向锁根本不会互斥;

锁的类型

表锁
读锁(read lock)

也叫共享锁(shared lock) 针对同一份数据,多个读操作可以同时进行而不会互相影响(select);

写锁(write lock)

也叫排他锁(exclusive lock) 当前操作没完成之前,会阻塞其它读和写操作(update、insert、delete);

默认存储引擎:MyISAM

特点

1. 读锁会阻塞写操作,不会阻塞读操作;2. 写锁会阻塞读和写操作;

1. 对整张表加锁;2. 开销小;3. 加锁快;4. 无死锁;5. 锁粒度大,发生锁冲突概率大,并发性低;

MyISAM的读写锁调度是写优先,这也是MyISAM不适合做写为主表的引擎,因为写锁以后,其它线程不能做任何操作,大量的更新使查询很难得到锁,从而造成永远阻塞;

上锁

隐式上锁(默认,自动加锁自动释放):

select //上读锁
insert、update、delete //上写锁

显式上锁(手动):

lock table tableName read;//读锁 
lock table tableName write;//写锁

解锁

unlock tables;//所有锁表

查看表锁情况

show open tables;

img

表锁分析:

show status like 'table%';

1. table_locks_waited
出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次值加1),
此值高说明存在着较严重的表级锁争用情况
2. table_locks_immediate
产生表级锁定次数,不是可以立即获取锁的查询次数,每立即获取锁加1

img

行锁

读锁(read lock)

也叫共享锁(shared lock) 允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁;

写锁(write lock)

也叫排他锁(exclusive lock) 允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享锁和排他锁;

为什么上了写锁,别的事务还可以读操作?因为InnoDB有MVCC机制(多版本并发控制),可以使用快照读,而不会被阻塞。

意向共享锁(IS)

一个事务给一个数据行加共享锁时,必须先获得表的IS锁;

意向排它锁(IX)

一个事务给一个数据行加排他锁时,必须先获得该表的IX锁;

默认存储引擎:InnoDB

特点

1. 对一行数据加锁;2. 开销大;3. 加锁慢;4. 会出现死锁;5. 锁粒度小,发生锁冲突概率最低,并发性高;

上锁

隐式上锁(默认,自动加锁自动释放):

select //不会上锁
insert、update、delete //上写锁

显式上锁:

select * from tableName lock in share mode;//读锁 
select * from tableName for update;//写锁

解锁(手动)

1. 提交事务(commit2. 回滚事务(rollback3. kill 阻塞进程

查看行锁情况

show status like 'innodb_row_lock%';

1. innodb_row_lock_current_waits //当前正在等待锁定的数量

2. innodb_row_lock_time //从系统启动到现在锁定总时间长度

3. innodb_row_lock_time_avg //每次等待所花平均时间

4. innodb_row_lock_time_max //从系统启动到现在等待最长的一次所花时间

5. innodb_row_lock_waits //系统启动后到现在总共等待的次数

img

页锁

开销、加锁时间和锁粒度介于表锁和行锁之间,会出现死锁,并发处理能力一般;

行锁的实现算法

Record Lock 锁

单个行记录上的锁 Record Lock总是会去锁住索引记录,

如果InnoDB存储引擎表建立的时候没有设置任何一个索引,

这时InnoDB存储引擎会使用隐式的主键来进行锁定;

Gap Lock(间隙) 锁

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的

已有数据记录的索引加锁,对于键值在条件范围内但并不存在的记录。

优点:解决了事务并发的幻读问题

不足:因为query执行过程中通过范围查找的话,他会锁定争个范围内所有的索引键值,

即使这个键值并不存在。

间隙锁有一个致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,

而造成锁定的时候无法插入锁定键值范围内任何数据。

在某些场景下这可能会对性能造成很大的危害。

Next-key Lock

同时锁住数据+间隙锁
在Repeatable Read隔离级别下,Next-key Lock 算法是默认的行记录锁定算法。

InnoDB对行的查询默认采用Next-key算法。然而,当查询条件为等值时,且索引有唯一属性时(就是只锁定一条记录),InnoDB存储引擎会对Next-Key Lock进行优化,将其降级为Record Lock,即仅锁住索引本身,而不是一个范围,因为此时不会产生重复读问题;

行锁特点

1. 只有通过索引条件检索数据时,InnoDB才会使用行级锁,否则会使用表级锁(索引失效,行锁变表锁);

2. 即使是访问不同行的记录,如果使用的是相同的索引键,会发生锁冲突;

3. 如果数据表建有多个索引时,可以通过不同的索引锁定不同的行;

死锁

指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象;

1. 查看死锁:show engine innodb status \G,只能看到最近一次的死锁信息;

2. 自动检测机制,超时自动回滚代价较小的事务(innodb_lock_wait_timeout 默认50s);

3. 人为解决,kill阻塞进程(show processlist);

4. wait for graph 等待图(主动检测);

5. 访问INNODB_LOCKS查看当前存在的锁情况:SELECT * FROM.INNODB_LOCKS\G;

1. row
lock_id: 16219:56:4:5
lock_trx_id: 16219
lock_mode: X,GAP
lock_type: RECORD
lock_table: `test`.`z`
lock_index: b
lock_space: 56
lock_page: 4
lock_rec: 5
lock_data: 6, 7
2. row
lock_id: 16218:56:4:5
: 16218
lock_mode: X,GAP
lock_type: RECORD
lock_table: `test`.`z`
lock_index: b
lock_space: 56
lock_page: 4
lock_rec: 5
lock_data: 6, 7
2 rows in set, 1 warning (0.00 sec)
  1. 通过表INNODB_LOCK_WAITS,可以很直观的反应当前事务的等待
mysql> SELECT * FROM.INNODB_LOCK_WAITS\G;
*****************************1.row************************
requesting_trx_id: 7311F4
requesting_lock_id: 7311F4:96:3:2
blocking_trx_id: 730FEE
blocking_lock_id: 730FEE:96:3:2
  1. information_schema 库

1. innodb_lock_waits表;

2. innodb_locks表;

3. innodb_trx表;

如何避免死锁

1. 加锁顺序一致,尽可能一次性锁定所需的数据行

2. 尽量基于primary(主键)或unique key更新数据

3. 单次操作数据量不宜过多,涉及表尽量少

4. 减少表上索引,减少锁定资源

5. 尽量使用较低的隔离级别

6. 尽量使用相同条件访问数据,这样可以避免间隙锁对并发的插入影响

7. 精心设计索引,尽量使用索引访问数据

8. 借助相关工具:pt-deadlock-logger