全局锁
对整个数据库实例加锁,一般用于全库逻辑备份。myisam不支持事务做备份时会用到,innodb做备份可以用mysqldump --single-transcation进行备份保持数据一致性。
开启全局锁:Flush tables with read/write lock;
解锁:unlock tables;
表级锁
表级锁分为表锁和元数据锁(meta data lock)简称MDL。表锁一般是在数据库引擎不支持行锁的时候才会被用到的,MDL锁用于表结构变更。
开启表锁:lock table xx表 read/write; 对表加读锁后,自己和其他线程只能读取该表,不能对其修改。 对表执加写锁后,该线程可以对这个表进行读写,其他线程对该表的读和写都受到阻塞。
解锁:unlock tables;
MDL锁不需要显示调用,访问表的时候会自动加上,使用MDL锁可以保证读写正确性,当对一个表做增删改查操作的时候,加 MDL 读锁,读锁之间不互斥,可以多个线程对一个表进行增删改查。当要对表做结构变更操作的时候,加 MDL 写锁,读写锁,写锁之间互斥,保证变更表结构安全性。
行锁
行锁由引擎层实现,innodb支持行锁,myisam不支持。不支持行锁只能使用表锁,在同一个表中只能有一个写线程在执行,并发粒度不如行锁,行锁分为共享锁,排他锁。
共享锁:又称读锁(S锁),共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
排他锁:又称写锁(X锁),排他锁不能与其他锁共存,如一个事务获取行记录的排他锁,其他事务就不能再获取该行的其他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
创建student表
CREATE TABLE `student` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(16) DEFAULT NULL,
`number` bigint(20) DEFAULT NULL COMMENT '普通索引编号',
`unique_number` bigint(20) DEFAULT NULL COMMENT '唯一索引编号',
`no_index_number` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `index2` (`unique_number`),
KEY `index1` (`number`)
) ENGINE=InnoDB AUTO_INCREMENT=107 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('2', '张三', '2', '2', '2');
INSERT INTO `student` VALUES ('5', '李四', '5', '5', '5');
INSERT INTO `student` VALUES ('8', '王五', '8', '8', '8');
INSERT INTO `student` VALUES ('11', '怀少飞', '11', '11', '11');
共享锁验证
共享锁与共享锁
线程1 | 线程2 |
BEGIN; SELECT * from student LOCK IN SHARE MODE; | |
SELECT * from student LOCK IN SHARE MODE; -- 共享锁查询成功 |
共享锁与无锁
线程1 | 线程2 |
BEGIN; SELECT * from student LOCK IN SHARE MODE; | |
SELECT * from student; -- 无锁查询成功 |
共享锁与排他锁
线程1 | 线程2 |
BEGIN; SELECT * from student LOCK IN SHARE MODE; | |
SELECT * from student FOR UPDATE; -- 排他锁查询阻塞 | |
commit; | |
查询成功 |
排他锁验证
排他锁与无锁
线程1 | 线程2 |
BEGIN; SELECT * from student FOR UPDATE; | |
SELECT * from student; -- 无锁查询成功 |
排他锁与共享锁
线程1 | 线程2 |
BEGIN; SELECT * from student FOR UPDATE; | |
SELECT * from student LOCK IN SHARE MODE; -- 共享锁查询阻塞 | |
commit; | |
查询成功 |
排他锁与排他锁
线程1 | 线程2 |
BEGIN; SELECT * from student FOR UPDATE; | |
SELECT * from student FOR UPDATE; -- 排他锁查询阻塞 | |
commit; | |
查询成功 |
行锁特性:在事务中行锁在需要时mysql自动添加,事务结束时释放,所以一个事务中多个sql应该把锁冲突的记录往后放。
间隙锁(Gap Lock)
查看是否开启间隙锁:show variables like 'innodb_locks_unsafe_for_binlog';
开启间隙锁:编辑my.cnf文件,[mysqld]中将innodb_locks_unsafe_for_binlog 修改为0,1表示关闭间隙锁。
可重复读隔离级别通过间隙锁解决幻读,间隙锁指锁两个行之间的间隙,比如student表number字段会产生(-∞,2),(2,5),(5,8),(8,11),(11,+∞)这5个间隙锁。间隙锁与行锁组成next-key lock 为(]前开后闭区间。
间隙锁在不同索引情况与查询条件不同时有以下特性:
1.等值查询时
主键索引与唯一索引锁存在记录使用行锁,锁不存在记录时构成[不存在记录,下一条存在记录)前闭后开区间。
普通索引锁存在记录或不存在记录是时构成[上一条存在记录,下一条存在记录)前闭后开区间
无索引字段锁全部区间。
2.范围查询时
情况比较复杂,写着有点麻烦,感兴趣可以试下,比如唯一索引:x>=存在记录and x<=不存在记录,x>存在记录and x<不存在记录,x>=不存在记录and x<不存在记录。x>存在记录 and x <不存在记录
总体来说间隙锁这块也看了网上的规律,总感觉网上总结的不太全面,有些偏差。目前我还没总结出通用的规律
等值查询
主键索引或唯一索引,锁存在记录
线程1 | 线程2 |
BEGIN; SELECT id from student WHERE id = 5 for update | |
INSERT INTO student (id) VALUES (6); -- 插入成功 INSERT INTO student (id) VALUES (4); -- 插入成功 INSERT INTO student (id) VALUES (5); -- 阻塞 |
主键索引或唯一索引,锁不存在记录
线程1 | 线程2 |
BEGIN; SELECT id from student WHERE id = 6 for update | |
INSERT INTO student (id) VALUES (8); -- 插入成功 INSERT INTO student (id) VALUES (4); -- 插入成功 INSERT INTO student (id) VALUES (6); -- 阻塞 INSERT INTO student (id) VALUES (7); -- 阻塞 |
普通索引,锁存在记录
线程1 | 线程2 |
BEGIN; SELECT id from student WHERE number = 5 for update | |
INSERT INTO student (number) VALUES (XX); 2<=x<8之间失败,其余成功 |
普通索引,锁不存在记录
线程1 | 线程2 |
BEGIN; SELECT id from student WHERE number= 6 for update | |
INSERT INTO student (number) VALUES (XX); 5<=x<8之间失败,其余成功 |