资料总结 资料总结
首页
go
java
云原生
  • mysql
  • redis
  • MongoDB
  • 设计模式详解
  • 数据结构与算法
  • 前端
  • 项目
  • 理论基础
  • 运营
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

linghui Wu

一只努力学飞的鱼
首页
go
java
云原生
  • mysql
  • redis
  • MongoDB
  • 设计模式详解
  • 数据结构与算法
  • 前端
  • 项目
  • 理论基础
  • 运营
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 数据库表设计
  • mysql

    • 索引
    • mysql
    • Sql优化
    • 事务
    • 锁机制
      • 从性能上
      • 从对数据库操作(锁的产生)
      • 从对数据操作的粒度
        • 全局锁
        • 表锁
        • 操作
        • 行锁
        • 注意:
        • 操作
        • 元数据锁
      • 间隙锁(Gap Lock)
      • 临键锁(Next-key Locks)
      • 总结:
        • 死锁问题
      • 排查
      • 解决机制
        • 锁优化建议
        • 存储引擎中锁的区别
      • MyISAM
      • InnoDB
        • 参考资料
    • MVCC
    • MySQL高可用架构
    • 运维内容
  • redis

  • MongoDB

  • InfluxDB
  • 数据库
  • mysql
wulinghui
2022-02-21
目录

锁机制

# 简介

事务并发访问同一数据资源的情况主要就分为读-读、写-写和读-写三种。

读-读 即并发事务同时访问同一行数据记录。由于两个事务都进行只读操作,不会对记录造成任何影响,因此并发读完全允许。

写-写 即并发事务同时修改同一行数据记录。这种情况下可能导致脏写问题,这是任何情况下都不允许发生的,因此只能通过加锁实现,也就是当一个事务需要对某行记录进行修改时,首先会先给这条记录加锁,如果加锁成功则继续执行,否则就排队等待,事务执行完成或回滚会自动释放锁。

读-写 即一个事务进行读取操作,另一个进行写入操作。这种情况下可能会产生脏读、不可重复读、幻读。最好的方案是读操作利用多版本并发控制(MVCC),写操作进行加锁。

可以看到锁主要解决写,写 的场景

# 锁分类

# 从性能上

  • 乐观锁(用版本对比来实现)

    不是数据库自带的,需要我们自己去实现。

  • 悲观锁

    悲观锁是由数据库自己实现了,就是写锁,for update

# 从对数据库操作(锁的产生)

  • 读锁(共享锁,S锁(Shared)) : 针对同一份数据,多个读操作可以同时进行而不会互相影响

    InnoDB通过使用lock in share mode加读锁,但是注意只锁覆盖索引,可以更新id主键相关的内容

  • 写锁(排它锁,X锁(eXclusive)) : 写锁(排它锁,X锁(eXclusive))

    InnoDB所有的DML操作默认加写锁。

    select可以通过for update加写锁,并且会锁住所有索引,不仅仅是索引覆盖的索引。

    所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。但是注意在MyISAM引擎中因为读也会自动加上读锁,所以使用排它锁的时候是不能去读的,这点要和InnoDB区别开

读锁会阻塞写,但是不会阻塞读。而写锁则会把读和写都阻塞

# 从对数据操作的粒度

# 全局锁

全局锁即对整个数据库实例加锁,使得整个库处于只读状态,会阻塞DML和DDL语句。

# 表锁

  • 每次操作锁住整张表,一般用在整表数据迁移的场景。
  • 开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低

# 操作

  • 手动增加表锁 : lock table 表名称 read(write),表名称2 read(write);
  • 查看表上加过的锁: show open tables;
  • 删除表锁 : unlock tables;

# 行锁

  • 每次操作锁住一行数据。
  • 开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。
  • InnoDB支持行级锁,MYISAM不支持

# 注意:

行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为表锁。

# 操作

参考读写锁的操作。

# 元数据锁

元数据锁(meta data lock 简称MDL) 用于确保事务执行过程中表结构的稳定。

# 间隙锁(Gap Lock)

  • 其他Session没法在这个范围所包含的所有行记录(包括间隙行记录)以及行记录所在的间隙里插入或修改任何数据(锁的操作都不能.)
  • 锁的就是两个值之间的空隙,中访问到的对象才会加锁。
  • 间隙锁是在可重复读隔离级别下才会生效。
  • 左开右开区间

# 临键锁(Next-key Locks)

  • 行锁与间隙锁的组合
  • 它的封锁范围,既包含索引记录,又包含索引区间。
  • 可以解决幻读的问题。
  • 在可重复读隔离级别下才会生效。
  • 临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。
  • 左开右闭区间

# 总结:

以下都是针对锁的前提下:

1、当使用唯一索引来等值查询的语句时, 如果这行数据存在,不产生间隙锁,而是记录锁。

2、当使用唯一索引来等值查询的语句时, 如果这行数据不存在,会产生间隙锁。

3、当使用唯一索引来范围查询的语句时,对于满足查询条件但不存在的数据产生间隙(gap)锁,如果查询存在的记录就会产生记录锁,加在一起就是临键锁(next-key)锁。

4、当使用普通索引不管是锁住单条,还是多条记录,都会产生间隙锁; 没有查询到没有影响。

5、在没有索引上不管是锁住单条,还是多条记录,还是没有查询到,都会产生表锁;

# 死锁问题

当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。

# 排查

  • 查看事务:: select * from INFORMATION_SCHEMA.INNODB_TRX;
  • 查看锁 :: select * from INFORMATION_SCHEMA.INNODB_LOCKS;
  • 锁等待:: select * from INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
  • kill trx_mysql_thread_id
  • 查看锁等待详细信息::: show engine innodb status\G;

# 解决机制

  • 超时

    直接进入等待,直到超时。这个超时时间可以通过参数innodb_lock_wait_timeout 来设置。

  • 死锁主动检查

    发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为on,表示开启死锁检测。

    注意,死锁检查会消耗额外资源,若并发的线过多,则有可能由死锁检测而消耗过多资源而导数据库崩溃。

# 锁优化建议

  • 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
  • 合理设计索引,尽量缩小锁的范围
  • 尽可能减少检索条件范围,避免间隙锁
  • 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行
  • 尽可能低级别事务隔离

# 存储引擎中锁的区别

# MyISAM

MyISAM在执行查询语句SELECT前,会自动给涉及的所有表加读锁,在执行update、insert、delete操作会自 动给涉及的表加写锁。

# InnoDB

InnoDB在执行查询语句SELECT时(非串行隔离级别),不会加锁。但是update、insert、delete操作会加行 锁。

# 参考资料

MySQL锁的浅析到深入 (opens new window)

共享锁,只锁覆盖索引的详细解释。

mysql记录锁(record lock),间隙锁(gap lock),Next-key锁(Next-key lock) (opens new window)

mysql行锁升级为表锁_MySQL 全局锁、表锁以及行锁 (opens new window)

InnoDB行锁升级为表锁的问题 (opens new window)

mysql的锁机制(读锁,写锁,表锁,行锁,悲观锁,乐观锁,间隙锁) (opens new window)

MySQL记录锁、间隙锁、临键锁小案例演示 (opens new window)

mysql事务和锁,一次性讲清楚! (opens new window)

什么时候数据库行锁会升级为表锁? (opens new window)

事务的隔离级别为:可重复读 (opens new window)时,如果有索引 (opens new window)(包括主键索引),以索引列为条件更新数据,会存在间隙锁,行锁,页锁,而锁住一些行。如果没有索引,更新数据时会锁住整张表。

事务隔离级别为:串行化时,读写数据都会锁住整张表。(一次只能一个连接玩表)

MySQL间隙锁问题 (opens new window)

insert 和 delete 同时操作一段间隙,就会导致一个等锁。

解决办法: 修改代码逻辑, 存在才删除,尽量不去删除不存在的记录。

mysql排它锁(FOR UPDATE) 场景介绍 (opens new window)

  • 当前使用for UPDATE (opens new window)查询,其他地方查询 -- 其他地方也使用for UPDATE会堵塞, 其他地方未使用for UPDATE不会堵塞
  • 当前使用for UPDATE查询,其他地方UPDATE更新 -- 会堵塞等待
  • 当前事务使用UPDATE更新, 其他地方使用for UPDATE查询 -- 会堵塞等待
  • 当前非事务使用UPDATE更新, 其他地方使用for UPDATE查询 -- 不会堵塞
  • 当前普通查询,其他地方使用for UPDATE查询 -- 不影响,不会堵塞

总结: for UPDATE 和 update等操作 都是加锁的,select 是不加锁的,所以不会影响。

MySQL-乐观锁 (opens new window) 和 MySQL 技巧:如何实现乐观锁? (opens new window)

1.查询出商品信息

select (status,status,version) from t_goods where id=#{id}

2.根据商品信息生成订单

3.修改商品status为2

update t_goods

set status=2,version=version+1

where id=#{id} and version=#{version};

通过乐观锁+自旋的方式,解决数据更新的线程安全问题,而且锁粒度比悲观锁低,并发性能好。

编辑 (opens new window)
上次更新: 2023/01/24, 15:21:15
事务
MVCC

← 事务 MVCC→

最近更新
01
架构升级踩坑之路
02-27
02
总结
02-27
03
语法学习
02-27
更多文章>
| Copyright © 2021-2025 Wu lingui |
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式