文章目录
一、数据库锁的分类
- 按锁的粒度划分,可分为页级锁、表级锁、行级锁
- 按锁级别划分,可分为共享锁(S)、排他锁(X)
- 按加锁方式划分,可分为自动锁、显式锁(for update,lock in share mode)
- 按操作划分,可分为DML、DDL锁
- 按使用方式划分,可分为乐观锁、悲观锁
二、MyISAM与InnoDB锁方面的区别
- InnoDb支持行锁和表锁,MyISAM只支持表锁
- MyISAM的写请求的优先级比读请求的优先级高
- MyISAM总是一次性获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。
- 但是在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,这就决定了InnoDB发生死锁是可能的。
我们直接上测试
测试准备:
- 一张基于MyISAM引擎的50万数据量的表person_info_myisam
- 一张基于InnoDB引擎的50万数据量的表person_info
- Navicat for MySQL
- mysql 8.x
注:测试数据生成可参考:该博客的中间部分,也可以自行百度。
有朋友可能问,为什么要这么多数据。查询这些数据需要些时间,在这个时间段内,执行另外一个会话中的sql语句来模拟并发
(一)MyISAM测试:
1、表的设计:
sql脚本:
/*
Navicat MySQL Data Transfer
Source Server : test
Source Server Version : 80014
Source Host : localhost:3306
Source Database : mysql_study
Target Server Type : MYSQL
Target Server Version : 80014
File Encoding : 65001
Date: 2020-04-23 19:25:19
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for person_info_myisam
-- ----------------------------
DROP TABLE IF EXISTS `person_info_myisam`;
CREATE TABLE `person_info_myisam` (
`id` int(7) NOT NULL AUTO_INCREMENT,
`account` varchar(10) DEFAULT NULL,
`name` varchar(20) DEFAULT NULL,
`area` varchar(20) DEFAULT NULL,
`title` varchar(20) DEFAULT NULL,
`motto` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_account` (`account`) USING BTREE,
KEY `index_area_title` (`area`,`title`) USING BTREE
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
2、开始测试
- 2.1、测试上了读锁之后,能否继续写表
我们先测试更新语句速度:update person_info_myisam set account = account where id = 500001;
可以看到很快,那我们先根据主键进行查询
select * from person_info_myisam where id between 1 and 500000;
在查询期间再来执行上面的更新语句
可以看到被阻塞了,稍作等待,等待查询一下结束,然后更新语句出现结果
花了2.+秒,和之前的比相差甚远
2.2、我们再来测试该期间能否select,判断是否是共享锁
在会话2中加入select * from person_info_myisam where id = 1;
按照之前的步骤,先执行会话1的select,读的过程中,MyiSam引擎会给表上读锁,然后我们执行会话2的选中的语句,结果如下
时间为0.000s,几乎没用什么时间,说明MyiSam引擎下的读锁为共享锁,不阻塞读操作,其他都阻塞。以上的加锁都是mysql自动给我们加的,我们可以用以下语句手动加读锁和取消它。
-- 添加读锁
LOCK TABLES person_info_myisam read;
-- 释放读锁
UNLOCK TABLES;
2.3、测试加写锁后,能否读和写
会话1语句先执行
-- select语句上排他锁
select * from person_info_myisam where id between 1 and 500000 FOR UPDATE;
先测试读:执行会话1的同时执行以下语句
select * from person_info_myisam where id = 1;
我们会发现,本条几乎不需要时间的语句花了1.8秒,中间被阻塞了
然后测试写
按照以上步骤,同时执行
update person_info_myisam set account = account where id = 500001;
结果也在预料之中,被阻塞了
以上的测试可以看出,MyiSam引擎加的是表锁(读取会话1之外的数据都被阻塞)其共享锁可读不可写,写锁(即排它锁),不可读也不可写。
(二)InnoDB测试
1、设计基于InnoDB引擎的person_info表
/*
Navicat MySQL Data Transfer
Source Server : test
Source Server Version : 80014
Source Host : localhost:3306
Source Database : mysql_study1
Target Server Type : MYSQL
Target Server Version : 80014
File Encoding : 65001
Date: 2020-04-23 20:50:27
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for person_info
-- ----------------------------
DROP TABLE IF EXISTS `person_info`;
CREATE TABLE `person_info` (
`id` int(7) NOT NULL AUTO_INCREMENT,
`account` varchar(10) DEFAULT NULL,
`name` varchar(20) DEFAULT NULL,
`area` varchar(20) DEFAULT NULL,
`title` varchar(20) DEFAULT NULL,
`motto` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_name` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3、
同样我们采用会话1和会话2的方式进行并发测试,注意,InnoDB引擎下不会自动给select上共享锁,需要我们手动添加lock in share mode
,另外我们需要将自动提交关闭:set autocommit = 0;
可以用show VARIABLES like 'autocommit';
查询该变量是否为OFF
3.1、先测试开启读锁后能否读其他的行(检测是否为行锁)
select * from person_info where id = 3 lock in share mode;
在会话1事务没提交时,我们查询id为5 的数据
select * from person_info where id = 5 lock in share mode;
3.2、说明InnoDB的是行锁,接下来测试在同一个记录上能否同时加两个共享锁,即多个事务读
select * from person_info where id = 3 lock in share mode;
两个会话都执行该sql,结果显示都能执行,即InnoDB的共享锁支持其他事务读
那支不支持其他事务写呢?
会话1先执行
select * from person_info where id = 3 lock in share mode;
会话2执行
update person_info set title = 'test1' where id = 3;
结果如下:
被阻塞了,然后超时,只有会话1commit才能执行
3.3、排他锁测试
会话1执行
update person_info set title = 'test3' where id = 5;
会话2执行
select * from person_info where id = 5 lock in share mode;
读操作被阻塞
update person_info set title = 'test1' where id = 5;
写操作被阻塞
3.4、我们测试不走索引的情况下会加什么锁(行锁or表锁?)
为了方便测试,我将字段name的索引删除了,
然后将之前两个会话执行commit
,提交之前的事务,然后会话1执行
update person_info set motto = 'test2' where name = 'BtomubiKI8qv8Xe7KdII';
执行成功,会话2查询另外一行记录
select account from person_info where name = 'kKBDcWVDe7KeM0wqoyrl' lock in share mode;
其他行都没法查询,说明表被锁了,也就是说,不走索引,行锁会升级为表锁。
总结
- InnoDB 支持表锁和行锁,使用索引作为检索条件修改数据时采用行锁,否则采用表锁
- InnoDB 自动给修改操作加锁,给查询操作不自动加锁
- 行锁可能因为未使用索引而升级为表锁,所以除了检查索引是否创建的同时,也需要通过explain执行计划查询索引是否被实际使用。
- 行锁相对于表锁来说开销要大,但优势在于高并发场景下表现更突出
两个引擎下的共享锁和排斥锁兼容性如下:
myisam适合的场景
- 频繁执行全表count语句,存有变量记录行数
- 对数据进行增删改的频率不高,查询非常频繁,(共享锁可用)
- 没有事务
myisam引擎中的数据和文件时分离的,是非聚集索引,索引保存的是数据文件的指针,主键索引和辅助索引是独立的,因此,myisam引擎在纯检索系统中或者增删改很少的系统中,性能要好于InnoDB引擎。
InnoDB适合的场景
- 数据增删改查都相当频繁
- 可靠性要求比较高,要求支持事务
- 高并发场景