知行合一
Github
顺翔的技术驿站
顺翔的技术驿站
  • README
  • ABOUTME
  • Computer Science
    • 数据结构与算法
      • 位运算以及位图
      • 随机数
      • 递归
      • 经典排序算法
      • 经典查找算法
      • 数组和动态数组
      • 链表
      • 栈和队列
      • 树
      • 哈希表
    • 计算机网络
      • 物理层
      • 数据链路层
      • 网络层
        • TCP
      • 运输层
      • 应用层
      • HTTP
        • HTTPS的原理
        • DNS详解
        • file协议
        • 邮件协议
    • 设计模式
      • 单例模式
      • 建造者模式
      • 原型模式
      • 工厂模式
      • 享元模式
      • 代理模式
      • 装饰者模式
      • 桥接模式
      • 适配器模式
      • 外观模式
      • 组合模式
      • 事件驱动
      • 有限状态机
      • 备忘录模式
      • 模板方法模式
      • 策略模式
      • 迭代器模式
      • 命令模式
      • 解释器模式
    • 加密与解密
      • 数字证书原理
      • cfssl
  • Programming Language
    • 编程语言学习要素
    • Java
      • 集合
        • List
          • ArrayList
          • Vector
          • Stack
          • LinkedList
        • Iterator
        • Set
          • HashSet
          • TreeSet
        • Map
          • HashMap
          • HashTable
          • TreeMap
          • LinkedHashMap
      • 常用API
        • 日期时间处理
        • System
        • Random
        • Arrays
        • Scanner
        • 格式化输出
      • java特性
        • java5特性
        • java8特性
        • java9特性
        • java10特性
        • java11特性
      • 并发编程
        • 线程基础
        • 线程同步:synchronized及其原理
        • 线程同步: volatile
        • 锁机制
        • 锁的分类与对应的Java实现
        • JUC:同步辅助类
        • JUC: AtomicXXX
        • 线程池
        • ThreadLocal详解
      • 测试
        • 使用JMH进行基准测试
      • JVM
        • 强引用、软引用、弱引用、虚引用
        • jvm内存模型
        • jvm优化
        • GC算法与回收器
        • 静态绑定与动态绑定
      • ORM
        • Mybatis
          • IBatis常用操作
      • Web编程
        • Servlet详解(一)
        • Servlet详解(二):request和response对象
        • Servlet详解(三):会话技术与Cookie
        • JSP详解(一):页面构成、EL表达式
        • JSP详解(二):九大内置对象
        • JavaWeb的编码问题
        • Thymeleaf
      • Velocity
      • Java日志框架总结
      • Spring
        • SpringIOC
        • SpringMVC
        • SpringBoot源码
      • 其他
        • Apache Commons Lang使用总结
        • 使用FtpClient进行ftp操作
        • Java PDF操作总结
        • Java使用zip4j进行文件压缩
        • Java解析Excel总结
    • JVM Language
      • Groovy
      • Scala
    • Kotlin
      • 变量和常量
      • 数据类型
        • 基本数据类型
        • 容器类型
        • 函数类型
        • null和null安全
      • 流程控制
      • 包
      • 面向对象
    • Golang
      • 关键字与标识符
      • 变量和常量
      • 数据类型
      • 函数
      • 常用API
        • 时间日期处理
        • 字符串操作
        • 正则表达式
      • 控制语句
      • 包package
      • 面向对象
      • 错误处理
      • 命令行编程
        • Cobra
      • 文件操作
      • 测试
      • 并发编程
        • sync包详解
      • 数据格式与编码
        • 使用encoding包操作xml
        • 使用encoding包操作json
        • 使用magiconair操作properties
        • 使用go-ini操作ini
      • 反射
      • Build Tools
        • Go Module
        • Go Vendor
      • 日志框架
        • zap日志框架
      • Web编程
        • Gin
    • JavaScript
      • 数据类型
      • ECMAScript
        • ECMAScript6
      • NodeJS
    • TypeScript
      • 变量和常量
      • 数据类型
      • 函数
      • 面向对象
      • 泛型
      • Build Tools
        • tsc编译
        • 与webpack整合
    • Python
      • BuildTools
        • requirements.txt
        • Ananconda
    • Swift
      • 变量和常量
    • Script Language
      • Regex
      • BAT
      • Shell
    • Markup Language
      • Markdown
      • Yaml
  • Build Tools
    • CMake
    • Maven
      • 搭建Nexus私服
      • maven使用场景
    • Gradle
  • Version Control
    • Git
      • Git工作流
      • Git分支管理
      • Git Stash
      • Git Commit Message规范
      • .gitttributes文件
    • SVN
  • Distributed
    • 分布式基础理论
      • 互联网架构演变
      • 架构设计思想AKF拆分原则
      • CAP理论
      • BASE理论
    • 一致性
      • 一致性模型
      • 共识算法
        • Paxos
        • Raft
        • ZAB
      • 复制
        • 主从复制
        • Quorum机制
        • Nacos Distro协议
      • 缓存一致性
        • 双写一致性
        • 多级缓存一致性
    • 事务一致性
      • Seata
      • 本地消息表实现方案
      • 关于dpad的事务问题的分析
    • IO
    • RPC协议
    • 序列化
    • Session共享
    • 分布式协调
      • Zookeeper
        • zk集群4节点搭建
    • 服务治理
      • Dubbo分布式治理
    • 分布式ID
      • 分布式ID生成策略总结
    • 分布式锁
    • 应用服务器
      • Tomcat
    • Web服务器
      • Nginx
        • Nginx的基本配置
        • ab接口压力测试工具
        • nginx模块
        • 随机访问页面
        • 替换响应内容
        • 请求限制
        • 访问控制
        • 状态监测
        • nginx应用场景
        • 代理服务
        • 负载均衡
        • 缓存
        • 静态资源服务器和动静分离
        • 附录
      • Kong
    • 缓存中间件
      • Caffeine
      • memcached
      • Redis
        • Centos下安装Redis
        • RatHat下安装Redis
    • 数据库中间件
      • ShardingSphere
      • MyCat2
    • 消息中间件
      • Kafka
      • RocketMQ
  • Microservices
    • 服务发现
      • Nacos注册中心
      • Consul
    • 配置中心
      • Apollo
    • 消息总线
    • 客户端负载均衡
    • 熔断器
    • 服务网关
    • 链路追踪
      • Skywalking
  • Domain-Specific
    • Auth
      • 有关权限设计的思考
      • 认证方式
      • JWT
    • 任务调度
      • QuartzScheduler
      • Elastic-Job
      • XXL-Job
      • PowerJob
    • 工作流
      • BPM
      • Activiti
      • Flowable
    • 规则引擎
      • Drools
  • Architect
    • DDD领域驱动设计
      • 三层架构设计
      • 四层架构设计
    • Cola
    • 代码设计与代码重构
      • 重构改变既有代码设计
      • 枚举规范化
      • 接口幂等
      • 限流
      • 历史与版本
      • 逻辑删除和唯一索引
      • 业务对象设计
    • 单元测试
      • SpringBoot单元测试实践
    • 项目管理
    • APM
      • SkyWalking
      • Arthas
    • 性能优化
      • 接口性能优化
    • 系统设计
      • 流程中台
      • 短信中台
      • 权限中台
        • 智电运维平台组织架构改造二期
  • Database
    • Oracle
      • Docker下安装oracle11g
    • IBM DB2
    • Mysql
      • 安装Mysql
      • 用户与权限管理
      • MySQL的逻辑架构
      • 存储引擎
      • 索引详解
      • MySql的列类型
      • MySql中表和列的设计
      • MySql的SQL详解
      • 锁机制
      • 事务
      • Mysql函数总结
      • MySql存储过程详解
      • MySql触发器详解
      • Mysql视图详解
      • Mysql中Sql语句的执行顺序
      • 配置MySql主从和读写分离
      • MySql的备份策略
      • MySql分库分表解决方案
      • MySql优化总结
      • MySQL实战调优
        • schema与数据类型优化
    • Mongo
  • File System
    • README
    • HDFS
    • FastDFS
    • MinIO
  • Linux
    • 常用的Linux命令
    • vim
    • Linux磁盘管理
    • Linux系统编程
    • RedHat
      • rpm包管理器具体用法
    • Ubuntu
      • Ubuntu下录制屏幕并做成gif图片
      • Ubuntu20.05LiveServe版安装
  • DevOps
    • VM
      • 新建一个新的Linux虚拟机需要配置的东西
      • VMware桥接模式配置centos
      • VMwareFusion配置Nat静态IP
    • Ansible
    • Container
      • Docker
        • Dockerfile详解
        • DockerCompose详解
      • Containerd
    • Kubernetes
      • 安装k8s
        • 使用Minikube安装k8s
        • centos7.x下使用kubeadm安装k8s1.21
        • ubuntu20下使用kubeadm安装k8s1.21
        • centos7.x下使用二进制方式安装k8s1.20
        • 使用DockerDesktop安装K8s(适用M1芯片)
      • 切换容器引擎
      • 使用k8s部署项目的流程
      • 集群维护-备份升级排错
    • Gitlab
      • GitlabCI/CD
    • CI/CD
      • ArgoCD
  • Big-Data
    • Hadoop
    • MapReduce
    • HDFS
  • Front-End
    • Android
      • Log的使用、自定义Log工具类
      • Android倒计时功能实现
      • 解决ViewDrawableLeft左侧图片大小不可控的问题
      • AndroidSQLite基本用法
      • View的生命周期
      • 工具类
      • WebView详解
      • ViewTreeObserver类监听ViewTree
      • 在onCreate中获取控件的宽高等信息的几种方法
      • View的foreground属性
        • MaterialDesign
          • BottomNavigationBar
          • CardView
          • Elevation高度、shadows阴影、clipping裁剪、tint着色
          • TouchFeedbackRipple波纹动画
      • Volley完全解析——使用、源码
      • Android围住神经猫的实现
      • LookLook剖析,架构概述——MVP、Retrofit+RxJava
      • Android性能优化之渲染
    • Browser
      • 浏览器的工作原理
    • HTML
      • DOCTYPE标签、XHTML与HTML的区别
    • CSS
      • CSS的继承性、层叠性、权重
      • CSS浮动float详解(一):标准文档流
      • CSS浮动float详解(二):使用float
      • CSS浮动float详解(三):清除浮动方案
    • Tools Lib
      • JavaScript 文件下载解决方案-download.js
      • js-url 用于url的js开源库
      • jsuri 用于操作url的js开源库
      • window offset
    • React
      • 模块化和组件
      • 组件的三大核心属性
      • 事件处理
      • 表单数据收集
      • 生命周期
      • DOM的diff算法
      • 工程化
        • 脚手架create-react-app
        • 工程结构和模块化
      • 路由
  • Design
    • 产品设计
      • 交互设计
由 GitBook 提供支持
在本页
  • 锁的内存结构
  • 全局锁
  • 表锁
  • 查看已经添加表锁的表
  • 表级别的S锁和X锁
  • 意向锁(Intention Lock)
  • 自增锁(AUTO-INC锁)
  • 元数据锁(MDL 锁)
  • 页锁
  • 行锁
  • 记录锁(Record Locks)
  • 间隙锁(Gap Locks)
  • 临建锁(Next-Key Locks)
  • 插入意向锁(Insert Intention Locks)
  • 锁升级
  • 乐观锁和悲观锁
  • 悲观锁
  • 乐观锁
  • 隐式锁和显示锁
  • 隐式锁
  • 显示锁
  • 死锁
  • 解决死锁:设置超时时间
  • 解决死锁:MySQL的死锁检测
  • 如何避免死锁

这有帮助吗?

在GitHub上编辑
  1. Database
  2. Mysql

锁机制

上一页MySql的SQL详解下一页事务

最后更新于2年前

这有帮助吗?

在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。为保证数据的一致性,需要对 并发操作进行控制 ,因此产生了锁。同时 锁机制 也为实现MySQL的各个隔离级别提供了保证。 锁冲突 也是影响数据库 并发访问性能 的一个重要因素。所以锁对数据库而言显得尤其重要,也更加复杂。

MySQL的锁分类如下:

锁的内存结构

InnoDB的锁的内存结构如下:

  1. 锁所在的事务信息,不论是 表锁 还是 行锁 ,都是在事务执行过程中生成的,哪个事务生成了这个 锁结构 ,这里就记录这个事务的信息。此 锁所在的事务信息 在内存结构中只是一个指针,通过指针可以找到内存中关于该事务的更多信息,比方说事务id等。

  2. 索引信息,对于 行锁 来说,需要记录一下加锁的记录是属于哪个索引的。这里也是一个指针。

  3. 表锁/行锁信息,表锁结构 和 行锁结构 在这个位置的内容是不同的:

  4. 表锁:记载着是对哪个表加的锁,还有其他的一些信息

  5. 行锁:记载了三个重要的信息

    1. Space ID :记录所在表空间。

    2. Page Number :记录所在页号。

    3. n_bits :对于行锁来说,一条记录就对应着一个比特位,一个页面中包含很多记录,用不同的比特位来区分到底是哪一条记录加了锁。为此在行锁结构的末尾放置了一堆比特位,这n_bits 属性代表使用了多少比特位。

  6. type_mode,是一个32位的数,被分成了如下几个部分:

    1. lock_mode,锁的模式,比如是共享、独占、意向、自增

    2. lock_type,锁的类型,表级锁还是行级锁

    3. rec_lock_type,间隙锁、记录锁、临建锁、元数据锁

    4. is_waiting,为了节省空间将该属性放在这,代表事务是否获取到了锁

  7. 其他信息,为了更好的管理系统运行过程中生成的各种锁结构而设计了各种哈希表和链表

  8. 一堆比特位,如果是 行锁结构 的话,在该结构末尾还放置了一堆比特位,比特位的数量是由上边提到的 n_bits 属性表示的。InnoDB数据页中的每条记录在 记录头信息 中都包含一个 heap_no 属性,伪记录 Infimum 的 heap_no 值为 0 , Supremum 的 heap_no 值为 1 ,之后每插入一条记录, heap_no 值就增1。 锁结 构 最后的一堆比特位就对应着一个页面中的记录,一个比特位映射一个 heap_no ,即一个比特位映射到页内的一条记录。

全局锁

全局锁就是对 整个数据库实例加锁。当你需要让整个库处于 只读状态 的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。全局锁的典型使用 场景 是:做全库逻辑备份 。

全局锁命令:

flush tables with read lock

表锁

表锁会锁定整张表,他是MySQL中最基本的锁策略,不依赖存储引擎。由于表锁一次会将整个表锁定,所以可以很好地避免死锁的问题,但是因为锁的粒度太大,出现锁争抢的概率也最高,所以并发效率处理很低。

查看已经添加表锁的表

show open tables where in_use > 0;

表级别的S锁和X锁

使用如下命令可以给表增加S锁或者X锁:

  • LOCK TABLES 表名 READ:InnoDB存储引擎会对表加表级别的S锁

  • LOCK TABLES 表名 WRITE:InnoDB存储引擎会对表加表级别的X锁

如果要对表进行解锁,需要使用unlock:

lock tables mylock read;
unlock; -- 释放锁

一般情况下,不会使用InnoDB存储引擎提供的表级别的S锁和X锁。只会在一些特殊情况下,比如崩溃恢复中用到。因为表锁不由InnoDB管理,所以InnoDB无法自动检测并处理这种锁的死锁。如果要在InnoDB中使用表锁,还要保证autocommit=0,innodb_table_locks = 1 这两个变量的值。

S锁和X锁的特点:

锁类型
当前事物可读
当前事务可写
当前事务可操作其他表
其他事务可读
其他事务可写

读锁

√

×

×

√

×

写锁

√

√

×

×

×

意向锁(Intention Lock)

意向锁允许行级锁和表级锁共存,是InnoDB提供的一种多粒度锁(multiple granularity locking),它的存在主要为了:

  1. 协调行锁与表锁的关系

  2. 是一种不与行级锁冲突的表级锁

  3. 表明某个事务正在某些行持有了锁或该事务有意向准备去去持有锁

意向锁主要解决的问题:

  1. 有T1、T2两个线程

  2. T1对目标表增加了行锁-X锁

  3. T2想要对目标表增加表锁

  4. 如果没有意向锁,那么T2要遍历所有的行,查询是否有行加了行锁,如果有会进行互斥

  5. 如果有了意向锁,T1增加行锁后,会给当前表增加一个意向锁,T2再要给表增加表锁时,会发现有意向锁,避免遍历所有行操作,直接阻塞,大大提高了表锁的性能。

意向锁实际就是一个标记,标记当前表是否有行级锁。这样其他事务要对表进行加锁时,就不用遍历整张表判断是否可以加锁了。

增加行锁会自动增加表意向锁,根据行锁的类型,意向锁也分为两种:

  • 意向共享锁(IS锁),事务有意向对表中的某些行加共享锁(S锁)

    -- 增加行S锁,自动增加IS锁
    SELECT columns FROM table... LOCK IN SHARE MODE; 
  • 意向排他锁(X锁),是有有意向对表中的某项行加排他锁(X锁)

    -- 增加行X锁,自动增加IX锁
    SELECT columns FROM table... FOR UPDATE;

自增锁(AUTO-INC锁)

在使用MYSQL的过程中,我们可以为表的某个列增加AUTO_INCREMENT属性。因为AUTO_INCREMENT字段声明了自动增长,所以在插入语句内是不用赋值的,他会自动增长。

针对自增字段插入自动对自增字段的行为可以将插入语句大致可以分为3种情况:

  1. Simple Insert,简单插入,在插入之前就可以通过一些方式确认自增字段值的SQL:

    INSERT INTO teacher(id, name) VALUE(1, 'aaa');
    INSERT INTO teacher(id, name) VALUE(2, 'bbb'), (3, 'ccc');
    INSERT INTO teacher(name) VALUES('张三');
    INSERT INTO teacher(name) VALUES('李四'), ('王五'), ('赵六');
    REPLACE INTO TABLE (name) VALUES(1,'aa'), (2,'bb');
  2. Bulk inserts,批量插入,在SQL执行前无法得知自增字段的值的语句,InnoDB将每处理一行就给给记录分配一个值,比如:

    INSERT INTO Websites (name, country) SELECT app_name, country FROM apps WHERE id=1;
    LOAD DATA 语句
  3. Mixed-mode Inserts,混合模式插入,部分的数据在SQL执行前可以确认,部分的不可以:

    INSERT INTO teacher(id, name) VALUE(1, 'aaa'), (NULL, 'ccc'), (4, 'ddd'), (NULL, 'eee');

为了保证上述三种模式的插入可以正常工作,所以引入了自增锁。因为自增锁针对自增列进行加锁,目标是自增列的所有行,所以自增锁是表锁。他共有三种模式,每种方式的锁的实现不同,由变量innodb_autoinc_lock_mode决定:

  1. innodb_autoinc_lock_mode = 0,传统锁定模式

    每当执行insert的时候,都会得到一个表级锁(AUTO-INC锁),使得语句中生成的auto_increment为顺序,且在binlog中重放的时候,可以保证master与slave中数据的auto_increment是相同的。因为是表级锁,当在同一时间多个事务中执行insert的时候,对于AUTO-INC锁的争夺会 限制并发 能力。

  2. innodb_autoinc_lock_mode = 1,连续锁定模式

    在 MySQL 8.0 之前,连续锁定模式是 默认 的。在这个模式下,“bulk inserts”仍然使用AUTO-INC表级锁,并保持到语句结束。这适用于所有INSERT ... SELECT,REPLACE ... SELECT和LOAD DATA语句。同一时刻只有一个语句可以持有AUTO-INC锁。对于“Simple inserts”(要插入的行数事先已知),则通过在 mutex(轻量锁) 的控制下获得所需数量的自动递增值来避免表级AUTO-INC锁, 它只在分配过程的持续时间内保持,而不是直到语句完成。不使用表级AUTO-INC锁,除非AUTO-INC锁由另一个事务保持。如果另一个事务保持AUTO-INC锁,则“Simple inserts”等待AUTO-INC锁,如同它是一个“bulk inserts”。

  3. innodb_autoinc_lock_mode = 2,交错锁定模式

    从 MySQL 8.0 开始,交错锁模式是 默认 设置。在此锁定模式下,自动递增值 保证 在所有并发执行的所有类型的insert语句中是 唯一 且 单调递增 的。但是,由于多个语句可以同时生成数字(即,跨语句交叉编号),为任何给定语句插入的行生成的值可能不是连续的。

元数据锁(MDL 锁)

MDL锁不需要显示添加,他会按照以下方式隐式添加:

  1. 当对一个表做增删改查操作的时候,加 MDL读锁

  2. 当要对表做结构变更操作的时候,加 MDL 写锁

读读不互斥、读写/写读互斥

也就是说当发生表结构变更的时候,不能进行任何的增删改查操作,防止出现非预期的情况。

页锁

页锁就是在页的粒度上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录。

  1. 当我们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行。

  2. 页锁的开销介于表锁和行锁之间,会出现死锁。

  3. 锁定粒度介于表锁和行锁之间,并发度一般。

行锁

行锁也称为记录锁,用于锁定某条记录。Mysql本身没有提供对行锁的实现,行锁只在存储引擎中实现,且只有InnoDB才支持行锁。

  • 优点,锁的粒度小,发生锁冲突的概率比较低,可以实现的并发高。

  • 缺点,对锁的开销大,加锁比较慢,容易出现死锁。

记录锁(Record Locks)

记录锁就是将一条记录加锁,他的官方名称为LOCK_REC_NO_GAP,非间隙锁。比如我们将id=8的记录使用记录锁记录,如下所示,因为是无间隙锁,所以对其周围的数据没有影响:

间隙是指上图中,存在的记录之前不存在的记录的位置。比如 3和8之间存在 4~7的间隙。

记录锁也分为两种:

  • X型记录锁

  • S型记录锁

举例:

  1. 事务1对id=3的数据进行操作,默认会给李四这条数据增加一个X记录锁

  2. 事务2对id!=3的数据可以进行正常操作,因为其他行没有加锁

  3. 但是事务2对id=3的数据进行操作时将会阻塞等待锁释放

给行增加记录锁:

select * from EMP for update;          -- 给所有的行增加x记录锁
select * from EMP lock in share mode;  -- 给所有的行增加s记录锁

注意,x锁和s锁互斥,但是x锁不和无锁互斥,比如: select * from xx for update 与 select * from xx lock in share互斥,但是与select * from xx 不互斥。

间隙锁(Gap Locks)

间隙锁是为了防止插入幻影记录提出的,给指定的不存在的间隙位置加一个范围锁,防止在操作时插入数据,发生幻读。下图展示了在id值为8的记录上增加间隙锁,意味着不可以在id为8这条记录前面的间隙新插入任何记录(也就是3~8之间),所以间隙锁主要解决幻读的问题:

间隙锁的范围也可以是一个不存在的id记录,比如如下SQL:

-- 给3~6之间增加间隙锁,6是一个不存在的位置
select * from student where id = 7;

如果想要在范围21~25增加间隙锁:

select * from student where id = 26;

如果想要在21 ~ +∞ 上增加间隙锁,可以借助Supermum记录,它表示最大记录,SQL可以这么写:

select * from student where id >= 20;

如果想过要在-∞ ~ 20这个范围内增加间隙,可以借助Infimum记录,他表示最小记录,SQL可以这么写:

select * from student where id <= 21;

如果想要在0~20这个范围内增加间隙:

select * from student where id < 21 and id > 0;

在mysql的事务隔离级别REPEATABLE-READ下,给记录加锁默认会添加间隙锁,可以解决大部分的幻读问题。

间隙锁容易出现死锁的问题,因为他都是操作范围的行,很容易发生交叉锁的问题。

临建锁(Next-Key Locks)

间隙锁影响的是开区间的范围,比如

select * from student where id = 25; -- 影响的范围是 21 ~ 24这个范围

而有时候我们既想 锁住某条记录 ,又想 阻止 其他事务在该记录前边的 间隙插入新记录,所以InnoDB就提出了一种称之为 Next-Key Locks 的锁,官方的类型名称为: LOCK_ORDINARY ,我们也可以简称为next-key锁 。Next-Key Locks是在存储引擎 innodb 、事务级别在 可重复读 的情况下使用的数据库锁,innodb默认的锁就是Next-Key locks。

比如,上述范围如果是临建锁:

select * from student where id = 25; -- 影响的范围是 21 ~ 25这个范围

插入意向锁(Insert Intention Locks)

  1. 一个事务在 插入 一条记录时需要判断一下插入位置是不是被别的事务加了 gap锁 ( next-key锁也包含 gap锁),如果有的话,插入操作需要等待,直到拥有 gap锁 的那个事务提交。

  2. 但是InnoDB规定事务在等待的时候也需要在内存中生成一个锁结构,表明有事务想在某个间隙中插入新记录,但是现在正在等待中。

  3. InnoDB就把这种类型的锁命名为 Insert Intention Locks ,官方的类型名称为:LOCK_INSERT_INTENTION ,我们称为 插入意向锁 。

  4. 插入意向锁是一种 Gap锁 ,不是表的意向锁,在insert操作时产生。

  5. 插入意向锁是在插入一条记录行前,由 INSERT 操作产生的一种间隙锁 。事实上插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁。

锁升级

每个层级(表、页、行锁)的锁数量是有限制的,因为锁会占用内存空间, 锁空间的大小是有限的 。当某个层级的锁数量超过了这个层级的阈值时,就会进行锁升级 。锁升级就是用更大粒度的锁替代多个更小粒度的锁,比如InnoDB 中行锁升级为表锁,这样做的好处是占用的锁空间降低了,但同时数据的并发度也下降了。

乐观锁和悲观锁

从对待锁的态度来看锁的话,可以将锁分成乐观锁和悲观锁,从名字中也可以看出这两种锁是两种看待数据并发的思维方式 。需要注意的是,乐观锁和悲观锁并不是锁,而是锁的设计思想 。

悲观锁

悲观锁是一种思想, 他对数据被其他事务的修改持保守态度,会通过数据库自身的锁机制来实现,从而保证数据操作的排它性。

悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 阻塞 直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,当其他线程想要访问数据时,都需要阻塞挂起。

Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

悲观锁 适合 写操作多 的场景,因为写的操作具有 排它性 。采用悲观锁的方式,可以在数据库层面阻止其他事务对该数据的操作权限,防止读 - 写和写 - 写的冲突。

乐观锁

乐观锁认为对同一数据的并发操作不会总发生,属于小概率事件,不用每次都对数据上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,也就是不采用数据库自身的锁机制,而是通过程序来实现。在程序上,我们可以采用 版本号机制 或者 CAS机制 实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。

在Java中 java.util.concurrent.atomic 包下的原子变量类就是使用了乐观锁的一种实现方式:CAS实现的。

在MySQL数据库中,可以使用如下几种方式实现乐观锁:

  1. 使用版本号机制:在表中设计一个 版本字段 version ,第一次读的时候,会获取 version 字段的取值。然后对数据进行更新或删除操作时,会执行 UPDATE ... SET version=version+1 WHERE version=version。此时如果已经有事务对这条数据进行了更改,修改就不会成功。

  2. 使用时间戳机制:也是在更新提交的时候,将当前数据的时间戳和更新之前取得的时间戳进行比较,如果两者一致则更新成功,否则就是版本冲突。你能看到乐观锁就是程序员自己控制数据并发操作的权限,基本是通过给数据行增加一个戳(版本号或者时间戳),从而证明当前拿到的数据是否最新。

乐观锁 适合 读操作多 的场景,相对来说写的操作比较少。它的优点在于 程序实现 , 不存在死锁问题,不过适用场景也会相对乐观,因为它阻止不了除了程序以外的数据库操作。

隐式锁和显示锁

隐式锁

显示锁

死锁

在数据库中的死锁案例,以行锁为例:

时间点
事务1
事务2

1

begin;

begin;

2

update account set money=100 where id = 1;

3

update account set money=100 where id =2;

4

update account set money=200 where id =2; 这里id=2的数据已经被事务2占用所以一直等待

5

update account set money=200 where id = 1; 这里id=1的数据已经被事务1占用所以也一直等待

发生死锁的必要条件:

  1. 两个两个或以上的事务

  2. 每个事务都已经持有锁并且申请新的锁

  3. 锁资源同时只能被同一个事务持有者不兼容

  4. 事务之间因为持有锁和申请锁导致彼此循环等待

解决死锁:设置超时时间

通过属性innodb_block_wait_timeout设置,单位为妙,默认为50s。但是缺点在于时间不好设置,长了会等待很久,短了会导致正常执行的线程中断。

解决死锁:MySQL的死锁检测

死锁检测会主动检测该操作会不会造成死锁,如果会造成死锁将会报错。他的主要原理是构建一个以事务为顶点,锁为边的有向图,并判断有向图是否存在环,存在就说明有死锁。

他的主要机制如下:

  1. 它主要由数据库存储事务等待链表(左图)以及锁信息链表(右图)

  2. 以row2为例,T1先在row2上获取了S锁,T4也是S锁,所以T4不需要等待T1,而T2需要等待T1,T3需要等待T2

  3. 根据上述信息绘制wait for graph等待图:

  4. 当发现有环时,就代表出现了死锁。

开启死锁检测:

innodb_deadlock_detect=on

死锁检测每次都要检测一次所有环路,那么n个线程加入就要检测n次,那么复杂度O(n),所以对CPU也是一种不小的损耗。可以通过以下两种方式解决:

  1. 关闭死锁检测,但是业务可能有损。

  2. 控制并发访问的数量。

如何避免死锁

  1. 合理设计索引,使业务SQL尽量通过索引定位更少的行,减少锁竞争

  2. 调整业务SQL的执行顺序,避免需要加锁的操作放在前面

  3. 避免大事务,尽量将大事务拆分为多个小事务处理,减少锁冲突

  4. 并发场景较大的情况,尽量不要显示加锁

  5. 如果业务员允许,降低隔离级别,比如将隔离级别从可重复的降低为读提交,可以减少间隙锁造成的死锁。

image-20220313150537071
image-20220313150731873
image-20220313151613894
image-20220313100128548
image-20220313111158449
image-20220313120732608
image-20220313140741395
image-20220313141552747