MySQL 锁机制

锁的简介

锁是计算机协调多个进程或线程并发访问某一资源的机制。

在数据库中,除传统的计算资源 (如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个总要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

锁的分类

  1. 对数据操作的类型可以划分为:读锁/写锁

读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。

  1. 对数据操作的粒度可以划分为:表锁/行锁

MySQL 的三锁

表锁(偏读)

特点

偏向 MyISAM 存储引擎,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

案列分析
  • 建表 SQL
【表级锁分析 建表SQLcreate 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');

# 查询数据
select * from mylock;

# 手动增加表锁
lock table 表名字 read/write,表名字2 read/write,其他;

# 查看表上加过的锁
show open tables;

# 解除所有表上加的锁
unlock tables;
  • 加读锁

我们为 mylock 表加 read 锁(读堵塞写例子)

session_1session_2
获取表 mylock 的 read 锁定 在这里插入图片描述连接终端
当前 session 可以查询该表记录在这里插入图片描述其他 session 也可以查询该表的记录在这里插入图片描述
当前 session 不能查询其他没有锁定的表在这里插入图片描述其它 session 可以查询或者更新未锁定的表在这里插入图片描述
当前 session 中插入或者更新锁定的表都会提示错误在这里插入图片描述其它 session 插入或者更新锁定表会一直等待获得锁在这里插入图片描述
释放锁在这里插入图片描述session_2获得锁,修改操作完成在这里插入图片描述
  • 加写锁
session_1session_2
获取表 mylock 的 write 锁定 在这里插入图片描述待 session_1 开启写锁后,session_2在连接终端
当前 session 对锁定表的增删改查都可以执行在这里插入图片描述其它 session 对锁定表的增删改查都会被阻塞,需要等待锁被释放在这里插入图片描述
session_1释放锁在这里插入图片描述session_2获得锁,查询返回在这里插入图片描述
案例结论

MyISAM 在执行查询语句 SELECT 前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
MySQL 的表级锁有两种模式:
表共享读锁(Table Read Lock)
表独占写锁(Table Write Lock)

锁类型可否兼容读锁写锁
读锁
写锁

结论:
综合上表,所以对 MyISAM 表进行操作,会有以下情况:

  1. 对 MyISAM 表的读操作(加读锁),不会阻塞其它进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。
  2. 对 MyISAM 表的写操作(加写锁),会阻塞其它进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。
    简而言之,就是读锁会阻塞写,但是不会阻塞读,而写锁则会把读和写都阻塞。
表锁分析
# 可以查看哪些表被加锁了
show open tables;

# 分析表锁定
show status like 'table%'

在这里插入图片描述

这里有两个状态变量记录 MySQL 内部表级锁定的情况,两个变量的意义如下:

  1. Table_locks_immediate:产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1;
  2. Table_locks_waited:出现表级锁定争用而发生等待的次数(不能立即获得锁的次数,每等待一次锁值加1),此值高则说明存在着较为严重的表级锁争用情况;

此外,MyISAM的读写锁调度是写优先,这也是 MyISAM 不适合做写为主表的引擎。因为写锁后,其它线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。

行锁(偏写)

特点

偏向 InnoDB 存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

InnoDB 与 MyIASM 最大不同有两点:一是支持事务;二是采用了行级锁。

事务 Transaction

A指的是原子性:事务是一个原子操作,对数据的修改,要么全部执行,要么全部不执行。

C指的是一致性:保持数据的一致性,成功则全部成功,失败则全部失败。

I指的是隔离性:本次事务操作过程中的状态对外部是不可见的,外部也见不到内部的事务执行过程。

D指的是持久性:事务操作的数据对于数据库来说是永久性的,只要事务成功了,就算系统故障数据的修改也能保持。

并发事务处理带来的问题
更新丢失 Lost Update

当两个或多个事务选择同一行,然后基于最初选定的值更新改行时,由于每个事务都不知道其它事务的存在,就会发生丢失更新问题–最后的更新覆盖了有其他事务所做的更新。

例如:两个程序员修改同一个 java 文件,每个程序员独立的更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖前一个程序员所做的更改。

如果在一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。

脏读 Dirty Reads

一个事务正在对一条记录做修改,在这个事务完成并提交之前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏数据”,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被称为“脏读”。

简单而言:事务A读取到事务B已修改但尚未提交的数据,还在这个数据基础上做了操作,此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。

不可重复读 Non-Repeatable Reads

事务A读取到了事务B已经提交的修改数据,不符合隔离性。

幻读 Phantom Reads

一个事务按相同的查询条件重新读取以前检索过的数据,却发现其它事务插入了满足其它查询条件的新数据,这种现象就成为“幻读”。

事务A读取到了事务B提交的新增数据,不符合隔离性。

幻读和脏读有点类似:
脏读是事务B里面修改了数据。
幻读是事务B里面新增了数据。

事务隔离级别

脏读、不可重复读、幻读,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。

读数据一致性及允许的并发副作用隔离级别读数据一致性脏读不可重复读幻读
未提交读最低级别,只能保证不读取物理上损坏的数据
已提交读语句级
可重复读事务级
可序列化最高级别,事务级

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。

常看当前数据库的事务隔离级别:

show variables like 'tx_isolation';
案例分析
  • 建表 SQL
create table test_innodb_lock (a int(11),b varchar(16))engine=innodb;

insert into test_innodb_lock values(1,'b1');
insert into test_innodb_lock values(2,'b2');
insert into test_innodb_lock values(3,'b3');
insert into test_innodb_lock values(4,'b4');
insert into test_innodb_lock values(5,'b5');
insert into test_innodb_lock values(6,'b6');
insert into test_innodb_lock values(7,'b7');
insert into test_innodb_lock values(8,'b8');
insert into test_innodb_lock values(9,'b9');

create index test_innodb_a_ind on test_innodb_lock(a);

create index test_innodb_lock_b_ind on test_innodb_lock(b);

select * from test_innodb_lock;
  • 索引失效行锁就会变为表锁。
间隙锁的危害

【什么是间隙锁】
当我们用范围条件而不是相等条件检索数据,并请求共享或排它锁,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙 GAP”,InnODB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁 Next-Key锁。

【危害】
因为查询执行过程中通过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。

间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。

面试题:如何锁定一行
begin;
// select xxx... for update 锁定某一行后,其它的操作会被阻塞,知道锁定行的会话提交 commit;
select * from tableName for update;
commit;

在这里插入图片描述

案例结论

Innodb 存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力要远远优于 MyIASM 的表级锁定。当系统并发量较高的时候,Innodb 的整体性能和 MyIASM 相比就会有比较明显的优势了。

但是,Innodb 的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让 Innodb 的整体性能表现不仅不能比 MyIASM 高,甚至可能会更差。

行锁分析

【如何分析行锁定】
通过检查 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:系统启动后到现在总共等待的次数;

对于这5个状态变量,比较重要的是:

Innodb_row_lock_time:从系统启动到现在锁定总时间长度;
Innodb_row_lock_time_avg:每次等待所花平均时间;
Innodb_row_lock_waits:系统启动后到现在总共等待的次数;

优化建议
  1. 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。
  2. 合理设计索引,尽量缩小锁的范围
  3. 尽可能减少检索条件,比秒间隙锁
  4. 尽量控制事务大小,减少锁定资源量和时间长度
  5. 尽可能低级别事务隔离

页锁

开销和加锁时间界于表锁和行锁之间;会出现死锁;锁粒度界于表锁和行锁之间,并发度一般。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值