通常我们需要一组操作,同时成功,或者同时失败的时候需要通过事务来进行保证。
从而保证原子性,一致性,隔离性,和持久性。
InnoDB 是支持 表锁与行锁的。
MyIsam 只支持行锁。
用于锁定整个表,阻止其他会话对整个表进行访问
创建一个InnoDB引擎的表
drop table if exists InnoDBTest;
CREATE TABLE `InnoDBTest` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) NOT NULL,
`b` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into InnoDBTest(a,b) values(1,1),(2,2);
# 开启session1
BEGIN;
# 使用写锁
select * from InnoDBTest FOR UPDATE;
# 重新开始一个查询窗口后
# 开启session2
BEGIN;
# 使用写锁
select * from InnoDBTest FOR UPDATE;
# 会发现 session2的selet会被阻塞住,
# 当我们的session1 执行commit后,session2的select才会执行完成。
用于锁定表中的某行或者某些行(范围),允许其他事务方法未被锁定的行
# 开启session1
BEGIN;
# 使用写锁
select * from InnoDBTest where a = 1 FOR UPDATE;
# 重新开始一个查询窗口后
# 开启session2
BEGIN;
# 使用写锁
select * from InnoDBTest where a = 1 FOR UPDATE;
# 会发现 session2的selet会被阻塞住,
# 开启session3
BEGIN;
# 使用写锁
select * from InnoDBTest where a = 2 FOR UPDATE;
# 会发现可以session3的select会执行成功,不会等待session1
# 可以理解会 where a = 1 给a=1的记录加了行锁,只要不访问带有锁的行就能不被阻塞。
普通的select
通过加上关键字LOCK IN SHARE MODE
可以使对应的行或者表获取到锁。
允许多个事务同时读取一份数据,不会互相干扰。
但读锁会阻止其他事务获得写锁,但不会阻止其他事务获得写锁。
多个事务可以同时获取读锁,称之为共享锁
# 开启session1
BEGIN;
# 使用读锁
select * from InnoDBTest where a = 1 LOCK IN SHARE MODE;
# 重新开始一个查询窗口后
# 开启session2
BEGIN;
# 使用读锁
select * from InnoDBTest where a = 1 LOCK IN SHARE MODE;
# 会发现session2能正常获取数据
# 开启session3
BEGIN;
# 使用写锁
update InnoDBTest set b = 3 where a = 1;
# session3会被阻塞因为update where a=1 需要获取a=1的记录当时此时,session1给a=1的记录上了读锁,所以session3会阻塞,只到 session1完成。
# 开启session4
BEGIN;
# 使用写锁
update InnoDBTest set b = 3 where a = 2;
# 会发现session4不会阻塞,因为在innodb中支持行锁,a=2的记录没有被上读锁。
排他锁
通常我们的update、insert、delete
都是会获取写锁的。但是普通的select并没有获取锁,需要为其加上 for update
才能使普通的select拿到写锁
# 开启session1
BEGIN;
# 使用读锁
select * from InnoDBTest for update;
# 开启session2
BEGIN;
select * from InnoDBTest;
# 普通读不会阻塞
# 开启session3
BEGIN;
# 使用读锁
select * from InnoDBTest LOCK IN SHARE MODE;
# 需要获取InnoDBTest的读锁所以会阻塞
# 开启session4
BEGIN;
# 使用写锁
insert into InnoDBTest value(3,3);
# session4执行insert 需要获取写锁,所以 session3会阻塞
当一个会话正在修改表结果(新增列,删除列(DDL)等)时,会持有一个表级的元数据锁阻止其他会话对相同的表结果进修改
当我们的表被锁住时,我们通过DDL语句去操作表结果的时候,就会触发
表级MDL
,触发后不管是读锁,写锁,或者是普通的select都会被阻塞
# 开启session1
BEGIN;
# 获取写锁
delete from InnoDBTest;
# 开启session2
alter table InnoDBTest add COLUMN c VARCHAR(50);
# 因为session1把表的写锁给获取了,所以当我们执行DDL语句的时候,就被阻塞了,需要等待session1执行完成,但是,alter table 又需要获取元数据锁,所以,之后只要操作InnoDBTest的操作都会阻塞,需要等待 seesion1与session2执行完成。
# 开启session3
select * from InnoDBTest
# 普通的select阻塞了。
这里如果session1一直不commit这里就是一个死锁。也就导致了锁表。
如果验证呢?请看锁表章节
SHOW OPEN TABLES WHERE In_use >0;
In_use > 0 代表至少被一个会话使用的表才显示。
2.查看当前正在执行的所有数据库连接和查询的详细信息
SHOW FULL PROCESSLIST;
列描述
Id
:连接或线程的唯一标识符。User
:执行查询的MySQL用户。Host
:连接的主机名或IP地址。db
:当前连接的数据库(如果有)。Command
:正在执行的命令类型,例如 SELECT、INSERT、UPDATE、DELETE 等。Time
:该查询执行的时间(以秒为单位)。State
:查询的当前状态,例如正在锁定、发送数据等。Info
:包含有关查询的更多信息,如查询文本。可以看见我们这条sql导致了锁表。也就是我们的session使得session2也阻塞了,从而导致table metadata lock
一直被占用。
所以,线上环境在高峰期的时候一定要避免去执行DDL,这样会导致非常严重的问题,类似与,数据源连接池全部都阻塞了。导致程序挂掉。有幸遇到过
如果session1能够被关闭还好了,但是如果通过session1关不掉,不是我们自己的程序去操作的那怎么办?
刚才通过SHOW FULL PROCESSLIST
找到了是个主机,及那个sql使得我们的表锁定了。找到该行的**ID
**
# 通过线程ID来终止指定的线程
KILL <process_id>;
# 终止对应的进程后
kill 21;
SHOW FULL PROCESSLIST;
# 也没有看到其他的锁表信息了。
# 普通select 可以正常查询了
select * from InnoDBTest
今天遇到一个问题就是 有一个truncate table
在执行,导致锁表了。并且一直没有完成,从而导致线上服务查询基本信息的时候出现大量卡住的问题,导致服务不可用。记录下问题及解决方案,及其他知识点。
以上没有解决的话!
请直接祭出最终办法:万能重启!!!