mysql手册12_锁机制
锁是协调多个线程并发访问某一资源的机制(避免资源的争抢)
锁的分类
- 以对数据库的操作粒度划分:表锁(操作时锁定整个表)、行锁(锁定当前行)
- 以对数据库的操作类型划分:读锁(共享锁)、写锁(排他锁)
共享锁:多个事务可并发读取数据,但任何事务都不可获取数据的排它锁(写),除非已释放所有共享锁
排它锁:某个事务获得了排它锁,其他事务不能读取也不能修改
各存储引擎对锁的支持情况:
存储引擎 | 表级锁 | 行级锁 | 页面锁 |
---|---|---|---|
MyISAM | 支持 | 不支持 | 不支持 |
InnoDB | 支持 | 支持 | 不支持 |
MEMORY | 支持 | 不支持 | 不支持 |
BDB | 支持 | 不支持 | 支持 |
3种锁的特性:
锁类型 | 特点 |
---|---|
表级锁 | 偏向MyISAM存储引擎,开销小,加锁快,不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低 |
行级锁 | 偏向InnoDB存储引擎,开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度最高 |
页面锁 | 开销和加锁时间中等,会出现死锁,锁定粒度中等,并发度一般 |
MyISAM存储引擎的表锁
在执行查询语句前,MyISAM会 自动 给涉及的表加上 读锁
在执行更新操作前,MyISAM会 自动 给涉及的表加上 写锁
对 MyISAM 表的读操作,不会阻塞其他事务对同一个表的读操作,但会阻塞其他事务对该表的写操作
对 MyISAM 表的写操作,则会阻塞其他事务对同一表的读和写操作
所以 MyISAM 不适合做以写为主的表的存储引擎
列举当前状态在表缓存中当前被打开的表,In_use不等于0表示该表正在被锁定
show open tables
+--------------------+---------------------------+--------+-------------+
| Database | Table | In_use | Name_locked |
+--------------------+---------------------------+--------+-------------+
| mysql | default_roles | 0 | 0 |
| mysql | check_constraints | 0 | 0 |
| mysql | slave_master_info | 0 | 0 |
................
................
................
查看表锁的情况
show status like 'Table_locks%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Table_locks_immediate | 9 |
| Table_locks_waited | 0 |
+-----------------------+-------+
Table_locks_immediate 表示能够立即获得表级锁的次数
Table_locks_waited 表示需要等待获取表锁的次数,该值较高说明存在严重的表级锁的争用情况
InnoDB存储引擎的行锁
InnoDB 与 MyISAM 的最大不同有两点:支持事务、采用行级锁
并发事务带来的问题
问题 | 含义 |
---|---|
丢失更新 | 多个事务操作同一行数据时,后提交的事务修改的值会覆盖前面提交的事务修改的值 |
脏读 | 一个事务访问到了另一个事务还没有提交的数据 |
不可重复读 | 同一事务中执行了两次相同的查询,得到的结果不一致 |
幻读 | 事务在插入已经检查过不存在的记录时,惊奇的发现这些数据已经存在了 |
使用事务的隔离级别来解决以上问题
隔离级别 | 丢失更新 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
Read uncommitted | X | Y | Y | Y |
Read committed | X | X | Y | Y |
Repeatable read (默认) | X | X | X | Y |
Serializable | X | X | X | X |
注:当 Where 查询条件中的字段没有索引,或者索引失效时,InnoDB 默认的行锁更新操作将变为表锁!应该尽量避免!
间隙锁
间隙锁(Gap Lock)是Innodb在可重复读提交下为了解决幻读问题时引入的锁机制,在更新操作中如果进行了范围查询,此时的行级锁将无法满足要求,需要对一定范围的行数据进行加锁
举例:数据库中存在id为1,3,4,5,6 的数据,缺少id为2的数据
A事务执行以下更新操作,此时范围 id<5 的行数据将被加锁
update t_user set name = "zhangsan" where id < 5
同时B事务执行以下 insert 语句,此时B事务将进入阻塞状态,直至A事务提交
insert into t_user values(2,'lisi');
注:尽量缩小范围区间以避免间隙锁
查看InnoDB的行锁争用情况:
show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 0 |
| Innodb_row_lock_time_avg | 0 |
| Innodb_row_lock_time_max | 0 |
| Innodb_row_lock_waits | 0 |
+-------------------------------+-------+
Innodb_row_lock_current_waits 当前正在等待行锁的数量
Innodb_row_lock_time 锁定的总时长
Innodb_row_lock_time_avg 锁定的平均时长
Innodb_row_lock_time_max 锁定的最大时长
Innodb_row_lock_waits 系统启动至今总共等待的次数
总结:
虽然 InnoDB 在锁机制上的性能损耗比 MyISAM 高,但在整体并发处理能力方面远远优于 MyISAM
优化建议:
- 尽可能让所有数据的检索通过索引完成,以避免行锁升级为表锁
- 合理涉及索引,尽量缩小锁的范围
- 尽可能减少索引条件和索引范围,避免间隙锁
- 尽量控制事务大小,减少锁定的资源量和锁定的时间长度
- 尽可能使用低级别的事务隔离(在满足业务需求的前提下)