1、锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除了传统的计算资源(如COU、RAM、I/O等)的争用以外,数据也是一种供许多用户享用的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
2、锁的分类:从对数据操作的类型(读\写)分 --- 读锁和写锁 ;从对数据操作的粒度分 --- 表锁和行锁 。
2.1、表锁:偏读
2.1.1、表锁的特点:偏向MyISAM存储引擎,开销小、加锁快、无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
2.1.2、表锁演示:
-- 新建表
CREATE TABLE mylock(
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20)
)engine myisam;
-- 插入几条数据
INSERT INTO mylock(name) VALUES('a');
INSERT INTO mylock(name) VALUES('b');
INSERT INTO mylock(name) VALUES('c');
INSERT INTO mylock(name) VALUES('d');
INSERT INTO mylock(name) VALUES('e');
【手动增加表锁】
lock table mylock read, book write;
【查看表上加过的锁】
show open tables;
可以从下图看到,这两个表被加上了锁。
【释放表锁】
unlock tables;
2.2、读锁:也叫共享锁,针对同一份数据,多个读操作可以同时进行而不会相互影响。
2.2.1、为mylock表加read锁(读阻塞写例子)
session_1 | session_2 |
获得表mylock的READ锁定 |
连接终端 |
当前session可以查询该表记录 |
其它session也可以查询该表的记录 |
当前session不能查询其他没有锁定的表 |
其他session可以查询或者更新未锁定的表 |
当前session中插入或者更新锁定的表都会提示错误 |
其他session插入或者更新锁定的表会一直等待获得锁 |
释放锁 |
session2获得锁,插入操作完成 |
2.3、写锁:也叫排它锁,当前写操作没有完成之前,他会阻断其他写锁和读锁操作。
2.3.1、为mylock表加write锁(MyISAM存储引擎的写阻塞读例子)
session_1 | session_2 |
获得表mylock的WRITER锁定 |
待session1开启写锁后,session2再连接终端 |
当前session对锁定表的查询+更新+插入操作都可以执行 |
其他session对锁定表的查询被阻塞,需要等待锁被释放: 备注:如果可以,请换成不同的id来进行测试,因为mysql有缓存,第2次的条件会从缓存中取得,影响锁效果的演示 |
释放锁 |
session2获得锁,查询返回: |
2.4、案例结论
2.4.1、MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
2.4.2、MySQL的表级锁的两种模式:表共享读锁(Table Read Lock); 表独占写锁(Table Write Lock)。
锁类型 | 可否兼容 | 读锁 | 写锁 |
读锁 | 是 | 是 | 否 |
写锁 | 是 | 否 | 否 |
2.4.3、结合上表,对MyISAM表进行操作,会有以下情况:
(1)对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其他进程的写操作。
(2)对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作。
简而言之,就是读锁会阻塞写,但是不会阻塞读;而写锁则会把读和写都阻塞。
2.4.5、表锁分析
【查看被加锁的表】
show open tables
【分析表锁定】
show status like 'table%';
执行完命令后,如下图所示,可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定情况。这两个状态变量记录MySQL内部表锁定的情况,两个变量说明如下:
table_locks_waited:产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1;
table_locks_immediate:出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁值加1),此值高则说明存在着较严重的表级锁争用情况
此外,Myisam的读写锁调度是写优先,这也是Myisam不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会是查询很难得到锁,从而造成永久阻塞。
3.1、行锁:偏写
3.1.1、行锁的特点:偏向InnoDB存储引擎,开销大、加锁慢、会出现死锁;锁定粒度小,发生锁冲突的概率最低,并发度最高。
3.1.2、行锁和表锁的区别:InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁
3.1.3、行锁演示:
【新建表】
-- 新建表
CREATE table test_innodb_lock (a INT(11),b VARCHAR(16)) engine=innodb;
INSERT INTO test_innodb_lock VALUES(1,'b2');
INSERT INTO test_innodb_lock VALUES(3,'3');
INSERT INTO test_innodb_lock VALUES(4,'4000');
INSERT INTO test_innodb_lock VALUES(5,'5000');
INSERT INTO test_innodb_lock VALUES(6,'6000');
INSERT INTO test_innodb_lock VALUES(7,'7000');
INSERT INTO test_innodb_lock VALUES(8,'8000');
INSERT INTO test_innodb_lock VALUES(9,'9000');
INSERT INTO test_innodb_lock VALUES(1,'b1');
CREATE INDEX test_innodb_a_ind on test_innodb_lock(a);
CREATE INDEX test_innodb_lock_b_ind on test_innodb_lock(b);
【行锁定基本演示】
Session_1 | Session_2 |
更新但是不提提交,没有手写commit; |
Session_2被阻塞,只能等待
|
提交更新 |
解除阻塞,更新正常进行 |
commit命令执行 | |
下面试试1号会话更新a=1,2号会话更新a=9 | |
发现互不干扰 |
【索引失效行锁变表锁】
-- 索引失效情况(VARCHAR类型要加单引号才能保证索引不失效)
UPDATE test_innodb_lock set a=41 WHERE b=4000;
等待 Session_1 commit之后,查看Session_2的情况:
【什么是间隙锁】
当我们使用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
【间隙锁的危害】
因为Query执行过程中通过范围查找的话,它会锁定整个范围内所有的索引键值,即使这个键值并不存在。
间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。
Session_1 | Session_2 |
阻塞产生,暂时不能插入 |
|
执行commit; | 阻塞接触,完成插入 |
【面试常考题:如何锁定一行】
3.1.4、行锁小结
Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,Innodb的整体性能和MyISAM相比就会有比较明显的优势了。但是,Innodb的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现不仅不能比MyISAM高,甚至可能会更差。
3.1.5、行锁分析
通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况
SHOW STATUS LIKE 'innodb_row_lock%';
各个变量如下图所示:
Innodb_row_lock_current_waits:当前正在等待锁定的数量
Innodb_row_lock_time:从系统启动到现在锁定总时间长度
Innodb_row_lock_time_avg:每次等待所花平均时间
Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花的时间
Innodb_row_lock_waits:系统启动后到现在总共等待的次数
对于这五个状态变量,比较重要的是:
Innodb_row_lock_time(等待总时长)
Innodb_row_lock_time_avg(等待平均时长)
Innodb_row_lock_waits(等待总次数)
尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手制定优化计划。
3.2、优化建议
尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。
合理设计索引,尽量缩小锁的范围。
尽可能较少检索条件,避免间隙锁。
尽量控制事务大小,减少锁定资源量和时间长度。
尽可能低级别事务隔离。
4、页锁
不做过多介绍,了解一下即可。