锁总览:
锁是基于索引去找到数据记录再加锁的,而索引的规则是通过索引找到主键索引,所以:
如果没用索引去做要加排他锁的操作(更新),会上表锁
通过 唯一/主键索引 等值加锁 ,只会锁具体的行,非唯一索引则不一定,SQL优化器会基于数据分布来选择行锁或临键锁
RR 级别下才会有间隙锁,如果操作的数据是跨多个范围,就会加多个区间的间隙锁
MySQL的默认锁就是【临键锁】,所以在执行SQL时,记录(行)锁和间隙锁会同时存在,范围是 ( 左开, 右闭 ]
按性质划分
共享(S)读锁
当前线程加了共享读锁时,所有线程都只允许使用 DQL 语句进行查询(读)。不允许写操作(DML、DDL),本线程也不行
独占(排它 X)写锁
当前线程加了独占写锁时,本线程可以读写操作,其他线程任何操作都不允许
按粒度划分
全局锁
-
锁定所有表,全库备份时使用
-
阻塞所有写操作(DML、DDL),仅允许读操作
-
使用
mysqldump --single-transaction
通过事务实现一致性备份,可以避免锁表
表级锁
1. 表锁
-
显式加锁:
LOCK TABLES table_name READ/WRITE
-
隐式加锁:MyISAM 引擎的查询/更新自动加表锁
-
解锁:
UNLOCK TABLES
或事务结束自动释放
2. 元数据锁(Meta Data Lock)
DML:Data Manipulate Language,比如增删改
DDL:Data Definition Language,比如 create、drop、alter..
-
DML 加读锁,DDL 加写锁。
-
读锁之间兼容,读写锁互斥。长事务未提交时,DDL 操作会被阻塞,导致后续查询排队
3. 意向锁
-
加与当前锁性质相同的锁(共享/排它)
-
虽然锁表的场景很少,如果要用共享锁锁表不得一行行去遍历看看数据有没有被排他锁锁过,如果要用排它锁锁表就得一行行去遍历看是否存在数据被共享锁或排他锁占用。
实际上是不会遍历的,代价太大
所以在对某些数据加共享锁的时候,就会给表加上 意向共享锁,对某些数据加排他锁的时候就会对表加上 意向排他锁。
避免加表锁时一行一行去查看行锁加锁情况,解决上述低效而引入的,隐式加的锁
行级锁
1. 行锁
对单个记录加锁。RC 和 RR 都支持
LOCK IN SHARE MODE
(S 共享锁)
SELECT ... FOR UPDATE
(X 排它锁)
2. 间隙锁 (左开右开)
在记录间的间隙加锁。为了解决 RR 隔离级别下的幻读,幻读的产生是当前事务多次的查询结果数量上不一致,间隙锁的目的就是保证当前范围内的数据不会被更改,所以它会锁住某些个区间的数据。
3. 临键锁 (左开右闭]
!!! 只有在 RR 隔离级别下才会用临键锁
对查出来的记录和记录的间隙加锁(其实就是行锁 + 间隙锁),锁住记录以及其前方间隙
间隙锁和临键锁都能解决幻读问题
4. 插入意向锁
执行 INSERT 前申请,标记要插入的位置,与间隙锁互斥
按思想划分
乐观锁
乐观锁就是不加锁,通过 CAS 解决并发问题,比如添加 version 字段
悲观锁
悲观锁一定加锁