MySQL锁分类
一,Mysql锁分类
二、按粒度分
1,全局锁
是对整个数据库实例加锁,加锁后整个实例处于只读状态,后续的DML、DDL语句以及已经执行更新操作的事务提交语句都将被阻塞。一般用于数据库备份、恢复等操作。
--加全局锁
flush tables with read lock;
-- 执行数据库备份命令,比如使用mysqldump
mysqldump -u username -p database_name > backup.sql
--释放锁
unlock tables;
2,表级锁
每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率高,并发度低,MyISAM、InnoDB、BDB等存储引擎都会应用。
具体表现为三种锁:表锁、元数据锁、意向锁
2.1 表锁
表锁分为表读锁和写锁两种(注意和行锁的"共享锁"“独占锁”有区别):
- 读锁:
- 所有线程都可对该表进行读操作,但不允许进行写操作,包括当前线程也不行;
- 当锁住了A表之后,就只能对A表进行读操作,不能对其他表进行操作;
- 写锁:只有当前线程可以对表进行读、写操作,其他线程的读、写操作都会等待,直到锁被释放为止;
-- 读锁
LOCK TABLE table_name [ AS alias_name ] READ
# sql
UNLOCK tables
-- 写锁
LOCK TABLE table_name [AS alias_name] [ LOW_PRIORITY ] WRITE
# sql
UNLOCK tables
2.2 元数据锁
元数据锁( MDL)锁定数据库对象的元数据,如表结构;
MDL锁主要作用是维护表元数据的数据一致性,当某一张表有未提交的事务时,其他的连接不能修改表的结构,加锁过程是系统自动控制,无需显式使用。
- 当对一张表进行增删改查的时候,会加元数据共享锁( SHARED_READ 或 SHARED_WRITE );
- 当对表结构进行变更操作的时候,会加元数据排他锁( EXCLUSIVE )。
--查看数据库中的元数据锁情况:
select object_type,object_schema,object_name,lock_type,lock_duration
from performance_schema.metadata_locks;
2.3 意向锁
为了解决行锁与表锁的冲突, 在 InnoDB 引擎中引入了意向锁, 加行锁时同时也会给该表加上意向锁,这样加表锁时就不需要去检查每行是否有行锁了;
- 意向共享锁( IS ):由语句select … lock in share mode添加,与表读锁是兼容的;
- 意向排他锁( IX ):由insert、update、delete、select…for
update添加,与表读锁、写锁都是互斥的。
--查看数据库中的意向锁和行锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data
from performance_schema.data_locks;
3,页级锁
由数据库底层数据存储结构可知,一张表可以存储成百上千万条数据,而数据库底层是以页为单位的,一页大小为16KB,所以一张数据库的表有可能会有很多很多页的数据。
“页锁”开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁;
“页锁”现实业务中使用较少,仅做了解即可。
4,行级锁
行级锁是Mysql中锁定粒度最细的一种锁,仅 InnoDB支持,MyISAM不支持;
行级锁能大大减少数据库操作的冲突,数据库并发能力极高,但由于其粒度小,加锁的开销大,且容易出现死锁的情况。
- 共享锁(S):对某一资源加共享锁,自身可以进行读写操作,其他事务可以读,但不能写。
-- 加共享锁
select * from table lock in share mode
-- for share是Mysql8.0新增特性,是lock in share mode的替代品, 支持nowait、skip locked和of tbl_name选项
select * from table for share
- 排他锁(X):对某一资源加排他锁,自身可以进行读写操作,其他事务不能再在其上加其他的锁(共享锁或排它锁)。
-- 加排它锁
select * from table for update
Mysql的"当前读"都会加行锁,"insert、update、delete"自动加,“select for update”、 "select lock in share mode"在事务中手动加。
而"快照读"不会加锁,如普通"select * from *"
注意:排他锁指的是其他事务不能再在其上加其他的锁,而不是说其它事务不能读。select语句默认不会加任何锁类型,所以其它事务是可以通过普通select语句查询到“排它锁”的数据的。
注意:行级锁都是基于索引的,如果一条SQL语句没有用到索引,是不会使用行级锁的,会自动升级为表锁锁住所有数据。
-- 查看意向锁和行锁情况
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data
from performance_schema.data_locks;
行级锁可以细分为记录锁、间隙锁、临键锁:
4.1 记录锁( Record Lock )
又叫“行锁”,锁定单个行记录,防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持。
4.2 间隙锁( Gap Lock )
锁定索引记录之间的一个范围(开区间,不包括双端端点),用于防止其他事务在范围内插入数据产生“幻读” ,但不包含记录本身。在RR隔离级别下支持。
4.3 临键锁( Next-Key Lock )
结合了行锁和间隙锁的特性,同时锁住数据,并锁住数据前后的间隙(左开右闭区间)。在RR隔离级别下支持。
默认情况下,innodb使用临键锁( Next-Key Lock )来锁定记录。
各种锁触发条件:
- 使用普通索引检索时,不管是何种查询,只要加锁,都会产生间隙锁(Gap Lock)。
- 针对唯一索引进行等值匹配检索时:对已存在的记录加锁,会自动优化为行锁;对不存在的记录加锁时,会优化为间隙锁(Gap Lock)。
- 针对唯一索引进行范围匹配检索时:存在的记录会产生行锁,对于满足查询条件但不存在的数据产生间隙锁,加在一起就是临键锁(Next-Key Lock)。
针对唯一索引进行范围匹配检索,8.0.17 版本是前开后闭,而 8.0.18 版本及以后,修改为了前开后开区间;
三、按兼容性分
- 读锁、共享锁(S);
- 写锁、独占锁(X);
在按粒度分时,各粒度锁都分别说明此两个类型锁了,此处不在赘述,唯一要再次强调的一点是:表级锁的"读锁"和行级锁的"共享锁",表级锁的"写锁"和行级锁的"独占锁",冲突检测并不一样!!!
所以,很多人喜欢把"共享锁"叫做"读锁",“独占锁"叫做"写锁”,个人并不赞同这么叫,容易照成概念混淆。
四、按加锁态度分
乐观锁与悲观锁是一个抽象的概念描述,是对锁的一种态度,并不是一种真正的锁。可以简单理解为:乐观锁是人工手动控制冲突的一种技术实现手段,而悲观锁就是上文中讲到的mysql中自带的各种真正的锁。
1,乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不加锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用“版本号机制”来实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
2,悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。上文说到的全局锁、表锁、页锁、行锁等等,都是在做操作之前先上锁,也就是所谓的悲观锁。
五、死锁
死锁也不是一种真正的锁,是指两个或两个以上的事务在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等的进程称为死锁进程。
遇到死锁可以执行如下的查询语句观察等待的事务:
-- 查看当前的事务
select * from information_schema.innodb_trx;
-- 查看当前锁定的事务
select * from information_schema.innodb_locks;
-- 查看当前等锁的事务
select * from information_schema.innodb_lock_waits;
-- 删除事务
kill 进程号;
-- 未提交事务自动回滚时间
set innodb_lock_wait_timeout = 10;
我的另一篇文章《MySQL锁机制》对部分锁进行了更加深入和全面的描述,感兴趣的可以去看下:
链接地址:https://blog.csdn.net/bt_xxx/article/details/89553991