文章目录
- 1. 锁是什么?
- 2. 全局锁
- 2.1 相关语法
- 2.2 特点
- 3. 表级锁
- 3.1 表锁
- 3.1.1 共享读锁(S)
- 3.1.2 排它写锁(X)
- 3.2 元数据锁(MDL)
- 3.2 意向锁(IS、IX)
- 4. 行级锁
- 4.1 行锁
- 5. 死锁
- 5.1 死锁检测
- 5.2 避免死锁
1. 锁是什么?
- 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
- MySQL中的锁,为了尽可能提高数据库的并发量,每次锁定的数据范围越小越好,越小的锁其耗费的系统资源越多,系统性能下降。为在高并发响应和系统性能两方面进行平衡,这样就产生了“锁粒度”的概念。 按照锁的粒度分,分为以下三类:
- 全局锁:锁定数据库中的所有表。
- 表级锁:每次操作锁住整张表。
- 行级锁:每次操作锁住对应的行数据。
- 从锁的角度来说,表级锁适合以查询为主,只有少量按索引条件更新数据的应用,如 Web 应用。而行级锁更适合于有大量按索引条件,同时又有并发查询的应用,如一些在线事务处理( OLTP)系统。
2. 全局锁
-
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
对数据库进行进行逻辑备份之前,先对整个数据库加上全局锁,一旦加了全局锁之后,其他的DDL、DML全部都处于阻塞状态,但是可以执行DQL语句,也就是处于只读状态,而数据备份就是查询操作。那么数据在进行逻辑备份的过程中,数据库中的数据就是不会发生变化的,这样就保证了数据的一致性和完整性。
2.1 相关语法
-
加全局锁:
flush tables with read lock ;
-
数据备份:
mysqldump -uroot –p1234 db_name > db_name.sql
-
释放锁:
unlock tables ;
2.2 特点
-
数据库中加全局锁,是一个比较重的操作,存在以下问题:
-
如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
-
如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。
在InnoDB引擎中,我们可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致性数据备份。
mysqldump --single-transaction -uroot –p123456 db_name > db_name.sql
-
3. 表级锁
3.1 表锁
-
对于表锁,分为两类:表共享读锁(read lock)、表排它写锁(write lock) 。
-
语法:
-
加锁:
lock tables 表名... read/write
-
释放锁:
unlock tables
-
3.1.1 共享读锁(S)
-
共享锁的代号是 S,是
Share
的缩写,也可称为读锁。是一种可以查看但无法修改和删除的数据锁。 -
共享锁的锁粒度是行或者元组(多个行)。一个事务获取了共享锁之后,可以对锁定范围内的数据执行读操作。会阻止其它事务获得相同数据集的排他锁。简单来说就是获得读锁的事务A与其他事务B都可以对锁范围内的数据进行读操作,但都不能进行写操作。
3.1.2 排它写锁(X)
-
排他锁的代号是 X,是
eXclusive
的缩写,也可称为写锁,是基本的锁类型。 -
排他锁的粒度与共享锁相同,也是行或者元组。一个事务获取了排他锁之后,可以对锁定范围内的数据执行写操作。允许获得排他锁的事务更新数据,阻止其它事务取得相同数据集的共享锁和排他锁。简单来说就是获得写锁的事务A可以对锁范围内的数据进行读和写,但其他事务B不能对锁范围内的数据进行读和写。
3.2 元数据锁(MDL)
-
meta data lock , 元数据锁,简写MDL。MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与DDL冲突,保证读写的正确性。这里的元数据,大家可以简单理解为就是一张表的表结构。 也就是说,某一张表涉及到未提交的事务时,是不能够修改这张表的表结构的。
-
在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)。
-
常见的SQL操作时,所添加的元数据锁:
对应SQL 锁类型 说明 lock tables xxx read / write SHARED_READ_ONLY / SHARED_NO_READ_WRITE select 、select … lock in share mode SHARED_READ 与SHARED_READ、 SHARED_WRITE兼容,与 EXCLUSIVE互斥 insert 、update、 delete、select … for update SHARED_WRITE 与SHARED_READ、 SHARED_WRITE兼容,与 EXCLUSIVE互斥 alter table … EXCLUSIVE 与其他的MDL都互斥
3.2 意向锁(IS、IX)
-
为了允许行锁和表锁共存,实现多粒度锁机制,
InnoDB
还有两种内部使用的意向锁。意向锁是一种表锁,锁定的粒度是整张表,分为**意向共享锁( IS)和意向排他锁( IX)**两类。 -
添加意向共享锁:
select ... lock in share mode
-
添加意向排它锁:
insert、update、delete、select...for update
-
其中共享锁、排他锁、意向共享锁、意向排他锁相互之间的兼容/互斥关系如下表所示,其中 Y 表示相容, N 表示互斥。
参数 X S IX IS X(排他锁) N N N N S(共享锁) N Y N Y IX(意向排他锁) N N Y Y IS(意向共享锁) N Y Y Y 如果一个事务请求的锁模式与当前的锁兼容,
InnoDB
就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。
4. 行级锁
-
在 MySQL 中,
InnoDB
行锁通过给索引上的索引项加锁来实现,如果没有索引,InnoDB
将通过隐藏的聚簇索引来对记录加锁。 -
InnoDB
的行锁是针对于索引加的锁,如果不通过索引条件检索数据,那么InnoDB
将对表中的所有记录加锁,此时就会升级为表锁。 -
InnoDB
支持 3 种行锁定方式:-
行锁( Record Lock):直接对索引项加锁。
select * from student where id = 1 for update;
-
间隙锁( Gap Lock):锁加在索引项之间的间隙,也可以是第一条记录前的“间隙”或最后一条记录后的“间隙”。
select * from student where id > 2 and id < 5 for update;
-
Next-Key Lock:行锁与间隙锁组合起来用就叫做 Next-Key Lock。 前两种的组合,对记录及其前面的间隙加锁。
select * from student where id > 4 for update;
-
4.1 行锁
-
InnoDB
实现了以下两种类型的行锁:- 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
- 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
-
常见的SQL语句,在执行时,所加的行锁如下:
SQL 行锁类型 说明 INSERT … 排他锁 自动加锁 UPDATE … 排他锁 自动加锁 DELETE … 排他锁 自动加锁 SELECT(正常) 不加任何 锁 SELECT … LOCK IN SHARE MODE 共享锁 需要手动在SELECT之后加LOCK IN SHARE MODE SELECT … FOR UPDATE 排他锁 需要手动在SELECT之后加FOR UPDATE
5. 死锁
- 定义:死锁是指两个或两个以上的事务在执行过程中,因争夺资源而造成的一种互相等待的现象。 就是所谓的锁资源请求产生了回路现象,即死循环,此时称系统处于死锁状态或系统产生了死锁。常见的报错信息为“Deadlock found when trying to get lock…”。
- 解决方案:死锁发生以后,只有部分或完全回滚其中一个事务,才能打破死锁。多数情况下只需要重新执行因死锁回滚的事务即可。
5.1 死锁检测
InnoDB
的并发写操作会触发死锁,同时InnoDB
也提供了死锁检测机制。通过设置innodb_deadlock_detect
参数的值来控制是否打开死锁检测。
-
innodb_deadlock_detect = ON
:默认值,打开死锁检测。数据库发生死锁时,系统会自动回滚其中的某一个事务, 让其它事务可以继续执行。 -
innodb_deadlock_detect = OFF
:关闭死锁检测。发生死锁时,系统会用锁等待来处理。锁等待是指在事务过程中产生的锁,其它事务需要等待上一个事务释放锁,才能占用该资源。如果该事务一直不释放,就需要持续等待下去,直到超过了锁等待时间。 当超过锁等待允许的最大时间,就会出现死锁,然后当前事务执行失败,自动执行回滚操作。
5.2 避免死锁
- 在实际应用中,我们要尽量防止锁等待现象的发生,下面介绍几种避免死锁的方法:
- 如果不同程序会并发存取多个表,或者涉及多行记录时,尽量约定以相同的顺序访问表,这样可以大大降低死锁的发
生。 - 业务中要及时提交或者回滚事务,可减少死锁产生的概率。
- 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率。
- 对于非常容易产生死锁的业务部分,可以尝试使用升级锁粒度,通过表锁定来减少死锁产生的概率(表级锁不会产生死锁)。
- 如果不同程序会并发存取多个表,或者涉及多行记录时,尽量约定以相同的顺序访问表,这样可以大大降低死锁的发