后端学习 - MySQL存储引擎、索引与事务

文章目录

  • 一 存储引擎
    • 1 MyISAM 与 InnoDB 的差异
  • 二 索引
    • 1 主键索引与二级索引、索引覆盖、延迟关联
    • 2 聚簇索引与非聚簇索引
    • 3 数据结构
      • 3.1 哈希表
      • 3.2 B树
      • 3.3 B+树
      • 3.4 跳表
      • 3.5 为什么不使用红黑树
      • 3.6 为什么不使用B树**
    • 4 索引下推 ICP **
    • 5 索引失效(索引不命中)的情况
    • 6 前缀索引和索引选择性
    • 7 索引用于 ORDER BY
  • 三 MySQL 事务
    • 1 ACID 及其保证手段
    • 2 事务的隔离级别
    • 3 多版本并发控制 MVCC
      • 3.1 MVCC 实现流程
      • 3.2 RR 等级下使用 MVCC 防止幻读
  • 四 设计规范与优化建议
    • 1 索引、数据表设计
    • 3 连接查询优化
    • 4 分库和分表
    • 5 ALTER TABLE
    • 6 SQL 执行时间过长,如何优化
  • 五 锁:服务器层与 InnoDB 存储引擎
    • 1 表锁:S、X、IS、IX
    • 2 行锁:S、X
    • 3 根据位置的行锁划分
  • 六 SQL 查询
    • 1 语法
    • 2 执行顺序
    • 3 常用函数 / 关键字
    • 4 SQL 更新数据的执行流程 & 两阶段提交
  • 七 InnoDB 日志
    • 1 日志对比
    • 2 Redo Log
      • 2.1 解决持久性的问题
      • 2.2 执行流程
      • 2.3 Redo Log 刷盘策略
    • 3 Undo Log
      • 3.1 数据分类
      • 3.2 Undo Log 的两种类型
      • 3.3 Redo Log + Undo Log 生成过程(SQL在执行引擎层的流程)*
    • 4 Bin Log
      • 4.1 记录 Bin Log 的三种模式
      • 4.2 Bin Log 的刷盘策略
      • 4.3 使用 Bin Log 进行主从复制


一 存储引擎

  • MySQL 服务器通过 API 与存储引擎交互,接口屏蔽了不同存储引擎之间的差异,对上层是透明的
  • 不同存储引擎之间不会相互通信,只是简单地响应上层服务器的请求
  • 索引在存储引擎层实现

1 MyISAM 与 InnoDB 的差异

  • MySQL 5.5 之前,MyISAM 引擎是 MySQL 的默认存储引擎,之后的默认引擎是 InnoDB
  • 相比之下,InnoDB 具有 支持事务、支持行级锁、支持外键、支持数据库异常崩溃后的安全恢复(redo log) 的特性

二 索引

  • 索引在存储引擎层实现,而非服务器层
  • 使用索引不一定能提升查询性能,在表比较小时,全表扫描的速度可能比使用索引更快
  • 索引的优点
    • 减少服务器需要扫描的数据量
    • 避免创建排序表和临时表
    • 将随机 I/O 变为顺序 I/O
  • 联合索引中,把选择性最高的属性放在前面;把需要范围查询的属性放在后面

1 主键索引与二级索引、索引覆盖、延迟关联

  • 主键索引
    • 主键索引存放的数据是表中的一条数据,二级索引存放的数据是主键索引
    • 一张数据表有只能有一个主键,并且主键不能为 null,不能重复
    • 不发生索引覆盖时,在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据二级索引查找时,则需要先取出主键的值,再使用主索引进行数据查找
    • 如果没有主键也没有合适的唯一索引,那么 InnoDB 内部会生成一个隐藏的主键作为聚集索引,这个隐藏的主键是一个6个字节,类型为长整型的列,该列的值会随着数据的插入自增
  • 二级索引
    • 联合索引是二级索引,多个属性建立联合索引只会建立一棵B+树
    • 二级索引的内节点,也保存了主键的值,避免相同的二级索引值造成歧义
  • 索引覆盖
    • 除非发生索引覆盖的情况,否则使用二级索引会导致回表
    • 回表:当查到索引对应的主键后,还需要根据主键再到主键索引查询
    • 索引覆盖:需要查询的字段正好是索引的字段,无论主索引或二级索引
      # 一种索引覆盖的特殊情况:假设在 age 属性建立二级索引,id 属性是主键索引
      # 因为二级索引的叶子节点存放了主键索引,所以下面的查询发生索引覆盖
      select id from test_table where age = 20;
      
    • 索引覆盖直接从内存中的索引读取数据,避免了磁盘 I/O
  • 延迟关联
    • 使用索引覆盖查找目标数据行的主键 ID,再和原表根据主键进行关联查询
    • 核心思想是在二级索引中,利用查询条件得到主键,再访问主键索引,而非直接扫描数据
# 场景:当偏移量很大时,如 limit 100000, 10
# 取第100001-100010条记录,会取出100010条记录然后将前100000条记录丢弃,这无疑是一种巨大的性能浪费# 优化前:先访问所有数据,再截取
SELECT * FROM orders LIMIT 10000, 20;# 优化后:先截取,获得目标数据的主键,再访问数据
SELECT * FROM orders AS o1 JOIN (SELECT id FROM orders LIMIT 10000, 20) AS o2  # 内层扫描使用了索引覆盖
ON o1.id = o2.id;

2 聚簇索引与非聚簇索引

  • 聚簇索引指的是,索引和数据存放在一起
    • 对于 InnoDB 引擎的表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据
    • 一个表只能存在一个聚簇索引,因为无法把数据行存放在两个不同的地方
    • 主键索引是聚簇索引,二级索引是非聚簇索引
  • 聚簇索引提高了 I/O 密集型应用的性能,如果数据全部存放在内存中,则访问顺序没那么重要,聚簇索引无法体现优势

在这里插入图片描述

索引类型优点缺点
聚簇索引查询速度快,无需回表需要依赖有序的数据(因为数据结构是B+树,无序插入会导致页分裂);更新代价大(如果对索引列的数据被修改时,那么对应的索引也将会被修改
非聚簇索引更新代价比聚簇索引小(叶子节点存放的是主键值,数据移动时不需要修改二级索引需要依赖有序的数据;可能会回表

3 数据结构

3.1 哈希表

在这里插入图片描述

  • 插入数据时,根据 key 进行哈希运算,得到 bucket 的位置,如果该位置无 value 则插入成功,如果有则发生了哈希冲突,使用拉链法或者红黑树解决(类似 Java 的 HashMap
  • 哈希索引的缺点
    • 只支持全值查询,不能使用部分索引字段,因为根据全部字段才能计算哈希值
    • 只支持等值比较查询,包括 =, IN(), <=>,不支持范围查询,同样也无法用于 ORDER BY 排序
  • 自定义哈希索引
    • 适用场景:需要在长字符串 url 上建立索引
    • 使用方法
      1. 选择合适的哈希函数 MyHashFunc
      2. 新增一个列 url_hash,用该列存储字符串的哈希值,并在该列上建立索引
      3. 设置触发器维护该列,使哈希值随 url 的修改同步更新
      4. 执行查询 SELECT id FROM url_table WHERE url="http://www.mysql.com" AND url_hash=MyHashFunc("http://www.mysql.com"),如果发生哈希冲突,仅需比较相同哈希值的行的 url 是否相同

3.2 B树

  • MongoDB 采用的索引类型,多路平衡查找树
  • B树的中间节点存放的也是数据;叶节点之间没有连接
  • B树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了

B树的阶:树的节点最多的孩子结点(子树)数
根节点至少2个子树(一个关键字),非根节点至少有 ⌈M /2⌉ 个子树(⌈M /2⌉ - 1 个关键字

3.3 B+树

在这里插入图片描述

  • MyISAM 和 InnoDB 均采用的数据结构,是多路平衡查找树
  • B+树的中间节点只存放索引,数据全部都在叶节点上;叶节点之间有连接
  • 相对于B树而言,对范围查找的支持更好
  • 查询效率更加稳定,因为每次查询必定会访问到叶节点
  • 叶子节点构成一个有序链表,而且叶子节点本身按照关键字的大小从小到大顺序链接
  • B+树的阶数并非越大越好
    • 阶数很大时,一个节点的大小会超过一个页的大小,读取这样一个节点会导致多次I/O操作,造成性能下降
    • 要尽量让每个节点的大小等于一个页的大小

3.4 跳表

  • 在 Redis 中的 Zset 使用到的数据结构,在 Java 容器类中也有实现
  • 基于链表,能够实现近似二分查找
  • 为什么在 MySQL 中选择 B+ 树而非跳表作为索引的数据结构
    • 如果是查询磁盘文件,B+ 树会比跳表的性能好很多,因为磁盘查询性能比内存差,所以要尽量减少查询的次数
    • B+ 树每个节点按页存放数据,每次查询可以查询一批数据到内存中;而且 B+ 树的层数低,可以减少访问磁盘的次数

3.5 为什么不使用红黑树

  • MySQL 用 I/O 次数衡量查询效率,磁盘查找存取的次数往往由树的高度所决定
    1. 红黑树是内排序使用的数据结构,B+ 树常用于外排序
    2. 红黑树的高度通常更高(因为是二叉树),需要的磁盘 I/O 次数更多,效率低;B+树可以有多个子节点,树的高度通常为1~3层,效率高
    3. B+树的一个节点对应一个页,如果使用红黑树的话每个节点(页)存放一条数据,造成很大的浪费

3.6 为什么不使用B树**

  1. B树执行范围查询时只能按照类似于中序遍历的方式进行,效率不如B+树在叶节点的顺序访问
  2. B树/B+树的特点就是每层节点数目非常多,层数很少,就是为了减少 I/O 次数,但是B树的每个节点都存放数据,这无疑增大了节点大小,增加了磁盘 I/O 次数(磁盘I/O一次读出的数据量大小是固定的,单个数据变大,每次读出的就少,导致 I/O次数增多),而B+树除了叶子节点均不存储数据,节点小,磁盘 I/O 次数就少

4 索引下推 ICP **

参考链接

  • 索引下推的目的是减少回表次数,即减少 I/O 操作
  • 对 InnoDB 存储引擎来说,索引下推 只适用于二级索引,因为对于 InnoDB 的主索引(聚簇索引)来说,完整的行记录已经加载到缓存区了,索引下推也就失去意义
  • 在使用 ICP 的情况下,如果存在某些被索引的列的判断条件时(即使这些列无法使用索引),服务器层将这一部分判断条件传递给存储引擎,然后由存储引擎判断索引是否符合服务器传递的条件,只有当 索引符合条件时才会将数据检索出来返回给服务器

举例

  • 假设在 (name, city) 建立了联合索引,查询条件为 name = 'LiSi' and city like '%z%' and age > 25

  • 不使用索引下推 回表4次

    1. 存储引擎根据 (name, city) 联合索引,找到 name = 'LiSi' 的记录,共4条记录
    2. 因为 city 的模糊查询违反了最佳左前缀原则,所以只能根据 name 过滤的这4条记录中的 id 值,逐一进行回表扫描,去聚簇索引中取出完整的行记录,并把这些记录返回给 Server 层
    3. Server 层接收到这些记录,并按条件 name="LiSi" and city like "%Z%" and age > 25 进行过滤,最终留下 ("LiSi", "ZhengZhou", 30) 这条记录
      在这里插入图片描述
  • 使用索引下推 回表2次

    1. 存储引擎根据 (name, city) 联合索引,找到 name='LiSi' 的记录,共4条
    2. 由于联合索引中包含 city 列,存储引擎直接在联合索引中按 city like "%Z%" 进行过滤(即使它已经无法使用索引),过滤后剩下2条记录
    3. 根据过滤后的记录的 id 值,逐一进行回表扫描,去聚簇索引中取出完整的行记录,并把这些记录返回给 Server 层;
    4. Server 层根据 WHERE 语句的其它条件 age > 25,再次对行记录进行筛选,最终只留下 ("LiSi", "ZhengZhou", 30) 这条记录

在这里插入图片描述

5 索引失效(索引不命中)的情况

  1. 违背最佳左前缀法则
    • 对于列 (age, class, name) 建立了联合索引,如果查询条件是 class=5 and name='zy' 等违背左前缀,则无法使用该索引
    • 优化器可以决定 where 后面条件的顺序,所以 class=5 and name='zy' and age=10 可以使用索引
  2. 计算、函数、类型转换导致索引失效(索引列是表达式的一部分)
    • WHERE name=123 不能使用索引
    • WHERE name='123' 可以使用索引
  3. IS NULL 可以使用索引,IS NOT NULL 不能使用索引
  4. 范围条件右边(指的是定义联合索引的右,而非 where 条件中的右)的列索引失效
    对于列 (age, class, name) 建立了联合索引,WHERE student.age=30 AND student.name = 'abc' AND student.classId>20 会导致 name 索引失效
  5. 不等于 != 导致索引失效
  6. OR 前后存在非索引的列,索引失效:因为对于非索引的条件需要全表扫描,该情况下直接放弃使用索引而执行全表扫描
  7. 对于模糊查询,通配符(%)在搜寻词首出现,一般会导致不使用索引,"%ABC"不能使用,但"A%BC"可以使用

6 前缀索引和索引选择性

  • 为类型 TEXT/BLOB/长的VARCHAR 添加索引时,不允许索引列的完整长度,需要使用自定义哈希索引前缀索引
  • 前缀索引需要选择合适的前缀长度,使前缀选择性接近于完整列的选择性
    # 例如对于 TEXT 类型的字段 city
    # 1.计算完整的列的选择性
    SELECT COUNT(DISTINCT city) / COUNT(*) FROM demo;# 2.计算前5个字符的选择性
    SELECT COUNT(DISTINCT LEFT(city, 5)) / COUNT(*) FROM demo;# 添加前缀索引
    ALTER TABLE demo ADD KEY (city(5));
    
  • 显然前缀索引无法用于索引覆盖,因为索引中不包含完整的列

7 索引用于 ORDER BY

  • 索引的列顺序和 ORDER BY 子句的顺序完全一致,且排序方向相同时,才能使用索引对结果排序
  • 如果是关联查询,只有 ORDER BY 子句引用的字段完全属于第一个表的时候,才能用索引排序
  • ORDER BY 子句需要满足最佳左前缀的要求,除非前导列是常量
# 例如对于联合索引(rental_date, inventory_id, customer_id)# 正确示例1
... WHERE rental_date = '2022-12-24'  # 索引的第一列被指定为常数,该索引可以用于 ORDER BY 排序
ORDER BY inventory_id, customer_id
# 正确示例2
... WHERE rental_date > '2022-12-24'
ORDER BY rental_date, inventory_id# 错误示例1:排序方向不同
... WHERE rental_date = '2022-12-24'
ORDER BY inventory_id ASC, customer_id DESC
# 错误示例2:字段不在索引中
... WHERE rental_date = '2022-12-24'
ORDER BY inventory_id
# 错误示例3:违反最佳左前缀
... WHERE rental_date = '2022-12-24'
ORDER BY customer_id
# 错误示例4:第一列是范围条件,违反最佳左前缀
... WHERE rental_date > '2022-12-24'
ORDER BY inventory_id
# 错误示例5:多个等于条件,对排序而言是范围查询
... WHERE rental_date = '2022-12-24'
AND inventory_id IN(1, 2)
ORDER BY customer_id
  • 前缀索引无法用于 ORDER BY / GROUP BY

三 MySQL 事务

1 ACID 及其保证手段

  • 原子性:事务是最小的执行单位,不允许分割(事务内的一系列操作,要么全都做,要么全不做)
  • 一致性:事务执行前后,数据库的一致性保持不变
  • 隔离性:并发进行的事务之间互不影响
  • 持久性:事务提交后,对数据库中数据的修改是永久的
  • InnoDB 引擎如何保证 ACID
    • 使用 redo log 保证事务的持久性;
    • 使用 undo log 来保证事务的原子性;
    • 锁机制、MVCC 等手段来保证事务的隔离性( 默认隔离级别是可重复读);
    • 保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障

2 事务的隔离级别

隔离级别&出现的异常脏读(读到未提交数据)不可重复读(两次读取同一数据结果不同)幻读(两次查询结果数目不同)
读未提交
读提交×
可重复读××
串行化×××
  • 可重复读的隔离级别下,使用 MVCC + Next-key Lock 也可以避免幻读

3 多版本并发控制 MVCC

  • MVCC 处理的是 读数据 的问题,避免读加锁,写数据必须依靠加锁
  • 普通的 SELECT 语句在 读提交可重复读 隔离级别下会使用到 MVCC,主要针对的也是这两种隔离级别(因为这两种隔离级别要求读到的是 已经提交了的 事务修改过的记录)
  • 另外两种隔离级别不适用 MVCC
    • 可串行化对读取的每一行记录加锁
    • 读未提交总是读取最新的数据,无需同步
  • MVCC 的读指的是 快照读(读取的是快照数据) , 而非当前读(当前读读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
    • 普通的 SELECT 操作是快照读
    • SELECT ... FOR UPDATESELECT ... IN SHARE MODE 是当前读

3.1 MVCC 实现流程

  • 其实现主要依赖于 记录的隐藏字段 + UndoLog + ReadView
  • 读提交 和 可重复读 关于 ReadView 的区别是
读提交可重复读
快照读每次查询时创建 ReadView事务开始时创建 ReadView
当前读加锁加锁
  • ReadView 包含主要结构
creator_trx_id创建这个 ReadView 的事务 ID(只读事务为0,修改记录的事务会被分配 ID)
trx_ids生成 ReadView 时,活跃的读写事务的事务 ID 列表
up_limit_id活跃的事务中最小的事务 ID
low_limit_id生成 ReadView 时,系统中应该分配给下一个事务的 ID 值

注意:low_limit_id 并不是 trx_ids 中的最大值,事务id是递增分配的。比如,现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成 ReadView 时,trx_ids就包括1和2,up_limit_id的值就是1,low_limit_id的值就是4

  • ReadView 规则:

    1. 如果被访问版本的 trx_id 属性值与 ReadView 中的 creator_trx_id 值相同,说明该记录的修改是由当前事务创建的,允许访问
    2. 如果被访问版本的 trx_id 属性值小于 ReadView 中的 up_limit_id 值,表明生成该版本的事务在当前事务生成 ReadView 之前已经提交,允许访问
    3. 如果被访问版本的 trx_id 属性值大于或等于 ReadView 中的 low_limit_id 值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,不允许访问
    4. 如果被访问版本的 trx_id 属性值在 ReadView 的 up_limit_id 和 low_limit_id 之间,那就需要判断一下 trx_id 属性值是不是在 trx_ids 列表中:如果在,说明生成该版本的事务,在当前事务开启时尚未提交,不允许访问;反之说明生成该版本的事务,在当前事务开启时已经提交,允许访问
  • MVCC 整体流程:

    1. 首先获取事务自己的版本号,也就是事务 ID;
    2. 获取 ReadView;
    3. 查询得到的数据,然后与 ReadView 中的事务版本号进行比较;
    4. 如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照;
    5. 最后返回符合规则的数据,如果无符合规则的数据则返回空
      在这里插入图片描述

3.2 RR 等级下使用 MVCC 防止幻读

  • 幻读:某个事务读取某个范围内的记录时,另外一个事务在该范围内插入了新的记录,导致当前事务再次读取该范围的记录时产生幻行
  • 针对两种不同的查询操作
    • 快照读:可重复读只会在事务开启后的第一次查询生成 Read View ,并使用至事务提交。所以在生成 ReadView 之后其它事务所做的更新、插入记录版本对当前事务并不可见,防止快照读下的幻读
    • 当前读:InnoDB 使用 Next-key Lock 来防止这种情况。当执行当前读时,会锁定读取到的记录的同时,锁定它们的间隙,防止其它事务在查询范围内插入数据

四 设计规范与优化建议

1 索引、数据表设计

  1. 让主键具有 AUTO_INCREMENT ,让存储引擎自己生成主键,而不是手动插入,让插入的记录的主键值依次递增,避免页面分裂带来的性能损耗
    在这里插入图片描述
  • 页分裂的负面影响
    • 插入之前需要从磁盘中找到目标页,并读取到内存中,产生大量随机 I/O
    • 导致大量的数据移动,一次插入至少需要修改三个页
    • 页变得稀疏,产生碎片
  1. 字段的数据类型定义准确
  2. 设计数据表时适当遵循范式规则 1/2/3NF
    • 1NF:数据表的每个字段不可拆分
    • 2NF:数据表中非主属性完全依赖于主属性
    • 3NF:数据表中的非主属性不传递依赖于主属性
  3. 对数据表进行适当的行、列拆分(分表)
  4. 数据库和表的字符集统一使用 utf8mb4
  5. 表属性尽量设置为 NOT NULL,因为 IS NOT NULL 不能使用索引,且 NULL 会带来额外的问题
  6. 联合索引中,把选择性最高的属性放在前面;把需要范围查询的属性放在后面

3 连接查询优化

  • 驱动表:无法避免全表扫描的表称为驱动表
    • 在决定哪个表做驱动表的时候,两个表按照各自的条件过滤,过滤后计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表
    • 为了提高连接查询效率,需要将小表作为驱动表,大表作为被驱动表,减少外层循环的次数
    • LEFT JOIN 保证左表的每个记录都会出现,即左表为驱动表,所以右表是关键,一定需要建立索引(右外连接反之)
    • INNER JOIN 会自动决定驱动表、被驱动表
    • 对于连接属性,如果两表只有一个具有索引,则它作为被驱动表
    • 如果两个表都在该属性有索引,小表作为驱动表,大表作为被驱动表
  • 需要JOIN 的字段,数据类型保持绝对一致,避免类型转换导致索引失效

4 分库和分表

切分定义应用场景
分库数据库中的数据分散到不同的数据库上应用的并发量太大;数据库中的数据占用的空间越来越大,备份时间越来越长
分表对单表的数据进行拆分,可以是垂直拆分,也可以是水平拆分单表的数据达到千万级别以上,数据库读写速度比较缓慢
  • 分库带来的问题
    • join 操作 : 同一个数据库中的表分布在了不同的数据库中,导致无法使用 join 操作,需要手动进行数据的封装,比如在一个数据库中查询到一个数据之后,再根据这个数据去另外一个数据库中找对应的数据
    • 事务问题 :同一个数据库中的表分布在了不同的数据库中,如果单个操作涉及到多个数据库,那么数据库自带的事务就无法满足要求
    • 分布式 id :分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了,需要为系统引入分布式 id

5 ALTER TABLE

  • MySQL 执行的大部分修改表结构的操作是用新结构创建一个空表,将旧表数据插入新表,最后删除旧表
  • 大部分的 ALTER TABLE 操作会导致 MySQL 服务中断,一般解决方案:
    • 在不提供服务的机器上执行 ALTER TABLE,再和提供服务的主机进行切换
    • 影子拷贝:用新结构创建一个空表(和原表无关),通过原子的重命名操作切换两张表
  • ALTER TABLE 修改列的三种操作
    • ALTER COLUMN
      • 改变、删除列的默认值
      • 直接修改 .frm 文件而不涉及表数据,所以操作很快
    • CHANGE COLUMN
      • 重命名列和修改列的数据类型
    • MODIFY COLUMN
      • 修改列数据类型,改变、删除列的默认值
      • 这个操作会有数据的读取和插入操作,拷贝整张表到一张新表

6 SQL 执行时间过长,如何优化

  1. explain 分析 SQL 语句,查看执行计划,优化 SQL
  2. 优化索引结构,适当添加索引
  3. 对数量大的表,可以考虑进行分表
  4. 数据库主从分离,读写分离
  5. 查看执行日志,分析是否有其他方面的问题

五 锁:服务器层与 InnoDB 存储引擎

该部分的参考

  • 服务器层实现了最基本的表锁,该表锁也实现了读锁、写锁的划分
    • 读锁之间不阻塞,其它情况存在阻塞
  • 存储引擎层管理自己的锁策略,设置自己的锁粒度,服务器层是不可见的
    • InnoDB 采用两阶段锁协议:事务执行过程中,随时可以执行锁定,只有执行 COMMITROLLBACK(事务提交或回滚时),同时释放所有的锁

1 表锁:S、X、IS、IX

  • 表锁是 MySQL 中锁定 粒度最大 的一种锁,是最基本的锁策略,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁;锁定粒度最大,触发锁冲突的概率最高,并发度最低
  • 意向锁:
    • 当事务要在记录上加上行锁时,要首先在表上加上意向锁使得判断表中是否有记录正在加锁更简单,无需遍历整张表的数据
    • 意向锁不与行级锁发生冲突,意向锁之间不发生冲突
  • 表级锁的兼容矩阵
    在这里插入图片描述

2 行锁:S、X

  • 行锁在存储引擎层实现,以下结论均基于 InnoDB
  • 行级锁是 MySQL 中锁定 粒度最小 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突;加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁
  • 行锁是 加在索引 上的,如果查询语句不使用索引(索引失效/索引不命中)的话,那么它就会升级到表锁
  • 行锁根据行为划分为
    • 共享锁(S):加了锁的记录,所有事务都能去读取但不能修改,同时阻止其他事务获得相同数据集的排他锁
    • 排他锁(X):允许已经获得排他锁的事务去更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁
  • 执行的命令与加锁的情况
    • 修改语句加排他锁
    • 快照读不加锁
    • 当前读加锁:select ... for update 加排他锁;select ... in share mode 加共享锁

3 根据位置的行锁划分

根据行锁的位置不同进行划分,每种情况又会分为共享锁(读锁)/排他锁(写锁)
在这里插入图片描述

  1. Next-Key Lock

    • 本质上是 Gap Lock + Record Lock,左开右闭
    • MySQL 默认隔离级别是RR,在这种级别下,如果使用 select ... in share mode 或者 select ... for update 语句(即当前读),那么 InnoDB 会使用 Next-Key Lock,防止幻读,在上面已经讨论过
  2. Gap Lock

    • 使用范围条件而不是相等条件去检索,并请求锁时,InnoDB就会给符合条件的记录的索引项加上锁;而对于键值在条件范围内但并不存在的记录,就叫做间隙,InnoDB此时也会对间隙加锁
    • 在 RU 和 RC 两种隔离级别下,即使你使用 select in share mode 或 select for update,也无法防止幻读。因为这两种隔离级别下只会有行锁,而不会有间隙锁;而如果是 RR 隔离级别的话,就会在间隙上加上间隙锁
  3. Record Lock

    • 最简单的行锁
  4. Insert Intention Lock

    • 插入意图锁是一种间隙锁,在行执行 INSERT 之前的插入操作设置。如果多个事务 INSERT 到同一个索引间隙之间,但没有在同一位置上插入,则不会产生任何的冲突
    • 假设有值为4和7的索引记录,现在有两事务分别尝试插入值为 5 和 6 的记录,在获得插入行的排他锁之前,都使用插入意向锁锁住 4 和 7 之间的间隙,但两者之间并不会相互阻塞,因为这两行并不冲突

六 SQL 查询

1 语法

  • select 的属性(不包括函数使用的)必须在 group by 后面出现
  • where 子句不能跟聚合函数(min/max/sum/count/avg),但可以跟 year/month/date/timestampdiff 等函数
select [distinct]
from
join
on
where
group by
having
union
order by
limit

2 执行顺序

  • 步骤6执行的是聚合函数,特征是要结合多个行才能得到值,非聚合函数 year()substring()if() 等执行时机更早
  • having 字句出现的属性必须在 select 字句里出现,在 select 字句出现的属性必须在 group by 字句里出现
1. from  # 首先进入from字句
2. on  # 根据条件选择需要连接的行
3. join  # 执行连接
4. where  # 对连接结果过滤
5. group by  # 分组(在分组之前执行了 select 后面的 if、substring_index... 等非运算操作)
6. avg(), sum(), count()...  # **聚合**函数
7. having  # 对各个分组分别进行过滤
8. select  # 选择结果
9. distinct  # 结果去重
10. union  # 将当前结果并上其它结果
11. order by [asc, desc] # 排序
12. limit  # 选择指定行作为结果

3 常用函数 / 关键字

  1. day(date) / month(date) / year(date):获取日/月/年
# 获取2021年8月里,每天的练习量
select day(date) day, count(*) question_cnt
from question_practice_detail
where year(date) = 2021 and month(date) = 8
group by day
  1. if(cond, true_val, false_val):条件正确时选择前者,否则后者
# 分别查看 25岁以下和25岁及以上 两个年龄段的用户数量
# 这里先执行了 if 再执行了 group by
select if(age>=25, '25岁及以上', '25岁以下') as age_cut, count(device_id) as number
from user_profile
group by age_cut
# 复旦大学的每个用户在8月份练习的总题目数和回答正确的题目数情况
# 主要问题是统计 result = 'right' 的行数,count(result = 'right') 是错误的写法,应该 sum(if(result = 'right', 1, 0))
# count 只能统计非空(可选择不重复)的字段的个数
select t1.device_id, t1.university, count(t2.question_id) question_cnt, sum(if(t2.result = 'right', 1, 0)) right_question_cnt
from user_profile t1
left join (select device_id, result, question_idfrom question_practice_detailwhere month(date) = 8) t2
on t1.device_id = t2.device_id
where university = '复旦大学'
group by device_id
  1. case...when...then...else...end
# 将用户划分为20岁以下,20-24岁,25岁及以上三个年龄段
selectdevice_id,gender,casewhen age >= 25 then '25岁及以上'when age >= 20 then '20-24岁'when age < 20 then '20岁以下'else '其他'end age_cut
from user_profile
# bonus类型btype为1其奖金为薪水salary的10%,btype为2其奖金为薪水的20%,其他类型均为薪水的30%
# 请你给出emp_no、first_name、last_name、奖金类型btype、对应的当前薪水情况salary以及奖金金额bonus
select eb.emp_no, e.first_name, e.last_name, eb.btype, s.salary, round(s.salary * (case eb.btype when 1 then 0.1when 2 then 0.2 else 0.3end), 1) bonus
from employees as e join salaries as s on e.emp_no = s.emp_no join emp_bonus as eb on eb.emp_no = s.emp_no and eb.recevied <= s.to_date and eb.recevied >= s.from_date
order by emp_no asc 
  1. union [all]:不加 all 会自动去重

  2. substring_index(field, separator, index):如果 index > 0 则取前缀,否则取后缀

  3. upper(str):转为大写

  4. concat(str1, str2):拼接字符串

  5. timestampdiff(unit,begin,end):获取时间差,需要指定单位

4 SQL 更新数据的执行流程 & 两阶段提交

在这里插入图片描述
(上图存疑:先将数据加载到内存,再将修改记录到 Redo Log)

  • 两阶段提交是为了保证 Bin Log 和 Redo Log 的一致性
    1. 一阶段:将 Redo Log 写入到磁盘,并将其状态设置为 Prepare
    2. 二阶段:将 Bin Log 写入磁盘,并将 Redo Log 状态设置为 Commit
  • 数据库崩溃时,根据 Bin Log 和 Redo Log 的一致性判断恢复行为
    1. Bin Log 有记录,Redo Log 状态 Commit:事务正常完成,不需要恢复
    2. Bin Log 有记录,Redo Log 状态 Prepare:如果 Bin Log 事务完整则提交事务,否则回滚事务
    3. Bin Log 无记录,Redo Log 状态 Prepare:回滚事务

七 InnoDB 日志

1 日志对比

类型执行任务作用所属层
Redo Log记录物理级别的页的修改操作(页号, 偏移量, 数据)保证事务的持久性存储引擎层,事务在内存中修改后写入 Buffer,在事务发起提交后写入磁盘
Undo Log记录逻辑操作日志(比如执行 INSERT 时,在 Undo Log 中记录与之相反的 DELETE 操作,即每个修改操作的逆操作)保证事务的原子性MVCC存储引擎层,事务在内存中修改前写入
Bin Log记录逻辑操作日志数据恢复、数据复制数据库层,事务发起提交后根据策略写入
  • Redo Log 针对的是已提交数据的恢复操作,即提交后未刷盘的情况下,操作系统 / 数据库 发生停机时,用到 Redo Log
  • Undo Log 针对的是未提交数据的恢复操作,在事务未提交的情况下需要 rollback,用到 Undo Log
  • Undo Log 不是 Redo Log的逆过程,Undo Log 无需持久化,而 Redo Log 需要

2 Redo Log

2.1 解决持久性的问题

  • 事务执行并且 COMMIT 后(如果没有 COMMIT 则无需进行任何恢复,原子性),数据的修改只在内存中完成,并未同步到外存。此时发生宕机,持久性的保持问题
  • Redo Log 的解决方法是,每次仅仅记录如何修改而非具体的数据,所以占用空间很小
  • 数据库按一定频率将已提交的内存的数据同步到外存中。在此之前采用 WAL(Write-Ahead Logging)机制,先将日志写入到外存,再将数据写入到外存,只有日志写入成功,事务才算提交成功

2.2 执行流程

在这里插入图片描述

2.3 Redo Log 刷盘策略

此处讨论的是 Redo Log 的持久化策略,而非数据只有 Redo Log 持久化过的数据修改,这些修改后的数据才能写入外存

innodb_flush_log_at_trx_commit行为
0事务提交时不写入 page cache,也不刷盘,交给后台线程完成
1(默认)事务提交时立刻写入 page cache 并刷盘
2事务提交时立即写入 page cache,刷盘交给后台线程完成

下图中的编号代表事务提交时,不同策略下 Redo Log 的位置
在这里插入图片描述

3 Undo Log

3.1 数据分类

  • 未提交的回滚数据 (uncommitted undo information)
  • 已经提交但未过期的回滚数据 (committed undo information),特指未过期的 Update Undo Log,用于 MVCC 的实现
  • 事务已经提交并过期的数据 (expired undo information)

3.2 Undo Log 的两种类型

  • Insert Undo Log

    • INSERT 过程中产生的 Undo Log,因为 INSERT 操作的记录只对事务本身可见,其它事务不可见,所以可以在事务提交后直接删除
  • Update Undo Log

    • DELETEUPDATE 过程中产生的 Undo Log,用于实现 MVCC,所以不能在事务提交后就删除,而是放入 Undo Log 链表,等待过期后再删除

3.3 Redo Log + Undo Log 生成过程(SQL在执行引擎层的流程)*

在这里插入图片描述

  1. 事务开始,发起更新数据的请求
  2. 对于内存不存在的数据,需要先从外存加载到内存
  3. INSERT / UPDATE / DELETE 数据前,首先更新 Undo Log
  4. 在内存 Buffer Pool 中更新数据
  5. 更新 Redo Log Buffer
  6. (事务操作执行完毕,准备提交,进入两阶段提交)一阶段:将 Redo Log 刷新到外存 ,状态设置为 Prepare
  7. 二阶段:将 Bin Log 刷新到外存,将 Redo Log 状态设置为 Commit,此时才算提交成功
  8. 内存中修改后的数据按一定策略同步到外存

4 Bin Log

4.1 记录 Bin Log 的三种模式

在这里插入图片描述

模式行为优点缺点
Statement Level(默认)记录每一条修改数据的SQL语句日志占用空间小,节约磁盘I/O对一些特殊功能的复制效果不是很好,比如函数、存储过程的复制
Row Level记录的方式是行:如果批量修改数据,记录的不是批量修改的SQL语句,而是每条记录被更改的SQL语句记录每一行数据被修改的细节,不会出现特殊操作无法被复制的情况日志占用空间大
Mixed两种模式的结合,根据具体的SQL语句决定记录的日志形式--
  • 如果使用 MySQL 的特殊功能相对少(存储过程、触发器、函数),选择 Statement Level
  • 如果使用 MySQL 的特殊功能较多,可以选择 Mixed
  • 如果使用 MySQL 的特殊功能较多,又希望数据最大化一致,选择 Row level

4.2 Bin Log 的刷盘策略

sync_binlog策略
0由系统决定写入时机
1(默认)每次事务提交时将写入磁盘
N每N次事务提交时写入磁盘,牺牲一致性换取更高的性能

4.3 使用 Bin Log 进行主从复制

在这里插入图片描述

  • 整个过程需要依赖三个线程:主库的二进制日志转储线程,从库的I/O线程和SQL线程

    1. 主库的数据更新事件(update、insert、delete)写入 Bin Log
    2. 从库发起连接,连接到主库
    3. 主库创建一个 BinLog Dump Thread,把 Bin Log 的内容发送到从库
    4. 从库启动之后,创建一个 I/O Thread,读取主库传过来的 Bin Log 内容并写入到 Relay Log
    5. 从库创建一个 SQL Thread,从 Relay Log 里面读取内容,并执行其中包含的事务,使从库数据和主库保持一致
  • 数据库主库和从库不一致,常见优化方案

    • 业务可以接受,系统不优化
    • 强制读主:使用高可用主库,把读写任务都交给主库
    • 选择性读主:在 cache 里记录哪些记录发生过写请求,用来路由读主还是读从

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307135.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

. NET5正式版本月来袭,为什么说gRPC大有可为?

当前企业正在慢慢改用微服务架构来构建面向未来的应用程序&#xff0c;微服务使企业能够有效管理基础架构&#xff0c;轻松部署更新或改进&#xff0c;并帮助IT团队的创新和学习。它还可以帮助企业能够设计出可以轻松按需扩展的应用程序&#xff0c;此外&#xff0c;随着企业转…

后端学习 - 操作系统

文章目录一 基本概念1 操作系统的特征2 操作系统的位置3 计算机的硬件组成4 中断与异常5 系统调用二 进程管理1 进程控制块 PCB&#xff08;Process Control Block&#xff09;2 进程的状态与转换3 进程间的通信4 线程5 调度算法6 死锁7 PV 操作三 内存管理1 内存的非连续分配2…

西门子触摸屏脚本程序_通过驿唐PLC501远程下载Smart Line触摸屏

通过驿唐PLC-501远程下载Smart Line触摸屏一、触摸屏设置将PLC-501和Smart 700 IE V3通过网线连到一起。触摸屏上电后&#xff0c;点击Control Panel进入控制面板界面。进入控制面板后&#xff0c;点击Ethernet设置IP地址&#xff0c;与PLC-501联网宝在同一个网段。联网宝的IP地…

.NET for Apache Spark 1.0 版本发布

.NET for Apache Spark 1.0 现已发布&#xff0c;这是一个用于 Spark 大数据的 .NET 框架&#xff0c;可以让 .NET 开发者轻松地使用 Apache Spark。该软件包由微软和 .NET Foundation 牵头&#xff0c;经过大约两年的开发。在 2019 年的 Spark AI 峰会上&#xff0c;微软曾宣…

fillrect不填充被覆盖的区域 mfc_quot;条带覆盖quot;猜想的中二证明:quot;球面条线覆盖或点覆盖quot;积分π...

注销&#xff1a;“黎曼猜想”复平面质数单向“虚”圆柱螺旋&#xff1a;几何法证明&#xff0c;技术应用​zhuanlan.zhihu.com假设有这样一部针点打印机从球心对球面打印&#xff0c;外部有一台蓝牙打印&#xff0c;球面打印蓝牙条带打印同步&#xff0c;球面被覆盖&#xff0…

让你变厉害的7个底层思维

职场&认知洞察 丨 作者 / findyi这是findyi公众号分享的第89篇原创文章如果把你的思维比做操作系统&#xff0c;那思维模型就是一个个App。这些App会给你提供新的视角&#xff0c;快速帮你决策&#xff0c;提升你的工作效率。顶级的思维模型能提高你成功的可能性&#xff0…

后端学习 - 计算机网络

文章目录一 基本概念1 计算机网络体系结构2 时延二 应用层&#xff1a;HTTP1 请求和响应报文、常见 header2 URL & RESTful API3 HTTP 协议通信过程4 HTTP 方法5 HTTP 状态码6 短连接、长连接与流水线7 Cookie8 Session三 应用层&#xff1a;HTTPS1 加密方式2 证书认证四 应…

跟我一起学Redis之Redis配置文件啃了一遍之后,从尴尬变得有底气了(总结了一张思维图)...

前言秋高气爽的一天&#xff0c;那时候年轻帅气的我正在参照着搜索引擎写代码&#xff0c;迷之微笑般的敲着键盘(又从搜索引擎上找到代码案例啦)&#xff0c;突然领导在小隔间里传来了呼唤声&#xff0c;然后有了以下场景&#xff1a;领导&#xff1a;小Z&#xff0c;你过来一下…

1093芯片做正弦波逆变器_长途自驾游“缺电”如何选购正确车载逆变器,避开商家套路...

让车友三分钟就能看明白如何选择车用逆变器&#xff0c;节约车友时间。长途自驾游充电是一个难题&#xff0c;手机充电还好办&#xff0c;其他笔记本、电饭煲、车载冰箱或者无人机电池等充电就会用到220V电源&#xff0c;那就必须用到逆变器(逆变器就是将12V或24V直流电&#x…

Spring 相关问题

文章目录Spring1 Spring 框架中用到的设计模式2 Spring 事务、隔离级别3 单例 Bean 是线程安全的吗Spring IOC1 Spring 容器&#xff1a;BeanFactory & ApplicationContext2 依赖注入的两种方式3 Bean 的生命周期4 依赖注入的四个注解5 如何解决循环依赖Spring AOP1 基本概…

ndr4108贴片晶振是多少频率_关于山羊挺身你知道多少?这些干货速来了解一下...

就是因为有些人愿意吃苦&#xff0c;有些人缺乏决心毅力&#xff0c;才会有成功和失败之分。身材不是一天不运动或一天乱吃所造成&#xff0c;瘦身健身的效果也不是一天的努力可以看出来的。如同学业、事业&#xff0c;成功都不是一条直线&#xff0c;而是一条上上下下的崎岖路…

BeetleX之Websocket服务使用

BeetleX.FastHttpApi不仅是一个Webapi服务组件&#xff0c;它同时也是一个Websocket服务组件。由于BeetleX.FastHttpApi的实现是直接支持Websocket Upgrade操作&#xff0c;所以当启动服务后是HTTP还是Websocket完全取决于请求方&#xff1b;其原理和aspcore一样&#xff0c;同…

typeorm_Nestjs 热更新 + typeorm 配置

Nestjs 开发环境热更新的方案Nestjs 的热更新是基于 Webpack HMR(Hot-Module Replacement) 方案警告请注意&#xff0c;webpack不会自动将您的资产(例如graphql文件)复制到dist文件夹。同样&#xff0c;webpack与glob静态路径(例如TypeOrmModule中的实体属性)不兼容。1 使用 CL…

缓存一致性和跨服务器查询的数据异构解决方案canal

当项目的请求量上去了之后&#xff0c;通常有两种做法来应对高并发&#xff0c;第一是尽最大可能的使用cache来对抗&#xff0c;第二是尽最大可能的分库分表对抗。。。说起来容易&#xff0c;做起来并不那么乐观&#xff0c;这一篇就来浅析下。一&#xff1a;如何保证缓存一致性…

synchronized 实现原理

参考链接 文章目录一 基本使用1 三个作用2 三种用法二 同步原理1 监视器 Monitor2 synchronized 用于同步代码块3 synchronized 用于同步方法3 Mark Word4 对象头的 Mark Word 和线程的 Lock Record三 锁的优化1 自旋锁2 锁消除3 锁粗化4 偏向锁5 轻量级锁、重量级锁以及三种锁…

开头th_是什么文件_Python文件读写最详细的讲解

本文来自公众号&#xff1a;可乐的数据分析之路今天这篇文章来详细讲解一下Python中的文件读写。1、文件读写的流程1)类比windows中手动操作txt文档&#xff0c;说明python中如何操作txt文件&#xff1f;① windows中手动操作txt文件的步骤找到word文档打开word文档查看(或操作…

把 Console 部署成 Windows 服务,四种方式总有一款适合你!

一&#xff1a;背景 1. 讲故事上周有一个项目交付&#xff0c;因为是医院级项目需要在客户的局域网独立部署。程序&#xff1a;netcore 2.0&#xff0c;操作系统&#xff1a;windows server 2012&#xff0c;坑爹的事情就来了, netcore sdk 一直装不上&#xff0c;网上找了资料…

AQS ReentrantLock 实现原理

参考链接 文章目录1 AQS (AbstractQuenedSynchronizer)2 Lock 接口与显式条件3 转账 Demo&#xff1a;解决死锁的两种方案4 ReentrantLock 非公平锁加锁流程5 ReentrantLock 和 synchronized 的异同6 ReentrantReadWriteLock1 AQS (AbstractQuenedSynchronizer) 基于 AQS 的同步…

TensorFlow 2学习和工业CV领域应用 心得分享

我是一名来自苏州的机器视觉开发者&#xff0c;从事传统的机器视觉算法开发有11年了&#xff0c;从2018年开始&#xff0c;因为一些复杂微弱的瑕疵检测项目遇到的传统算法瓶颈&#xff0c;开始接触到了深度学习&#xff0c;并选择了使用TensorFlow&#xff0c;期间也是不断摸索…

历史版本_新版本爆料第弹丨英雄练习新去处,荣耀历史秀出来!

《万物有灵》新版本即将到来新版本来临之前妲己宝宝给自己定下了2个小目标&#xff01;via.小五怎么不开心目标一扩展自己小得可怜的英雄勺成为一名拥有英雄海的补位大神目标二通过自己的实力获得N1个响当当的荣耀称号很多召唤师会有疑问&#xff1a;凭妲己宝宝的实力&#xff…