MYSQL-浅谈MYSQL加锁机制、锁分类
文章转载自:https://cloud.tencent.com/developer/article/2431018
一、概述
1.1、MySQL锁的由来
客户端发往MySQL的一条条SQL语句,实际上都可以理解成一个个单独的事务(一条SQL语句就是一个事务),而事务是基于数据库连接的,每个数据库连接在MySQL中,又会用到一条工作线程来维护,也就意味着一个事务的执行。本质上就是一条工作线程在执行,当出现多个事务同时执行,这种情况则被称之为并发事务,所谓的并发事务也就是指多条线程并发执行。
多线程并发执行自然就会出现问题,也就是MySQL常见的并发事务四大问题 —— 脏写、脏读、不可重复读、幻读这些问题。对于这些问题又可以通过调整事务的隔离级别来避免,那为什么调整事务的隔离级别后能避免这些问题的产生?这是因为不同的隔离级别中,工作线程执行SQL语句时,用的锁粒度、类型不同。
1.2、锁定义
由以上可知,数据库的锁机制本身是为了解决并发事务带来的问题而诞生的,主要是确保数据库中,多条工作线程并行执行时的数据安全性。
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤为重要,也更复杂。
1.3、锁分类
数据库的锁机制与索引机制类似,都是由存储引擎负责实现的,这也就意味着不同的存储引擎、支持的锁也并不同,这里指不同的引擎实现的锁粒度不同。但除了从锁粒度来分锁之外,锁也可以从其他的维度来划分,因此也会有很多关于锁的名词,下面简单梳理以下MySQL的锁体系:
以锁粒度的维度划分:
- 全局锁:锁定数据库中的所有表。加上全局锁之后,整个数据库只能允许读。不允许做任何写操作
- 表级锁:每次操作锁住整张表。主要分三类
- 表锁(分为共享读锁 read lock、表独占写锁 write lock)
- 元数据锁(meta data lock,MDL):基于表的元数据加锁,枷锁后整张表不允许其他事务操作,这里的元数据可以简单理解为一张表的表结构
- 意向锁(分为意向共享锁、意向排他锁):这个是InnonDB引擎中为了支持多粒度的锁,为了兼容行锁、表锁而设计的,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查
- 行级锁:每次操作锁住对应的行数据。主要分为三类:
- 记录锁/Record锁:也就是行锁,一条记录和一行数据是同一个意思,防止其他事务对此进行update和delete,在RC、RR隔离级别下都支持
- 间隙锁/Gap锁:锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,从而产生幻读。在RR隔离级别下都支持
- 临键锁/Next-Key锁:间隙锁的升级版,同时具备记录锁+间隙锁的功能,在RR隔离级别下支持
以互斥性的角度划分:
- 共享锁/S锁:不同事务之间不会互相排斥、可以同时获取锁
- 排他锁/X锁:不同事务之间会互相排斥、同时只允许一个事务获取到锁
- 共享排他锁/SX锁:MYSQL5.7版本引入的锁,只要是解决SMO带来的问题
以操作类型的维度划分:
- 读锁:查询数据时使用的锁
- 写锁:执行插入、删除、修改DDL语句时使用的锁
以加锁方式的维度划分:
- 显示锁:编写SQL语句时,手动指定加锁的粒度
- 隐示锁:执行SQL语句时,根据隔离级别自动为SQL操作加锁
以思想的维度划分:
- 乐观锁:每次执行都会认为自己成功,因此先尝试执行,失败后在获取锁
- 悲观锁:每次执行都认为自己无法成功,因此会先获得锁,然后再执行
二、共享锁与排他锁
2.1、共享锁(S锁)
定义:一个事务已获取了共享锁,当另一个事务尝试对具备共享锁的数据进行读操作时,可以正常读;进行写操作时,会被共享锁排斥。
共享锁的意思很简单,也就是不同事物之间不会排斥,可以同时获取锁并执行。但这里所谓的不会排斥,仅仅是指不会排斥其他事务来读取数据,但当其他事务尝试写数据时,就会出现排斥性。举个例子简单理解:
-- 窗口1:
-- 开启一个事务
begin;
-- 获取共享锁并查询 id=2 的数据 这里的“lock in share mode”就是加共享锁操作
select * from bank_balance where id=2 lock in share mode;-- 窗口2:
-- 开启一个事务
begin;
-- 获取共享锁并查询 id=2 的数据
select * from bank_balance where id=2 lock in share mode;-- 尝试修改id=2的数据
update bank_balance set balance=230 where id=2;