Mysql各种锁机制
Posted wgchen~
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mysql各种锁机制相关的知识,希望对你有一定的参考价值。
阅读目录
一、mysql 锁介绍
锁来源
在存在并发操作的时候,必然需要一种机制来保证数据的完整性与一致性。
锁就是这一技术的实现。
锁种类
根据概念分:悲观锁和乐观锁
根据粒度分:
表锁、页锁、行锁,最常见的就是表锁和行锁。
其中,MyISAM引擎只有表锁,而InooDB既有表锁也有行锁。
根据功能分:
共享锁、排它锁(独占锁)、意向锁等。
其中,共享锁被称为 S 锁。排它锁称为 X 锁。
锁名称 | 特点 |
---|---|
表锁 | 加锁快,不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低。 |
行锁 | 开销大,发生锁冲突概率低。并发度高,会发生死锁。 |
页锁 | 开销、加锁时间、锁定粒度界于表锁和行锁之间,会出现死锁,并发度一般。 |
思维导图一览
MySQL 的各种锁可能会让人难以理解,理解之前务必心中要有个思维导图,哪个锁归属哪个引擎,哪个锁归属哪个锁,心中一定要有个大类和小类的区分,这样在学起来就不会太难了。
二、MyISAM 表锁
先看一下读锁和写锁的兼容性:
当前锁模式 / 是否兼容 / 请求锁模式 | 读锁 | 写锁 |
---|---|---|
读锁 | 是 | 否 |
写锁 | 是 | 否 |
MySQL 测试版本 | 5.7.26
表引擎:sys_admin_log 表为 MyISAM,test_users 表为 InnoDB。
sys_admin_log 操作日志 MyISAM 表
CREATE TABLE `sys_admin_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`admin_name` varchar(255) DEFAULT NULL COMMENT '账号',
`created_at` int(12) DEFAULT NULL COMMENT '操作时间',
`ip` varchar(200) CHARACTER SET utf8 DEFAULT NULL COMMENT 'ip',
`content` text COMMENT '日志',
`admin_id` int(11) DEFAULT NULL COMMENT '账号id',
`path` varchar(255) DEFAULT NULL COMMENT '操作路由',
`method` varchar(255) DEFAULT NULL COMMENT '操作方法',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='操作日志表';
INSERT INTO `sys_admin_log` (`id`, `admin_name`, `created_at`, `ip`, `content`, `admin_id`, `path`, `method`) VALUES ('1', 'admin_name', '1632898006', '127.0.0.1', '{\\"s\\":\\"\\\\/api\\\\/index\\"}', '1', 'api/index', 'GET');
INSERT INTO `sys_admin_log` (`id`, `admin_name`, `created_at`, `ip`, `content`, `admin_id`, `path`, `method`) VALUES ('2', 'admin_name', '1632898498', '127.0.0.1', '{\\"s\\":\\"\\\\/api\\\\/index\\"}', '1', 'api/index', 'GET');
test_users 用户 InnoDB 表
CREATE TABLE `test_users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT '',
`email` varchar(255) DEFAULT '',
`password` varchar(255) DEFAULT '',
`pid` int(11) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1800010 DEFAULT CHARSET=utf8;
INSERT INTO `test_users` (`id`, `name`, `email`, `password`, `pid`) VALUES ('800001', 'kana.sasaki', '1936902877maaya70@kato.net', '\\"\\\\BvW[h]\\'b~zV`nl', '0');
INSERT INTO `test_users` (`id`, `name`, `email`, `password`, `pid`) VALUES ('800002', 'fyamaguchi', '608386635sasada.satomi@matsumoto.com', '.Wh4_}', '0');
INSERT INTO `test_users` (`id`, `name`, `email`, `password`, `pid`) VALUES ('800003', 'nanami.sakamoto', '1302522125kyosuke37@hamada.jp', '3A)J&SqtrbxDiMU7S', '0');
INSERT INTO `test_users` (`id`, `name`, `email`, `password`, `pid`) VALUES ('800004', 'kanou.nanami', '317175880yuta34@uno.com', 'xKB1nz~+/J#FserC', '0');
INSERT INTO `test_users` (`id`, `name`, `email`, `password`, `pid`) VALUES ('800005', 'chiyo93', '35942393zkanou@murayama.jp', 'M&5M]B~hg!p', '0');
INSERT INTO `test_users` (`id`, `name`, `email`, `password`, `pid`) VALUES ('800006', 'uyamaguchi', '1852957470kondo.kaori@gmail.com', 'n*P*H`0[', '0');
INSERT INTO `test_users` (`id`, `name`, `email`, `password`, `pid`) VALUES ('800007', 'akira.sato', '1568165671hiroshi23@sato.jp', 'wQ_vE4[t\\"v', '0');
INSERT INTO `test_users` (`id`, `name`, `email`, `password`, `pid`) VALUES ('800008', 'atsushi06', '75486597zishida@kudo.net', 'FYw\\")QWG*', '0');
INSERT INTO `test_users` (`id`, `name`, `email`, `password`, `pid`) VALUES ('800009', 'shota40', '399696901kimura.tsubasa@yahoo.co.jp', '3uJ.u.7.<i?', '0');
INSERT INTO `test_users` (`id`, `name`, `email`, `password`, `pid`) VALUES ('800010', 'ogaki.yuta', '96819331asuka82@kudo.jp', '.=&~}7_]bI<', '0');
INSERT INTO `test_users` (`id`, `name`, `email`, `password`, `pid`) VALUES ('800011', 'koizumi.sayuri', '1457611112akira.aota@aoyama.jp', '+_2g|fCy}DuR~lU0en', '0');
1、读锁
语法:lock table tablename read
会话 1:给 sys_admin_log
表加锁,就不能对 test_users
等其它表进行操作了,只能对加锁的表进行读操作。
mysql> lock table sys_admin_log read;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from sys_admin_log;
+----+------------+------------+-----------+----------------------+----------+-----------+--------+
| id | admin_name | created_at | ip | content | admin_id | path | method |
+----+------------+------------+-----------+----------------------+----------+-----------+--------+
| 1 | admin_name | 1632898006 | 127.0.0.1 | {"s":"\\/api\\/index"} | 1 | api/index | GET |
| 2 | admin_name | 1632898498 | 127.0.0.1 | {"s":"\\/api\\/index"} | 1 | api/index | GET |
+----+------------+------------+-----------+----------------------+----------+-----------+--------+
2 rows in set (0.00 sec)
mysql> select * from test_users limit 0,2;
ERROR 1100 (HY000): Table 'test_users' was not locked with LOCK TABLES
mysql>
mysql> delete from test_users where id = 2;
ERROR 1100 (HY000): Table 'test_users' was not locked with LOCK TABLES
mysql>
会话 2:会话 1 中给 sys_admin_log
表加锁,不影响会话 2 查询加锁的表和其它表。
mysql> select * from sys_admin_log;
+----+------------+------------+-----------+----------------------+----------+-----------+--------+
| id | admin_name | created_at | ip | content | admin_id | path | method |
+----+------------+------------+-----------+----------------------+----------+-----------+--------+
| 1 | admin_name | 1632898006 | 127.0.0.1 | {"s":"\\/api\\/index"} | 1 | api/index | GET |
| 2 | admin_name | 1632898498 | 127.0.0.1 | {"s":"\\/api\\/index"} | 1 | api/index | GET |
+----+------------+------------+-----------+----------------------+----------+-----------+--------+
2 rows in set (0.00 sec)
会话 2:
此时会话 1 虽然对 sys_admin_log
加表锁了,
但是会话 2 也依然可以对 sys_admin_log
表加表锁。
但是加上表锁后,会话 2 也和会话 1 一样只能对进行查询的操作了。
mysql> lock table sys_admin_log read;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test_users;
ERROR 1100 (HY000): Table 'test_users' was not locked with LOCK TABLES
mysql>
mysql> UPDATE `sys_admin_log` SET `admin_name`='admin_name5' WHERE (`id`='1');
ERROR 1099 (HY000): Table 'sys_admin_log' was locked with a READ lock and can't be updated
mysql>
结论 1:在执行表读锁后,当前会话只能访问加锁的这个表,不能访问未加锁的表,但是非当前会话不受影响。
会话 1:对 sys_admin_log
表进行加表锁,并对 id 为 1 的数据进行删除。
mysql> lock table sys_admin_log read;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from sys_admin_log;
+----+-------------+------------+-----------+----------------------+----------+-----------+--------+
| id | admin_name | created_at | ip | content | admin_id | path | method |
+----+-------------+------------+-----------+----------------------+----------+-----------+--------+
| 1 | admin_name5 | 1632898006 | 127.0.0.1 | {"s":"\\/api\\/index"} | 1 | api/index | GET |
| 2 | admin_name | 1632898498 | 127.0.0.1 | {"s":"\\/api\\/index"} | 1 | api/index | GET |
+----+-------------+------------+-----------+----------------------+----------+-----------+--------+
2 rows in set (0.00 sec)
mysql> delete from sys_admin_log where id = 1;
ERROR 1099 (HY000): Table 'sys_admin_log' was locked with a READ lock and can't be updated
mysql>
会话 2:对 sys_admin_log
表中某行数据进行删除,但会造成阻塞。
mysql> delete from classroom where id = 3;
……等待
结论 2:
在执行表读锁后,当前会话只能进行查询操作,不能进行其它操作(update、delete 等)。非当前会话可以执行其它操作,但会造成阻塞。
2、写锁
语法:lock table tablename write
会话 1:会话 1 持有 sys_admin_log
表读锁,其它会话能持有该表的读锁,但不能持有该表的写锁。
mysql> lock table sys_admin_log read;
Query OK, 0 rows affected (0.00 sec)
会话 2:会话 2 可在持有 sys_admin_log
的表读锁,但不能持有表写锁,会造成阻塞。
mysql> lock table classroom read;
Query OK, 0 rows affected (0.00 sec)
mysql> lock table classroom write;
……等待
结论 1:当一个会话持有表读锁,其它会话可以持有表读锁,但不能持有表写锁。
会话 1:事务 1 给 sys_admin_log
加表锁,那只有事务 1 才能进行增删改查操作。
mysql> lock table sys_admin_log write;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from sys_admin_log;
+----+------------+------------+-----------+----------------------+----------+-----------+--------+
| id | admin_name | created_at | ip | content | admin_id | path | method |
+----+------------+------------+-----------+----------------------+----------+-----------+--------+
| 3 | admin_name | 1632898498 | 127.0.0.1 | {"s":"\\/api\\/index"} | 1 | NULL | NULL |
| 2 | admin_name | 1632898498 | 127.0.0.1 | {"s":"\\/api\\/index"} | 1 | api/index | GET |
+----+------------+------------+-----------+----------------------+----------+-----------+--------+
2 rows in set (0.00 sec)
mysql>
会话 2:会话 1 加了写锁后,会话 2 不能在对此表进行操作,但是可以对其它表进行操作。
mysql> select * from sys_admin_log;
Ctrl-C -- sending "KILL QUERY 214" to server ...
Ctrl-C -- query aborted.
ERROR 1317 (70100): Query execution was interrupted
mysql> select * from test_users limit 0,2;
+--------+-------------+--------------------------------------+------------------+------+
| id | name | email | password | pid |
+--------+-------------+--------------------------------------+------------------+------+
| 800001 | kana.sasaki | 1936902877maaya70@kato.net | "\\BvW[h]'b~zV`nl | 0 |
| 800002 | fyamaguchi | 608386635sasada.satomi@matsumoto.com | .Wh4_} | 0 |
+--------+-------------+--------------------------------------+------------------+------+
2 rows in set (0.00 sec)
mysql> update test_users set name = "willem" where id = 800001;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from test_users limit 0,2;
+--------+------------+--------------------------------------+------------------+------+
| id | name | email | password | pid |
+--------+------------+--------------------------------------+------------------+------+
| 800001 | willem | 1936902877maaya70@kato.net | "\\BvW[h]'b~zV`nl | 0 |
| 800002 | fyamaguchi | 608386635sasada.satomi@matsumoto.com | .Wh4_} | 0 |
+--------+------------+--------------------------------------+------------------+------+
2 rows in set (0.00 sec)
结论 2:当一个会话持有表写锁,那么该会话只能对该表进行增删改查操作。其它会话则不能对该表进行一切操作。但是不影响其它会话对别的表进行操作。
3、总结
\\ | 表读锁 | 表写锁 |
---|---|---|
当一个事务已持有表读 / 写锁,其它事务是否可对该表进行 curd | 可查不可增删改 | 可增删该查 |
当一个事务已持有表读锁,其它事务能否在继续持有表读 / 写锁 | 能在持有表读锁 | 不能持有表写锁 |
当一个事务已持有表写锁,其它事务能否在继续持有表读 / 写锁 | 不能持有表读锁 | 不能持有表写锁 |
当一个事务已持有表读 / 写锁,那这个事务能否在对别的表进行操作 | 不能 | 不能 |
# 二、InnoDB 表锁(意向锁): |
意向锁含义(百度百科)
意向锁的含义是如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁;对任一结点加锁时,必须先对它的上层结点加意向锁。
意向锁是有数据引擎自己维护的,用户无法手动干预,在加行级排它锁或共享锁之前,InooDB 先会判断所在数据行的数据表中是否有对应的意向锁。
InooDB 是持有行锁的,MyISAM 是没有行锁的,既然有行锁,必然就要了解一下 InooDB 下行锁和表锁之间的那兼容性。
下面做个实验:
会话 1:给 test_users
表中某一行数据加上共享锁,并未提交
mysql> select * from test_users limit 0,3;
+--------+-----------------+--------------------------------------+-------------------+------+
| id | name | email | password | pid |
+--------+-----------------+--------------------------------------+-------------------+------+
| 800001 | willem | 1936902877maaya70@kato.net | "\\BvW[h]'b~zV`nl | 0 |
| 800002 | fyamaguchi | 608386635sasada.satomi@matsumoto.com | .Wh4_} | 0 |
| 800003 | nanami.sakamoto | 1302522125kyosuke37@hamada.jp | 3A)J&SqtrbxDiMU7S | 0 |
+--------+-----------------+--------------------------------------+-------------------+------+
3 rows in set (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test_users where id = 800001 lock in share mode;
+--------+--------+----------------------------+------------------+------+
| id 以上是关于Mysql各种锁机制的主要内容,如果未能解决你的问题,请参考以下文章