昨天提到了数据库的四种隔离级别,它的实现离不开锁。

mysql的锁有三大类

第一类:全局锁

对整个数据库实例加锁。执行后,整个数据库会进入只读状态

执行效果

  • 所有库的所有表都被锁定
  • 只能执行查询(SELECT)
  • 阻塞所有更新操作(INSERT、UPDATE、DELETE)、DDL(ALTER、DROP)、事务提交

为什么要用全局锁?场景:全库备份(逻辑备份)

全局锁的代价非常直接:业务停摆

  • 加锁期间,所有写入操作都被阻塞
  • 如果是电商网站,用户无法下单、无法支付
  • 如果是社交应用,用户无法发帖、无法点赞

于是,现代数据库提供了更优雅的解决方案。

MySQL 的 –single-transaction 参数

mysqldump --single-transaction --all-databases > backup.sql

原理

  • 不启用全局锁
  • 而是启动一个可重复读隔离级别的长事务
  • 利用 MVCC (多版本并发控制),读取这个事务开始时的数据快照
  • 备份过程中,业务更新可以正常进行(写操作创建新版本,备份读老版本)

第二类:表级锁:

表锁

表锁有两种

读锁 (READ):所有人都可以读,但谁都不能写(包括加锁者自己)

写锁 (WRITE):只有加锁者自己能读写,别人啥都不能干

元数据锁

是一种自动的、隐式的锁,用来保护表结构不被修改。

两种类型

类型加锁时机作用
MDL读锁 (共享)执行 SELECT、UPDATE、INSERT 等 DML 语句时防止执行 DML 时别人改表结构
MDL写锁 (排他)执行 ALTER、DROP、CREATE 等 DDL 语句时防止改表结构时别人还在读写

意向锁

用来快速判断表里有没有行锁

没有时

-- 有人要在 users 表上加表锁
LOCK TABLES users WRITE;

-- 数据库需要检查:users 表里有没有行被锁住?
-- 必须扫描整张表的所有行,看有没有 X锁 → 太慢了!

有了之后

-- 事务A给某一行加行锁(X锁)时,同时自动给 users 表加一个"意向排他锁"
-- 这个意向锁只是一个"标志",不阻塞任何人

-- 事务B想加表锁时,一看 users 表有意向锁,就知道:"哦,表里有行被锁了,我得等着"
-- 不用扫描全表!
注意,元数据锁和意向锁都是自动加的

行级锁

行锁

最基础的锁,锁住索引上的某一条记录

特点

  • 锁的是索引,不是数据行本身
  • 即使表没有索引,InnoDB 也会创建隐藏的聚簇索引
  • 所以行锁总是锁在索引上

间隙锁

锁住一个范围,但不锁记录本身。这是 InnoDB 在可重复读级别下解决幻读的武器。

临键锁

行锁 + 间隙锁的组合,锁住”一个区间 + 右端点”

结语

既然mysql在选择了隔离级别之后会自动加锁,那为什么还要学习数据库的锁呢?

自动加锁的策略是”保守的”,了解了锁之后,可以在”安全”和”性能”之间做权衡,但自动加锁只会选择”安全”。

同样的业务,不同的设计。不了解锁:被数据库牵着走,问题:高并发下性能差,容易死锁。了解锁:主动设计

不了解锁,你是在使用数据库;了解锁,你是在驾驭数据库。

Categories:

Tags:

No responses yet

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注