一、锁的概述
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须要解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。因此,锁对数据库尤为重要,也更加复杂。
1.1、分类
1、全局锁:锁定数据库中的所有表。
2、表级锁:每次操作锁住整张表。
3、行级锁:每次操作锁住对应的行数据。
二、全局锁
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。
其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,从而保证数据的完整性。
加上全局锁:
2.1、语法结构
加锁:
FLUSH TABLES WITH READ LOCK;
执行数据备份:
MYSQLDUMP -uroot -p指定密码 数据库名称>数据库名称.sql;
解锁:
UNLOCK TABLES;
flush tables with read lock ;
update score set chines=92 where id=1;
#解锁
unlock tables;
#数据备份,可以在cmd命令行中输入
mysqldump -hlocalhost -uroot -p0219423 staff_data > D:/staff_data.sql
2.2、全局锁特点
数据库中加全局锁是一个比较重的操作,存在以下问题:
1、如果在主库上备份,那么在备份期间都不能执行更新,就业基本上陷入停摆;
2、如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志,会导致主从延迟。
在InnoDB引擎中我们可以在备份时加上参数 --single -transaction 参数来完成不加锁的一致性数据备份。
三、表级锁
表级锁,每次操作锁住整张表。锁的力度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、InnoDB、DBD等存储引擎中。
3.1、分类
对于表级锁,主要分为以下三类:
1、表锁
2、元数据所
3、意向锁
3.1.1、表锁
对于表锁,又可以分为两类:
1、表共享读锁(read lock):客户端一可以读取表数据,但不能进行写入,客户端二也是如此。
2、表独占写锁(write lock):客户端既能读取数据也可以写入数据,客户端二既不能读数据也不能写入数据。
#语法:
#1、加锁:
lock tables 表名...read/write;
#2、释放锁:
unlock tables/客户端断开连接;
3.1.2、元数据锁(MDL)
MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是为了维护表元数据的数据一致性,在表上有活动事务的时候,不可以队员数据进行写入操作。为了避免DDL与DML冲突,保证读写的正确性。
在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构变更操作的时候,加上MDL写锁(排他)。
对应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都互斥 |
#客户端一执行select操作:
begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from score;
+------+--------+--------+------+---------+
| id | name | chines | math | english |
+------+--------+--------+------+---------+
| 1 | 米老鼠 | 92 | 80 | 66 |
| 2 | 唐老鸭 | 67 | 88 | 91 |
| 3 | 杰瑞 | 76 | 71 | 77 |
| 4 | 汤姆 | 88 | 55 | 62 |
+------+--------+--------+------+---------+
4 rows in set (0.00 sec)
#客户端二执行修改表结构操作:
begin;
Query OK, 0 rows affected (0.00 sec)
mysql> alter table score add java int;
#到这里会发生阻塞,直到客户端1执行事务提交操作
查看元数据锁:
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks;
3.1.3、意向锁
为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
过程:
线程A在执行的时候先开启事务,对某一行加上行锁,然后对整张表加上一个意向锁,线程B则对这张表加上表锁,在加表锁时,会去检查这张表中意向锁的情况,通过意向锁的情况来决定加锁是否能够成功,如果意向锁和我们当前所加的表锁是兼容的,就可以加锁;反之线程B则会处于阻塞状态,直至线程A提交事务,行锁与意向锁释放。
意向锁的分类:
1、意向共享锁(IS):由语句select ... lock in share mode添加;与表锁共享锁(read)兼容,与表锁排他锁(write)互斥。
2、意向排他锁(IX):insert、update、delete、select ... for update添加;与表锁共享锁(read)及表锁排他锁(write)都互斥。意向锁之间不会互斥。
可以通过以下SQL来查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
#客户端一:
begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from score where id=1 lock in share mode;
#客户端二:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+-----------------+-----------+-----------+------------------------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+-----------------+-----------+-----------+------------------------+
| staff_data | score | NULL | TABLE | IS | NULL |
| staff_data | score | GEN_CLUST_INDEX | RECORD | S | supremum pseudo-record |
| staff_data | score | GEN_CLUST_INDEX | RECORD | S | 0x00000000022E |
| staff_data | score | GEN_CLUST_INDEX | RECORD | S | 0x00000000022F |
| staff_data | score | GEN_CLUST_INDEX | RECORD | S | 0x000000000230 |
| staff_data | score | GEN_CLUST_INDEX | RECORD | S | 0x000000000231 |
+---------------+-------------+-----------------+-----------+-----------+------------------------+
6 rows in set (0.00 sec)
mysql> lock tables score write;
#此时由于IS是意向共享锁与写锁互斥,从而导致阻塞,直至客户端一事务提交
四、行级锁
行级锁,每次操作锁住对应的行数据,锁定力度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。
在InnoDB中数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。
4.1、行级锁的分类
4.1.1、行锁(Record Lock)
锁定单个行记录的锁,防止其他事务对此行进行update和delete。在RC和RR隔离级别下都支持。
InnoDB引擎实现了以下两种类型的行锁:
1、共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
2、排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
当前锁类型\请求锁类型 | S(共享锁) | X(排他锁) |
S(共享锁) | 兼容 | 冲突 |
X(排他锁) | 冲突 | 冲突 |
SQL | 行锁类型 | 说明 |
INSERT... | 排他锁 | 自动加锁 |
UPDATE... | 排他锁 | 自动加锁 |
DELETE... | 排他锁 | 自动加锁 |
SELECT(正常) | 不加任何锁 | |
SELECT ... LOCK IN SHARE MODE | 共享锁 | 需要在SELECT之后手动加 LOCK IN SHARE MODE |
SELECT ... FOR UPDATE | 排他锁 | 需要在SELECT之后手动加 FOR UPDATE |
默认情况下,InnoDB在REPEATABLE READ事务隔离级别运行,InnoDB使用next-key锁进行搜索和扫描索引,以防止幻读。
1、针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
2、InnoDB的行锁是针对索引加的锁不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁。
#客户端一:
begin;
Query OK, 0 rows affected (0.00 sec)
select * from student where id=1;
+----+--------+------------+
| id | name | no |
+----+--------+------------+
| 1 | 黛丽丝 | 2000100101 |
+----+--------+------------+
1 row in set (0.00 sec)
#此时还未加上锁
mysql> select * from student where id=1 lock in share mode;
+----+--------+------------+
| id | name | no |
+----+--------+------------+
| 1 | 黛丽丝 | 2000100101 |
+----+--------+------------+
1 row in set (0.00 sec)
#客户端二:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
Empty set (0.00 sec)
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+---------------+-----------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+------------+-----------+---------------+-----------+
| test | student | NULL | TABLE | IS | NULL |
| test | student | PRIMARY | RECORD | S,REC_NOT_GAP | 1 |
+---------------+-------------+------------+-----------+---------------+-----------+
2 rows in set (0.00 sec)
mysql> update student set name='lily' where id=1;
#执行update语句会处于阻塞状态(共享锁与排他锁互斥)
#客户端一:
begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update student set name='lily' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
#客户端二:
begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update student set name='lily' where id=1;
#排他锁与排他锁互斥,处于阻塞状态,直至客户端一的事务提交
#客户端一:
begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update student set name='lili' where name='黛丽丝';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
#客户端二:
update student set name='lei' where id=2;
#因为name字段无索引,会对所有记录加锁
4.1.2、间隙锁(Gap Lock)
锁定索引记录间隙(不含该记录),确保索引间隙记录不变,防止其他事务在这个间隙进行insert,产生幻读。在RR级别下支持。
默认情况下,InnoDB在REPEATABLE READ 事务隔离级别运行,InnoDB使用next-key进行搜索和扫描索引,以防止幻读。
1、索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁。
2、索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时Next-Key Lock退化为间隙锁。
3、索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。
#客户端一:
select * from student;
+----+--------+------------+
| id | name | no |
+----+--------+------------+
| 1 | 黛丽丝 | 2000100101 |
| 2 | 谢逊 | 2000100102 |
| 3 | 殷天正 | 2000100103 |
| 4 | 韦一笑 | 2000100104 |
| 8 | lei | 2000100108 |
+----+--------+------------+
5 rows in set (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update student set name ='lili' where id=5;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
#客户端二:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+-----------+-----------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+------------+-----------+-----------+-----------+
| test | student | NULL | TABLE | IX | NULL |
| test | student | PRIMARY | RECORD | X,GAP | 8 |
+---------------+-------------+------------+-----------+-----------+-----------+
2 rows in set (0.00 sec)
begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into student values(7,'ruby',null);
#5和8之间存在间隙锁,插入数据会阻塞
注意:间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一个间隙上采用间隙锁。
4.1.3、临键锁(Next-Key Lock)
行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下都、支持。