MySQL 实战45讲--笔记

文章目录

  • MySQL 实战45讲-->笔记
    • 开篇词
    • 基础篇(8讲)
      • 01 | 基础架构:一条SQL查询语句是如何执行的?
        • 1.1 SQL 语句在 MySQL 的各个功能模块中的执行过程。
          • 依次看下每个组件的作用。
            • (1) 连接器
            • (2) 查询缓存
            • (3) 分析器
            • (4) 优化器
            • (5) 执行器
            • 小结
      • 02 | 日志系统:一条SQL更新语句是如何执行的?
        • 重要的日志模块:redo log
        • 重要的日志模块:binlog
      • 03 | 事务隔离:为什么你改了我还看不见?
        • 隔离性与隔离级别
        • 事务隔离的实现
        • 事务的启动方式
      • 04 | 深入浅出索引(上)
        • 索引的常见模型
        • InnoDB 的索引模型
      • 05 | 深入浅出索引(下)
        • 最左前缀原则
        • 索引下推
      • 06 | 全局锁和表锁 :给表加个字段怎么有这么多阻碍?
      • 07 | 行锁功过:怎么减少行锁对性能的影响?
        • 两阶段锁说起:
        • 死锁和死锁检测
      • 08 | 事务到底是隔离的还是不隔离的?
    • 实践篇(37讲)
      • 09 | 普通索引和唯一索引,应该怎么选择?
      • 10 | MySQL为什么有时候会选错索引?
      • 11 | 怎么给字符串字段加索引?
      • 12 | 为什么我的MySQL会“抖”一下?
      • 13 | 为什么表数据删掉一半,表文件大小不变?
      • 14 | count(*)这么慢,我该怎么办?
      • 15 | 答疑文章 (一) : 日志和索引相关问题
      • 16 | "order by"是怎么工作的?
      • 17 | 如何正确地显示随机消息?
      • 18 | 为什么这些SQL语句逻辑相同,性能却差异巨大
      • 19 | 为什么我只查一行的语句,也执行这么慢?
      • 20 | 幻读是什么,幻读有什么问题?
        • 幻读是什么?
        • 幻读有什么问题?
        • 如何解决幻读?
      • 21 | 为什么我只改一行的语句,锁那么多?
      • 22 | MySQL有那些"饮鸩止渴"提高性能的方法?
      • 23 | MySQL是怎么保证数据不丢的?
      • 24 | MySQL 是怎么保证主备一致的?
      • 25 | MySQL是怎么保证高可用的?
      • 26 | 备库为什么延迟好几个小时?
      • 27| 主库出问题了,从库怎么办?
      • 28 | 读写分离有那些坑?
      • 29 | 如何判断一个数据库是不是出问题了?
      • 30 | 答疑文章(二) : 用动态的观点看加锁
      • 31 | 误删数据后除了跑路,还能怎么办?
      • 32 | 为什么还有kill不掉的语句?
      • 33 | 我查了那么多数据,会不会把数据库内存打爆?
      • 34 | 到底可不可以使用join?
      • 35 | join语句怎么优化?
      • 36 | 为什么临时表可以重名?
      • 37 | 什么时候会使用内部临时表?
      • 38 | 都说InnoDB好,那还要不要使用Memory 引擎?
      • 39 | 自增主键为什么不是连续的?
      • 40 | insert语句的锁为什么这么多?
      • 41 | 怎么最快地复制一张表?
      • 40 | insert语句的锁为什么这么多?
      • 41 | 怎么最快地复制一张表?

MySQL 实战45讲–>笔记

[必须了解的mysql三大日志-binlog、redo log和undo log[]

https://segmentfault.com/a/1190000023827696

开篇词

  • 我希望这个专栏能够激发开发者对数据库原理的探索欲,从而更好地理解工作中遇到的问题,更能知道背后的为什么。
  • 会选那些平时使用数据库时高频出现的知识,如事务、索引、锁等内容构成专栏的主线。
  • 可以从点到线,再到面,形成自己的 MySQL 知识网络。

基础篇(8讲)

01 | 基础架构:一条SQL查询语句是如何执行的?

1.1 SQL 语句在 MySQL 的各个功能模块中的执行过程。

存储引擎层负责数据的存储和提取

  • 其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。
  • 现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。

不同存储引擎的表数据存取方式不同,支持的功能也不同,在后面的文章中,我们会讨论到引擎的选择。

不同的存储引擎共用一个 Server 层,也就是从连接器到执行器的部分。

依次看下每个组件的作用。
(1) 连接器

第一步,你会先连接到这个数据库上,这时候接待你的就是连接器。连接器负责跟客户端建立连接、获取权限、维持和管理连接。连接命令一般是这么写的:

mysql -h$ip -P$port -u$user -p

虽然密码也可以直接跟在 -p 后面写在命令行中,但这样可能会导致你的密码泄露。如果你连的是生产服务器,强烈建议你不要这么做。

连接命令中的 mysql 是客户端工具,用来跟服务端建立连接。在完成经典的 TCP 握手后,连接器就要开始认证你的身份,这个时候用的就是你输入的用户名和密码。

  • 如果用户名或密码不对,你就会收到一个"Access denied for user"的错误,然后客户端程序结束执行。
  • 如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。

这就意味着,一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。

连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 show processlist 命令中看到它。文本中这个图是 show processlist 的结果,其中的 Command 列显示为“Sleep”的这一行,就表示现在系统里面有一个空闲连接。

客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时。

如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: Lost connection to MySQL server during query。这时候如果你要继续,就需要重连,然后再执行请求了。

数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。

建立连接的过程通常是比较复杂的,所以我建议你在使用中要尽量减少建立连接的动作,也就是尽量使用长连接。

但是全部使用长连接后,你可能会发现,有些时候 MySQL 占用内存涨得特别快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL 异常重启了。

怎么解决这个问题呢?你可以考虑以下两种方案。

1.定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。

2.如果你用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。

(2) 查询缓存

连接建立完成后,你就可以执行 select 语句了。执行逻辑就会来到第二步:查询缓存。

MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。

如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。你可以看到,如果查询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。

但是大多数情况下我会建议你不要使用查询缓存,为什么呢?因为查询缓存往往弊大于利。

查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。

好在 MySQL 也提供了这种“按需使用”的方式。你可以将参数 query_cache_type 设置成 DEMAND,这样对于默认的 SQL 语句都不使用查询缓存。而对于你确定要使用查询缓存的语句,可以用 SQL_CACHE 显式指定,像下面这个语句一样:

mysql> select SQL_CACHE * from T where ID=10

需要注意的是,MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。

(3) 分析器

如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。

分析器先会做“词法分析”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。

MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。

做完了这些识别以后,就要做“语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。

如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如下面这个语句 select 少打了开头的字母“s”。


mysql> elect * from t where ID=1;

ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'elect * from t where ID=1' at line 1

一般语法错误会提示第一个出现错误的位置,所以你要关注的是紧接“use near”的内容。

(4) 优化器

经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。

优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。比如你执行下面这样的语句,这个语句是执行两个表的 join:

mysql> select * from t1 join t2 using(ID)  where t1.c=10 and t2.d=20;
  • 既可以先从表 t1 里面取出 c=10 的记录的 ID 值,再根据 ID 值关联到表 t2,再判断 t2 里面 d 的值是否等于 20。

  • 也可以先从表 t2 里面取出 d=20 的记录的 ID 值,再根据 ID 值关联到 t1,再判断 t1 里面 c 的值是否等于 10。

这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。

优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。如果你还有一些疑问,比如优化器是怎么选择索引的,有没有可能选择错等等,没关系,我会在后面的文章中单独展开说明优化器的内容。

(5) 执行器

MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。

开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示 (在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用 precheck 验证权限)。

mysql> select * from T where ID=10;

ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'

如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。

比如我们这个例子中的表 T 中,ID 字段没有索引,那么执行器的执行流程是这样的:

  1. 调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;

  2. 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。

  3. 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。

    至此,这个语句就执行完成了。

对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。

你会在数据库的慢查询日志中看到一个 rows_examined 的字段,表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。

在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟 rows_examined 并不是完全相同的。我们后面会专门有一篇文章来讲存储引擎的内部机制,里面会有详细的说明。

小结

今天我给你介绍了 MySQL 的逻辑架构,希望你对一个 SQL 语句完整执行流程的各个阶段有了一个初步的印象。由于篇幅的限制,我只是用一个查询的例子将各个环节过了一遍。如果你还对每个环节的展开细节存有疑问,也不用担心,后续在实战章节中我还会再提到它们。

02 | 日志系统:一条SQL更新语句是如何执行的?

前面我们系统了解了一个查询语句的执行流程,并介绍了执行过程中涉及的处理模块。相信你还记得,一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块,最后到达存储引擎。

那么,一条更新语句的执行流程又是怎样的呢?

之前你可能经常听 DBA 同事说,MySQL 可以恢复到半个月内任意一秒的状态,惊叹的同时,你是不是心中也会不免会好奇,这是怎样做到的呢?

我们还是从一个表的一条更新语句说起,下面是这个表的创建语句,这个表有一个主键 ID 和一个整型字段 c:

mysql> create table T(ID int primary key, c int);

如果要将 ID=2 这一行的值加 1,SQL 语句就会这么写:

mysql> update T set c=c+1 where ID=2;

前面我有跟你介绍过 SQL 语句基本的执行链路,这里我再把那张图拿过来,你也可以先简单看看这个图回顾下。首先,可以确定的说,查询语句的那一套流程,更新语句也是同样会走一遍。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xIPWd2ui-1615554620605)(MySQL 实战45讲–笔记.assets/0d2070e8f84c4801adbfa03bda1f98d9.png)]

​ MySQL 的逻辑架构图

你执行语句前要先连接数据库,这是连接器的工作。

你执行语句前要先连接数据库,这是连接器的工作。

前面我们说过,在一个表上有更新的时候,跟这个表有关的查询缓存会失效,所以这条语句就会把表 T 上所有缓存结果都清空。这也就是我们一般不建议使用查询缓存的原因。

接下来,分析器会通过词法和语法解析知道这是一条更新语句。优化器决定要使用 ID 这个索引。然后,执行器负责具体执行,找到这一行,然后更新。

与查询流程不一样的是,更新流程还涉及两个重要的日志模块,它们正是我们今天要讨论的主角:redo log(重做日志)和 binlog(归档日志)。如果接触 MySQL,那这两个词肯定是绕不过的,我后面的内容里也会不断地和你强调。不过话说回来,redo log 和 binlog 在设计上有很多有意思的地方,这些设计思路也可以用到你自己的程序里。

重要的日志模块:redo log

如果有人要赊账或者还账的话,掌柜一般有两种做法:

  • 一种做法是直接把账本翻出来,把这次赊的账加上去或者扣除掉;

  • 另一种做法是先在粉板上记下这次的账,等打烊以后再把账本翻出来核算。

在生意红火柜台很忙时,掌柜一定会选择后者,因为前者操作实在是太麻烦了。首先,你得找到这个人的赊账总额那条记录。你想想,密密麻麻几十页,掌柜要找到那个名字,可能还得带上老花镜慢慢找,找到之后再拿出算盘计算,最后再将结果写回到账本上。

这整个过程想想都麻烦。相比之下,还是先在粉板上记一下方便。你想想,如果掌柜没有粉板的帮助,每次记账都得翻账本,效率是不是低得让人难以忍受?

同样,在 MySQL 里也有这个问题,如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。为了解决这个问题,MySQL 的设计者就用了类似酒店掌柜粉板的思路来提升更新效率。

而粉板和账本配合的整个过程,其实就是 MySQL 里经常说到的 WAL 技术,WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘,也就是先写粉板,等不忙的时候再写账本。

具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log(粉板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事。

如果今天赊账的不多,掌柜可以等打烊后再整理。但如果某天赊账的特别多,粉板写满了,又怎么办呢?这个时候掌柜只好放下手中的活儿,把粉板中的一部分赊账记录更新到账本中,然后把这些记录从粉板上擦掉,为记新账腾出空间。

与此类似,InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-znhTwMQg-1615554620609)(MySQL 实战45讲–笔记.assets/16a7950217b3f0f4ed02db5db59562a7.png)]

write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。

有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe

要理解 crash-safe 这个概念,可以想想我们前面赊账记录的例子。只要赊账记录记在了粉板上或写在了账本上,之后即使掌柜忘记了,比如突然停业几天,恢复生意后依然可以通过账本和粉板上的数据明确赊账账目。

重要的日志模块:binlog

03 | 事务隔离:为什么你改了我还看不见?

隔离性与隔离级别

事务的性质(ACID):

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

SQL标准的事务隔离级别包括:

  • 读未提交是指,一个事务还没提交时,它做的变更就能别的事务看到。
  • 读提交是指,一个事务提交之后,它做的变更才会被其他是哇看到。
  • 可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
  • 串行性,顾名思义是对同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

事务隔离的实现

展开说明"可重复读"

​ 在MYSQL中,实际上记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

什么时候删除回滚日志?

在不需要的时候才删除。系统会判断当没有事务再需要用到这些回滚日志时,回滚日志就会被删除。

尽量不要使用长事务

因为会导致大量占用存储空间。

事务的启动方式

MYSQL的事务启动方式有以下几种:

  1. 显式启动事务语句,begin或start transaction。配套的提交语句时commit,回滚语句是rollback。
  2. set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。

04 | 深入浅出索引(上)

对于数据库的表而言,索引其实就是它的"目录"。

索引的常见模型

  1. 索引的作用:提高数据查询效率。
  2. 常见的索引的模型:哈希表,有序数组,搜索树
  3. 哈希表 是一种以键 - 值(key-value)存储数据的结构
  4. .哈希思路:把值放在数组里,用一个哈希函数把key换算成一个确定的位置,然后把value放在数组的这个位置
  5. 哈希冲突的处理办法:链表
  6. 哈希表这种结构适用于只有等值查询的场景。
  7. 有序数组索引只适用于静态存储引擎
  8. 有序数组:按顺序存储。查询用二分法就可以快速查询,时间复杂度是:O(log(N))
  9. 有序数组查询效率高,更新效率低
  10. 二叉搜索树的特点是:父节点左子树所有结点的值小于父节点的值,右子树所有结点的值大于父节点的值。
  11. 二叉搜索树:查询时间复杂度O(log(N)),更新时间复杂度O(log(N))
  12. 数据库存储大多不适用二叉树,因为树高,适用"N叉"树
  13. N 叉树由于在读写上的性能优点,以及适配磁盘的访问模式,已经被广泛应用在数据库引擎中了。

InnoDB 的索引模型

  1. InnoDB中的索引模型:B+Tree
  2. 根据叶子节点的内容,索引类型分为主键索引和非主键索引。
  3. 主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引(clustered index)。非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引(secondary index)。
  4. 主键索引和普通索引的查询有什么区别:主键索引只要搜索ID这个B+Tree即可拿到数据。普通索引先搜索索引拿到主键值,再到主键索引树搜索一次(回表)
  5. 当一个数据页满了,根据 B+ 树的算法,这时候需要申请一个新的数据页,然后挪动部分数据过去。这个过程称为页分裂,会导致性能下降,整体空间利用率降低大约50%。
  6. 当相邻的两个数据页利用率很低的时候会做数据页合并,合并的过程是分裂过程的逆过程。
  7. 从性能和存储空间方面考量,自增主键往往是更合理的选择。
  8. 有些业务的场景需求适合用业务字段直接做主键的:只有一个索引;该索引必须是唯一索引。

05 | 深入浅出索引(下)

  1. 回到主键索引树搜索的过程,我们成为回表。
  2. 二级索引查询结果仅仅是主键,此时不需要回表查主键索引,成为覆盖索引。
  3. 覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。
  4. B+ 树这种索引结构,可以利用索引的“最左前缀”,来定位记录。

最左前缀原则

  1. 在建立联合索引的时候,如何安排索引内的字段顺序:第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的
  2. 需要同时维护(a,b),(b)这两个索引,这时候我们要考虑的原则就是空间了。
  3. 不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。

索引下推

踩过坑:有人问我联合索引的技巧,回答的不是很好
总结:
1、覆盖索引:如果查询条件使用的是普通索引(或是联合索引的最左原则字段),查询结果是联合索引的字段或是主键,不用回表操作,直接返回结果,减少IO磁盘读写读取正行数据
2、最左前缀:联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符
3、联合索引:根据创建联合索引的顺序,以最左原则进行where检索,比如(age,name)以age=1 或 age= 1 and name=‘张三’可以使用索引,单以name=‘张三’ 不会使用索引,考虑到存储空间的问题,还请根据业务需求,将查找频繁的数据进行靠左创建索引。
4、索引下推:like 'hello%’and age >10 检索,MySQL5.6版本之前,会对匹配的数据进行回表查询。5.6版本后,会先过滤掉age<10的数据,再进行回表查询,减少回表率,提升检索速度

06 | 全局锁和表锁 :给表加个字段怎么有这么多阻碍?

全局锁主要用在逻辑备份过程中。对于全部是 InnoDB 引擎的库,我建议你选择使用–single-transaction 参数,对应用会更友好。

表锁一般是在数据库引擎不支持行锁的时候才会被用到的。如果你发现你的应用程序里有 lock tables 这样的语句,你需要追查一下,比较可能的情况是:

  • 要么是你的系统现在还在用 MyISAM 这类不支持事务的引擎,那要安排升级换引擎;
  • 要么是你的引擎升级了,但是代码还没升级。我见过这样的情况,最后业务开发就是把 lock tables 和 unlock tables 改成 begin 和 commit,问题就解决了。

MDL 会直到事务提交才释放,在做表结构变更的时候,你一定要小心不要导致锁住线上查询和更新。

07 | 行锁功过:怎么减少行锁对性能的影响?

两阶段锁说起:

  1. 在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。
  2. **建议:**如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。

死锁和死锁检测

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

出现死锁以后有两种策略:

  • 一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置,InnoDB引擎默认值是50s。
  • 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑(默认是开启状态)。

如何解决热点行更新导致的性能问题?

1、如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关闭掉。一般不建议采用
2、控制并发度,对应相同行的更新,在进入引擎之前排队。这样在InnoDB内部就不会有大量的死锁检测工作了。
3、将热更新的行数据拆分成逻辑上的多行来减少锁冲突,但是业务复杂度可能会大大提高。

innodb行级锁是通过锁索引记录实现的,如果更新的列没建索引是会锁住整个表的。

08 | 事务到底是隔离的还是不隔离的?

  1. innodb支持RC和RR隔离级别实现是用的一致性视图(consistent read view)

  2. 事务在启动时会拍一个快照,这个快照是基于整个库的。

    基于整个库的意思就是说一个事务内,整个库的修改对于该事务都是不可见的(对于快照读的情况)如果在事务内select表,另外的事务执行了DDL t表,根据发生时间,要嘛锁住要嘛报错(参考第六章)

  3. 事务是如何实现的MVCC呢?

    (1)每个事务都有一个事务ID,叫做transaction id(严格递增)

    (2)事务在启动时,找到已提交的最大事务ID记为up_limit_id。

    (3)事务在更新一条语句时,比如id=1改为了id=2.会把id=1和该行之前的row trx_id写到undo log里,
    并且在数据页上把id的值改为2,并且把修改这条语句的transaction id记在该行行头

    (4)再定一个规矩,一个事务要查看一条数据时,必须先用该事务的up_limit_id与该行的transaction id做比对,
    如果up_limit_id>=transaction id,那么可以看.

    如果up_limit_idtransaction id,才返回数据

  4. 什么是当前读,由于当前读都是先读后写,只能读当前的值,所以为当前读.会更新事务内的up_limit_id为该事务的transaction id

  5. 为什么rr能实现可重复读而rc不能,分两种情况

    (1)快照读的情况下,rr不能更新事务内的up_limit_id,而rc每次会把up_limit_id更新为快照读之前最新已提交事务的transaction id,则rc不能可重复读

    (2)当前读的情况下,rr是利用record lock+gap lock来实现的,而rc没有gap,所以rc不能可重复读

实践篇(37讲)

09 | 普通索引和唯一索引,应该怎么选择?

  1. 选择普通索引还是唯一索引?

    • 对于查询过程来说:

      (1) 普通索引,查到满足条件的第一个记录后,继续查找下一个记录,直到第一个不满足条件的记录

      (2)唯一索引,由于索引唯一性,查到第一个不满足条件的记录后,停止检索

      但是,两者的性能差距微乎其微。因为InnDB根据数据页来读写的。

    • 对于更新过程来说:

      概念:change buffer

      当需要更新一个数据页:

      (1)如果数据页在内存中就直接更新。

      (2)如果不在内存中,在不影响数据一致性的前提下,InnoDB会将这些更新操作缓存在change buffer中。下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer 中的与这个页有关的操作。

    • change buffer 是可以持久化的数据。在内存中有拷贝,也会被写入到磁盘上

    • purge:将change buffer 中操作应用到原数据页上,得到最新结果的过程中,也会执行purge 访问这个数据页会出发purge,系统后台线程定期purge,在数据库正常关闭的过程中,也会执行purge

    • 唯一索引的更新不能使用change buffer

    • change buffer用的是buffer pool里的内存,change buffer的大小,可以通过参数innodb_change_buffer_max_size来动态设置。这个参数设置为50的时候,表示change buffer的大小最多只能占用buffer pool的50%。

    • 将数据从磁盘读入内存涉及随机IO的访问,是数据库里面成本最高的操作之一。

    • change buffer 因为减少了随机磁盘访问,所以对更新性能的提升很明显。

change buffer使用场景

  • 在一个数据页做purge之前,change buffer记录的变更越多,收益就越大。
  • 对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时change buffer的使用效果最好。这种业务模型常见的就是账单类、日志类的系统。
  • 反过来,假设一个业务的更新模式是写入之后马上会做查询,那么即使满足了条件,将更新先记录在change buffer,但之后由于马上要访问这个数据页,会立即触发purge过程。
    这样随机访问IO的次数不会减少,反而增加了change buffer的维护代价。所以,对于这种业务模式来说,change buffer反而起到了副作用。

索引的选择和实践:
尽可能使用普通索引。
redo log主要节省的是随机写磁盘的IO消耗(转成顺序写),而change buffer主要节省的则是随机读磁盘的IO消耗。

10 | MySQL为什么有时候会选错索引?

  1. 索引统计的更新机制,并提到了优化器存在选错索引的可能性。
  2. 对于由于索引统计信息不准确导致的问题,可以用 analyze table 来解决。
  3. 于其他优化器误判的情况,可以在应用端用 force index 来强行指定索引,也可以通过修改语句来引导优化器,还可以通过增加或者删除索引来绕过这个问题。

11 | 怎么给字符串字段加索引?

  1. 直接创建完整索引,这样可能比较占用空间;
  2. 创建前缀索引,节省空间,但会增加查询扫描次数,并且不能使用覆盖索引;
  3. 倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够的问题;
  4. 创建hash字段索引,查询性能稳定,有额外的存储和计算消耗,跟第三种方式一样,都不支持范围扫描。

12 | 为什么我的MySQL会“抖”一下?

1:MySQL抖一下是什么意思?

抖我认为就是不稳定的意思,一个SQL语句平时速度都挺快的,偶尔会慢一下且没啥规律,就是抖啦!

2:MySQL为啥会抖一下?

因为运行的不正常了,或者不稳定了,需要花费更多的资源处理别的事情,会使SQL语句的执行效率明显变慢。针对innoDB导致MySQL抖的原因,主要是InnoDB 会在后台刷脏页,而刷脏页的过程是要将内存页写入磁盘。所以,无论是你的查询语句在需要内存的时候可能要求淘汰一个脏页,还是由于刷脏页的逻辑会占用 IO 资源并可能影响到了你的更新语句,都可能是造成你从业务端感知MySQL“抖”了一下的原因。

3:MySQL抖一下有啥问题?

很明显系统不稳定,性能突然下降对业务端是很不友好的。

4:怎么让MySQL不抖?

设置合理参数配配置,尤其是设置 好innodb_io_capacity 的值,并且平时要多关注脏页比例,不要让它经常接近 75%

5:啥是脏页?

当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。

按照这个定义感觉脏页是不可避免的,写的时候总会先写内存再写磁盘和有没有用WAL没啥关系?

6:啥是干净页?

内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”。

7:脏页是咋产生的?

因为使用了WAL技术,这个技术会把数据库的随机写转化为顺序写,但副作用就是会产生脏页。

8:啥是随机写?为啥那么耗性能?

随机写我的理解是,这次写磁盘的那个扇区和上一次没啥关系,需要重新定位位置,机械运动是很慢的即使不是机械运动重新定位写磁盘的位置也是很耗时的。

9:啥是顺序写?

顺序写我的理解是,这次写磁盘那个扇区就在上一次的下一个位置,不需要重新定位写磁盘的位置速度当然会快一些。

10:WAL怎么把随机写转化为顺序写的?

写redolog是顺序写的,先写redolog等合适的时候再写磁盘,间接的将随机写变成了顺序写,性能确实会提高不少。

13 | 为什么表数据删掉一半,表文件大小不变?

1:为啥删除了表的一半数据,表文文件大小没变化?

因为delete 命令其实只是把记录的位置,或者数据页标记为了“可复用”,但磁盘文件的大小是不会变的。也可以认为是一种逻辑删除,所以物理空间没有实际释放,只是标记为可复用,表文件的大小当然是不变的啦!

2:表的数据信息存在哪里?

表数据信息可能较小也可能巨大无比,她可以存储在共享表空间里,也可以单独存储在一个以.ibd为后缀的文件里,由参数innodb_file_per_table来控制,老师建议总是作为一个单独的文件来存储,这样非常容易管理,并且在不需要的时候,使用drop table命令也能直接把对应的文件删除,如果存储在共享空间之中即使表删除了空间也不会释放。

3:表的结构信息存在哪里?

首先,表结构定义占有的存储空间比较小,在MySQL8.0之前,表结构的定义信息存在以.frm为后缀的文件里,在MySQL8.0之后,则允许把表结构的定义信息存在系统数据表之中。

系统数据表,主要用于存储MySQL的系统数据,比如:数据字典、undo log(默认)等文件

4:如何才能删除表数据后,表文件大小就变小?

重建表,消除表因为进行大量的增删改操作而产生的空洞,使用如下命令:

  • alter table t engine=InnoDB
  • optimize table t( 等于 recreate+analyze)。
  • truntace table t (等于drop+create)

5:空洞是啥?咋产生的?

空洞就是那些被标记可复用但是还没被使用的存储空间。

使用delete命令删除数据会产生空洞,标记为可复用

插入新的数据可能引起页分裂,也可能产生空洞

修改操作,有时是一种先删后插的动作也可能产生空洞

14 | count(*)这么慢,我该怎么办?

  1. 又刷新了认知,先给结论(之前不知从哪看的,以为count(主键id)性能最佳)

    按照效率排序的话,count(字段)),所以老师建议,尽量使用 count()。

  2. count(*)这么慢,我该怎么办?

要么忍,要么自己动手记录,如果自己记录的话,老师建议使用数据库来弄,感觉使用数据库自己弄的思路可以建议MySQL实现一下?

  1. count()的语义是啥?

    首先,不同的存储引擎实现方式不同
    MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count() 的时候会直接返回这个数,效率很高;
    而 InnoDB 引擎就麻烦了,它执行 count(
    ) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。
    以下针对innodb来说
    count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加,最后返回累计值。

  2. count(字段)怎么计数?

4-1:如果这个“字段”是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加;
4-2:如果这个“字段”定义允许为 null,那么执行的时候,判断到有可能是 null,还要把值取出来再判断一下,不是 null 才累加。
从引擎返回的字段会涉及到解析数据行,以及拷贝字段值的操作。

  1. count(主键 id)怎么计数?

    对于 count(主键 id) 来说,InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。从引擎返回的 主键id 会涉及到解析数据行,以及拷贝字段值的操作。

  2. count(1)怎么计数?

对于 count(1) 来说,InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。

  1. count(*)怎么计数?

对于count()来说,并不会把全部字段取出来,而是专门做了优化,不取值。count() 肯定不是 null,按行累加。

  1. 现在终于弄明白这些count()背后的计算原理啦!非常感谢!另外,分析这些count()的原则如下:

8-1:server 层要什么就给什么;
8-2:InnoDB 只给必要的值;
8-3:现在的优化器只优化了 count(*) 的语义为“取行数”,其他“显而易见”的优化并没有做。
这几条原则对别的性能差别的分析也是OK的吧?
达到同样的目标,谁绕的弯越多做的事情越多就会越慢,性能自然不咋滴!不过知道每种达到目的的路径轨迹是一个难点,如果知道谁不喜欢走捷径呢?

15 | 答疑文章 (一) : 日志和索引相关问题

https://time.geekbang.org/column/article/73161

16 | "order by"是怎么工作的?

1.MySQL会为每个线程分配一个内存(sort_buffer)用于排序该内存大小为sort_buffer_size

  • 如果排序的数据量小于sort_buffer_size,排序将会在内存中完成
  • 如果排序数据量很大,内存中无法存下这么多数据,则会使用磁盘临时文件来辅助排序,也称外部排序
  • 在使用外部排序时,MySQL会分成好几份单独的临时文件用来存放排序后的数据,然后在将这些文件合并成一个大文件

2.mysql会通过遍历索引将满足条件的数据读取到sort_buffer,并且按照排序字段进行快速排序

  • 如果查询的字段不包含在辅助索引中,需要按照辅助索引记录的主键返回聚集索引取出所需字段
  • 该方式会造成随机IO,在MySQL5.6提供了MRR的机制,会将辅助索引匹配记录的主键取出来在内存中进行排序,然后在回表
  • 按照情况建立联合索引来避免排序所带来的性能损耗,允许的情况下也可以建立覆盖索引来避免回表

全字段排序
1.通过索引将所需的字段全部读取到sort_buffer中
2.按照排序字段进行排序
3.将结果集返回给客户端

缺点:
1.造成sort_buffer中存放不下很多数据,因为除了排序字段还存放其他字段,对sort_buffer的利用效率不高
2.当所需排序数据量很大时,会有很多的临时文件,排序性能也会很差

优点:MySQL认为内存足够大时会优先选择全字段排序,因为这种方式比rowid 排序避免了一次回表操作

rowid排序
1.通过控制排序的行数据的长度来让sort_buffer中尽可能多的存放数据,max_length_for_sort_data
2.只将需要排序的字段和主键读取到sort_buffer中,并按照排序字段进行排序
3.按照排序后的顺序,取id进行回表取出想要获取的数据
4.将结果集返回给客户端

优点:更好的利用内存的sort_buffer进行排序操作,尽量减少对磁盘的访问

缺点:回表的操作是随机IO,会造成大量的随机读,不一定就比全字段排序减少对磁盘的访问

3.按照排序的结果返回客户所取行数

17 | 如何正确地显示随机消息?

​ 如果你直接使用 order by rand(),这个语句需要 Using temporary 和 Using filesort,查询的执行代价往往是比较大的。所以,在设计的时候你要尽量避开这种写法。

​ 今天的例子里面,我们不是仅仅在数据库内部解决问题,还会让应用代码配合拼接 SQL 语句。在实际应用的过程中,比较规范的用法就是:尽量将业务逻辑写在业务代码中,让数据库只做“读写数据”的事情。因此,这类方法的应用还是比较广泛的。

18 | 为什么这些SQL语句逻辑相同,性能却差异巨大

对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能。

第二个例子是隐式类型转换,第三个例子是隐式字符编码转换,它们都跟第一个例子一样,因为要求在索引字段上做函数操作而导致了全索引扫描。

MySQL 的优化器确实有“偷懒”的嫌疑,即使简单地把 where id+1=1000 改写成 where id=1000-1 就能够用上索引快速查找,也不会主动做这个语句重写。

因此,每次你的业务代码升级时,把可能出现的、新的 SQL 语句 explain 一下,是一个很好的习惯。

19 | 为什么我只查一行的语句,也执行这么慢?

课前思考
1:为啥只查一行的语句,也执行这么慢?
查的慢,基本上就是索引使用的问题,和查一行还是N行(N不是巨大),没有必然联系。查一行慢,猜测没有走索引查询,且数据量比较大。
课后思考
1:阅后发现自己的无知,只查询一行的语句,也比较慢,原因从大到小可分为三种情况?
第一MySQL数据库本身被堵住了,比如:系统或网络资源不够
第二SQL语句被堵住了,比如:表锁,行锁等,导致存储引擎不执行对应的SQL语句
第三确实是索引使用不当,没有走索引
第四是表中数据的特点导致的,走了索引,但回表次数庞大
感谢老师的分享,真是醍醐灌顶呀

20 | 幻读是什么,幻读有什么问题?

幻读是什么?

幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。

“幻读”做一个说明

  1. 在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现。
  2. 上面 session B 的修改结果,被 session A 之后的 select 语句用“当前读”看到,不能称为幻读。幻读仅专指“新插入的行”。

幻读有什么问题?

首先是语义上的,其次是数据一致性的问题。

如何解决幻读?

为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。

比如行锁,分成读锁和写锁。下图就是这两种类型行锁的冲突关系。

也就是说,跟行锁有冲突关系的是“另外一个行锁”。

但是间隙锁不一样**,跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作**。间隙锁之间都不存在冲突关系。

间隙锁和 next-key lock 的引入,帮我们解决了幻读的问题,但同时也带来了一些“困扰”。

间隙锁的引入,可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的。

21 | 为什么我只改一行的语句,锁那么多?

总结的加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug”。

  1. 原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。
  2. 原则 2:查找过程中访问到的对象才会加锁。
  3. 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
  4. 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
  5. 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

**在删除数据的时候尽量加 limit。**这样不仅可以控制删除数据的条数,让操作更安全,还可以减小加锁的范围。

  1. 间隙锁和间隙锁之间并不冲突,间隙锁和insert到这个间隙的语句才会冲突,
  2. 因此session B加间隙锁(5, 10)是可以成功的,但是如果往(5, 10)里面插入的话会被阻塞。
  3. 但是如果直接加next-key lock(5, 10],那么肯定是会被阻塞的,因此这个例子确实说明,加锁的步骤是分两步的,先是间隙锁,后是行锁。
  4. 而且只要理解了间隙锁和行锁之间冲突的原则是不一样的,也就很容易理解这两个锁并不是一起加的了。

22 | MySQL有那些"饮鸩止渴"提高性能的方法?

第一种方法:先处理掉那些占着连接但是不工作的线程。

第二种方法:减少连接过程的消耗。

慢查询性能问题

在 MySQL 中,会引发性能问题的慢查询,大体有以下三种可能:

  1. 索引没有设计好;
  2. SQL 语句没写好;
  3. MySQL 选错了索引。

导致慢查询的第一种可能是,索引没有设计好。

这种场景一般就是通过紧急创建索引来解决。MySQL 5.6 版本以后,创建索引都支持 Online DDL 了,对于那种高峰期数据库已经被这个语句打挂了的情况,最高效的做法就是直接执行 alter table 语句。

导致慢查询的第二种可能是,语句没写好。

MySQL 5.7 提供了 query_rewrite 功能,可以把输入的一种语句改写成另外一种模式。

导致慢查询的第三种可能,就是碰上了我们在第 10 篇文章《MySQL 为什么有时候会选错索引?》中提到的情况,MySQL 选错了索引。

这时候,应急方案就是给这个语句加上 force index。

同样地,使用查询重写功能,给原来的语句加上 force index,也可以解决这个问题。

QPS 突增问题

有时候由于业务突然出现高峰,或者应用程序 bug,导致某个语句的 QPS 突然暴涨,也可能导致 MySQL 压力过大,影响服务。

23 | MySQL是怎么保证数据不丢的?

binlog 的写入机制

其实,binlog 的写入逻辑比较简单:事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。

write 和 fsync 的时机,是由参数 sync_binlog 控制的:

  1. sync_binlog=0 的时候,表示每次提交事务都只 write,不 fsync;
  2. sync_binlog=1 的时候,表示每次提交事务都会执行 fsync;
  3. sync_binlog=N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync。

redo log 的写入机制

redo log 可能存在的三种状态

MySQL 实战45讲--笔记_第1张图片

这三种状态分别是:

  1. 存在 redo log buffer 中,物理上是在 MySQL 进程内存中,就是图中的红色部分;
  2. 写到磁盘 (write),但是没有持久化(fsync),物理上是在文件系统的 page cache 里面,也就是图中的黄色部分;
  3. 持久化到磁盘,对应的是 hard disk,也就是图中的绿色部分。

日志写到 redo log buffer 是很快的,wirte 到 page cache 也差不多,但是持久化到磁盘的速度就慢多了。

为了控制 redo log 的写入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 参数,它有三种可能取值:

  1. 设置为 0 的时候,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中 ;
  2. 设置为 1 的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘;
  3. 设置为 2 的时候,表示每次事务提交时都只是把 redo log 写到 page cache。

两种场景会让一个没有提交的事务的 redo log 写入到磁盘中。

  1. 一种是,redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动写盘。
  2. 另一种是,并行的事务提交的时候,顺带将这个事务的 redo log buffer 持久化到磁盘。

如果你的 MySQL 现在出现了性能瓶颈,而且瓶颈在 IO 上,可以通过哪些方法来提升性能呢?

  1. 设置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 参数,减少 binlog 的写盘次数。这个方法是基于“额外的故意等待”来实现的,因此可能会增加语句的响应时间,但没有丢失数据的风险。
  2. 将 sync_binlog 设置为大于 1 的值(比较常见是 100~1000)。这样做的风险是,主机掉电时会丢 binlog 日志。
  3. 将 innodb_flush_log_at_trx_commit 设置为 2。这样做的风险是,主机掉电的时候会丢数据。

24 | MySQL 是怎么保证主备一致的?

1、简单主备,一主多备,主进行更新操作,将生成binlog文件发送给备,但是比较好奇一点的是所有备向主拿binlog文件的时候,主都是一个线程进行将binlog文件依次发送给备么?

两个库互为主备可以将一个负责数据的写入,生成binlog文件,另一个作为数据的同步,将其改变的binlog同步到自身,然后其他备再从其同步binlog,多master可以做到一台宕机,快速切换到另一台作为主,防止主库宕机对业务造成的影响,但是这样可能导致一定程度的同步延迟。

2、主备复制关系搭建完成,主有数据写入的时候,发送给备的应该不是整个binlog log文件吧,是每次写入的binlog event么?

3、在图 2 主备流程图对bg-thread->undolog(disk)->data(disk)不太理解,回滚段也是先记录到内存,再记录在磁盘么?undolog(disk)再到data(disk),看了下undo log的控制参数没有看到控制类似行为的,没想通?老师帮忙解答下哦

4、binlog的三种格式,statement,记录数据库原句,有可能导致,主备所选择的索引不一致,导致主备数据不一致。row,binlog log记录的是操作的字段值,根据binlog_row_image 的默认配置是 FULL包括操作行为的所有字段值,binlog_row_image 设置为 MINIMAL,则会记录必须的字段,一般设置为row,可以根据binlog文件做其他操作,比如在误删除一行数据时,可以做insert,恢复数据。

5、如果执行的是 update 语句的话,binlog 里面会记录修改前整行的数据和修改后的整行数据,在二级索引的普通索引,有个change buffer优化,防止频繁的将数据页读入进来,可以减少buffer pool的消耗,可以在读取数据时,再将其marge,或者后台线程marge,但是在binlog log设置row格式的,update时,需要记录更新前后的数据,那这样的话,chage buffer不是用不上了么?还是说设置成row格式的时候,change buffer会没生效?老师麻烦帮忙解答下哦,没想明白

作者回复: 2. 流式发送,一个事务提交就会发
\3. “回滚段也是先记录到内存,再记录在磁盘么?” 是的。 undolog(disk)不需要到data(disk),undo log的作用看一下08篇
\5. “update时,需要记录更新前后的数据,那这样的话,chage buffer不是用不上了么” — 不是的,binlog里面的内容用的是主键索引上的,主键索引确实用不上change buffer,但是普通索引可以

25 | MySQL是怎么保证高可用的?

遇到过下面几种造成主从延迟的情况:

1.主库DML语句并发大,从库qps高
2.从库服务器配置差或者一台服务器上几台从库(资源竞争激烈,特别是io)
3.主库和从库的参数配置不一样
4.大事务(DDL,我觉得DDL也相当于一个大事务)
5.从库上在进行备份操作
6.表上无主键的情况(主库利用索引更改数据,备库回放只能用全表扫描,这种情况可以调整slave_rows_search_algorithms参数适当优化下)
7.设置的是延迟备库
8.备库空间不足的情况下

1、主备延迟,就是在同一个事务在备库执行完成的时间和主库执行完成的时间之间的差值,包括主库事务执行完成时间和将binlog发送给备库,备库事务的执行完成时间的差值。每个事务的seconds_behind_master延迟时间,每个事务的 binlog 里面都有一个时间字段,用于记录主库上的写入时间,备库取出当前正在执行的事务的时间字段的值,计算它与当前系统时的差值。
2、主备延迟的来源

①首先,有些部署条件下,备库所在机器的性能要比主库所在的机器性能差,原因多个备库部署在同一台机器上,大量的查询会导致io资源的竞争,解决办法是配置”双1“,redo log和binlog都只write fs page cache

②备库的压力大,产生的原因大量的查询操作在备库操作,耗费了大量的cpu,导致同步延迟,解决办法,使用一主多从,多个从减少备的查询压力

③大事务,因为如果一个大的事务的dml操作导致执行时间过长,将其事务binlog发送给备库,备库也需执行那么长时间,导致主备延迟,解决办法尽量减少大事务,比如delete操作,使用limit分批删除,可以防止大事务也可以减少锁的范围。
④大表的ddl,会导致主库将其ddl binlog发送给备库,备库解析中转日志,同步,后续的dml binlog发送过来,需等待ddl的mdl写锁释放,导致主备延迟。

3、可靠性优先策略

①判断备库 B 现在的 seconds_behind_master如果小于某个值(比如 5 秒)继续下一步,否则持续重试这一步,②把主库 A 改成只读状态,即把 readonly 设置为 true,

③判断备库 B 的 seconds_behind_master的值,直到这个值变成 0 为止;

把备库 B 改成可读写也就是把 readonly 设置为 false; 把业务请求切换到备库,个人理解如果发送过来的binlog在中转日志中有多个事务,业务不可用的时间,就是多个事务被运用的总时间。如果非正常情况下,主库掉电,会导致出现的问题,如果备库和主库的延迟时间短,在中转日志运用完成,业务才能正常使用,如果在中转日志还未运用完成,切换为备库会导致之前完成的事务,”数据丢失“,但是在一些业务场景下不可接受。

4、**可用性策略,**出现的问题:在双m,且binlog_format=mixed,会导致主备数据不一致,使用使用 row 格式的 binlog 时,数据不一致的问题更容易发现,因为binlog row会记录字段的所有值。

26 | 备库为什么延迟好几个小时?

备库一般会延迟分钟级别,比如主库压力比较大的时候,备库有可能会延迟小时级别,为此mysql官方提供了多种多线程复制策略
1、5.6基于库的多线程复制策略,使用hash数据库名作为key,value为多少个事务修改此数据库,使用hash来分配多线程,如果一个新事务加入进来,如果有冲突的hash,分配给此线程,如果没有冲突分配给空闲的线程,感觉实现的思路使用队列+线程池,如果线程池中没有空闲的线程,就在队列中增加事务,如果队列满,分发器阻塞,不解析binlog,分发器是生产者,线程池是消费者,基于库的多线程复制有如下优点①构造 hash 值的时候很快,只需要库名;线程的hash项也很少②binlog不需要强制指定row,statement也可以拿到库名。缺点:①如果只有一个库单线程复制,可以将其热点表分布到多个库中(不推荐使用),如果多个库的热点程度不同也会使其单线程复制。
2、基于表的多线程复制(非官方,老师实现),hash数据库名+表名作为key,value为多少个事务修改此数据表,同一个事务的多张表,在同一个线程进行处理,防止违反原子性,优点对同一个库多个热点表可以同时复制,多表负载效果很好,如果碰到热点表,比如所有的更新事务都会涉及到某一个表的时候,会使用单线程复制。
3、基于行的多线程复制,key必须是“库名 + 表名 + 唯一键的值“也需考虑唯一主键,防止唯一主键冲突(cpu的多线程调度,顺序不固定),value为修改前后key的次数,约束①表必须有主键②不能有外键③binlog格式row(表复制也一样)缺点:①大事务耗cpu②hash项多。优化可以设置阈值,如果事务修改的行大于特定值,使用单线程复制(老师自己实现)。mysql官网基于行的多线程复制,表示的是对于事务涉及更新的每一行,计算出每一行的 hash保存在writeset中,优点,①是有mysql主库写入binlog中,不需要解析 binlog 内容(event 里的行数据),节省计算量②binlog格式没要求,可以使用statement③无需扫描整个事务的binlog省内存,mysql5.7.22的多线程复制实现方式。
4、mysql5.7的多线程复制实现方式,借助于处于redo prepare到commit状态下的事务可以并行,因为执行器找引擎拿数据时,事务如果锁冲突会阻塞,无法到写redo log这一步,可以使用binlog故意延迟fsync,防止频繁写磁盘操作,不会丢失数据(redo prepar+完整的binlog事务才能提交,否则回滚),使其在备库多线程复制,主备延迟低,,但是这样有一点不好,语句的响应时间变长,感觉mysql官网故意延迟redo的fsync,在binlog write的时候(因为事务的binlog要写完整,时间较长),使其能批量提交,减少iops,感觉很巧妙

27| 主库出问题了,从库怎么办?

基于位点的主备切换

一种取同步位点的方法是这样的:

  1. 等待新主库 A’把中转日志(relay log)全部同步完成;
  2. 在 A’上执行 show master status 命令,得到当前 A’上最新的 File 和 Position;
  3. 取原主库 A 故障的时刻 T;
  4. 用 mysqlbinlog 工具解析 A’的 File,得到 T 时刻的位点。

从库 B 的同步线程就会报告 Duplicate entry ‘id_of_R’ for key ‘PRIMARY’ 错误,提示出现了主键冲突,然后停止同步。

通常情况下,我们在切换任务的时候,要先主动跳过这些错误,有两种常用的方法。一种做法是,主动跳过一个事务。跳过命令的写法是:

另外一种方式是,通过设置 slave_skip_errors 参数,直接设置跳过指定的错误。

GTID

GTID 的全称是 Global Transaction Identifier,也就是全局事务 ID,是一个事务在提交的时候生成的,是这个事务的唯一标识。它由两部分组成,格式是:

GTID=server_uuid:gno
  • server_uuid 是一个实例第一次启动时自动生成的,是一个全局唯一的值;
  • gno 是一个整数,初始值是 1,每次提交事务的时候分配给这个事务,并加 1。

基于 GTID 的主备切换

GTID 和在线 DDL

28 | 读写分离有那些坑?

但是,不论使用哪种架构,你都会碰到我们今天要讨论的问题:由于主从可能存在延迟,客户端执行完一个更新事务后马上发起查询,如果查询选择的是从库的话,就有可能读到刚刚的事务更新之前的状态。

这种“在从库上会读到系统的一个过期状态”的现象,在这篇文章里,我们暂且称之为“过期读”。

这里,我先把文章中涉及到的处理过期读的方案汇总在这里,以帮助你更好地理解和掌握全文的知识脉络。这些方案包括:

  • 强制走主库方案;
  • sleep 方案;
  • 判断主备无延迟方案;
  • 配合 semi-sync 方案;
  • 等主库位点方案;
  • 等 GTID 方案。

强制走主库方案

强制走主库方案其实就是,将查询请求做分类。通常情况下,我们可以将查询请求分为这么两类:

  1. 对于必须要拿到最新结果的请求,强制将其发到主库上。比如,在一个交易平台上,卖家发布商品以后,马上要返回主页面,看商品是否发布成功。那么,这个请求需要拿到最新的结果,就必须走主库。
  2. 对于可以读到旧数据的请求,才将其发到从库上。在这个交易平台上,买家来逛商铺页面,就算晚几秒看到最新发布的商品,也是可以接受的。那么,这类请求就可以走从库。

Sleep 方案

主库更新后,读从库之前先 sleep 一下。具体的方案就是,类似于执行一条 select sleep(1) 命令。

判断主备无延迟方案

所以第一种确保主备无延迟的方法是,每次从库执行查询请求前,先判断 seconds_behind_master 是否已经等于 0。如果还不等于 0 ,那就必须等到这个参数变为 0 才能执行查询请求。

第二种方法,对比位点确保主备无延迟:

第三种方法,对比 GTID 集合确保主备无延迟:

配合 semi-sync

要解决这个问题,就要引入半同步复制,也就是 semi-sync replication。

semi-sync 做了这样的设计:

  1. 事务提交的时候,主库把 binlog 发给从库;
  2. 从库收到 binlog 以后,发回给主库一个 ack,表示收到了;
  3. 主库收到这个 ack 以后,才能给客户端返回“事务完成”的确认。

等主库位点方案

GTID 方案

select wait_for_executed_gtid_set(gtid_set, 1);

等 GTID 的执行流程就变成了:

  1. trx1 事务更新完成后,从返回包直接获取这个事务的 GTID,记为 gtid1;
  2. 选定一个从库执行查询语句;
  3. 在从库上执行 select wait_for_executed_gtid_set(gtid1, 1);
  4. 如果返回值是 0,则在这个从库执行查询语句;
  5. 否则,到主库执行查询语句。

29 | 如何判断一个数据库是不是出问题了?

select 1 判断

实际上,select 1 成功返回,只能说明这个库的进程还在,并不能说明主库没问题。

并发连接和并发查询,并不是同一个概念。你在 show processlist 的结果里,看到的几千个连接,指的就是并发连接。而“当前正在执行”的语句,才是我们所说的并发查询。

并发连接数达到几千个影响并不大,就是多占一些内存而已。我们应该关注的是并发查询,因为并发查询太高才是 CPU 杀手。这也是为什么我们需要设置 innodb_thread_concurrency 参数的原因。

查表判断

mysql> select * from mysql.health_check; 

使用这个方法,我们可以检测出由于并发线程过多导致的数据库不可用的情况。但是,我们马上还会碰到下一个问题,即:空间满了以后,这种方法又会变得不好使。

更新判断

既然要更新,就要放个有意义的字段,常见做法是放一个 timestamp 字段,用来表示最后一次执行检测的时间。这条更新语句类似于:

mysql> update mysql.health_check set t_modified=now();

你一定会疑惑,更新语句,如果失败或者超时,就可以发起主备切换了,为什么还会有判定慢的问题呢?

其实,这里涉及到的是服务器 IO 资源分配的问题。

内部统计

30 | 答疑文章(二) : 用动态的观点看加锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rt60jyIO-1615554620612)(MySQL 实战45讲–笔记.assets/image-20210215094032796.png)]

不等号条件里的等值查询

等值查询的过程

怎么看死锁?

怎么看锁等待?

update 的例子

31 | 误删数据后除了跑路,还能怎么办?

总结下今天的知识点:
我觉得DBA的最核心的工作就是保证数据的完整性
今天老师也讲到了先要做好预防,预防的话大概是通过这几个点:
1.权限控制与分配(数据库和服务器权限)
2.制作操作规范
3.定期给开发进行培训
4.搭建延迟备库
5.做好sql审计,只要是对线上数据有更改操作的语句(DML和DDL)都需要进行审核
6.做好备份。备份的话又分为两个点.
(1)如果数据量比较大,用物理备份xtrabackup。定期对数据库进行全量备份,也可以做增量备份。
(2)如果数据量较少,用mysqldump或者mysqldumper。再利用binlog来恢复或者搭建主从的方式来恢复数据。
定期备份binlog文件也是很有必要的
还需要定期检查备份文件是否可用,如果真的发生了误操作,需要恢复数据的时候,发生备份文件不可用,那就更悲剧了

如果发生了数据删除的操作,又可以从以下几个点来恢复:
1.DML误操作语句造成数据不完整或者丢失。可以通过flashback,不过我们目前用的是美团的myflash,也是一个不错的工具,本质都差不多.都是先解析binlog event,然后在进行反转。把delete反转为insert,insert反转为delete,update前后image对调。所以必须设置binlog_format=row 和 binlog_row_image=full.
切记恢复数据的时候,应该先恢复到临时的实例,然后在恢复回主库上。
2.DDL语句误操作(truncate和drop),由于DDL语句不管binlog_format是row还是statement.在binlog里都只记录语句,不记录image所以恢复起来相对要麻烦得多。只能通过全量备份+应用binlog的方式来恢复数据。一旦数据量比较大,那么恢复时间就特别长,
对业务是个考验。所以就涉及到老师在第二讲提到的问题了,全量备份的周期怎么去选择

32 | 为什么还有kill不掉的语句?

收到kill以后,线程做什么?

实现上,当用户执行 kill query thread_id_B 时,MySQL 里处理 kill 命令的线程做了两件事:

  1. 把 session B 的运行状态改成 THD::KILL_QUERY(将变量 killed 赋值为 THD::KILL_QUERY);
  2. 给 session B 的执行线程发一个信号。

这时候,从 show processlist 结果上看也是 Command=Killed,需要等到终止逻辑完成,语句才算真正完成。这类情况,比较常见的场景有以下几种:

  1. 超大事务执行期间被 kill。这时候,回滚操作需要对事务执行期间生成的所有新数据版本做回收操作,耗时很长。大查询回滚。
  2. 如果查询过程中生成了比较大的临时文件,加上此时文件系统压力大,删除临时文件可能需要等待 IO 资源,导致耗时较长。
  3. DDL 命令执行到最后阶段,如果被 kill,需要删除中间过程的临时文件,也可能受 IO 资源影响耗时较久。

另外两个关于客户端的误解

第一个误解是:如果库里面的表特别多,连接就会很慢。

我们感知到的连接过程慢,其实并不是连接慢,也不是服务端慢,而是客户端慢。

–quick 是一个更容易引起误会的参数,也是关于客户端常见的一个误解。

MySQL 客户端发送请求后,接收服务端返回结果的方式有两种:

  1. 一种是本地缓存,也就是在本地开一片内存,先把结果存起来。如果你用 API 开发,对应的就是 mysql_store_result 方法。
  2. 另一种是不缓存,读一个处理一个。如果你用 API 开发,对应的就是 mysql_use_result 方法。

那你会说,既然这样,为什么要给这个参数取名叫作 quick 呢?这是因为使用这个参数可以达到以下三点效果

  • 第一点,就是前面提到的,跳过表名自动补全功能。
  • 第二点,mysql_store_result 需要申请本地内存来缓存查询结果,如果查询结果太大,会耗费较多的本地内存,可能会影响客户端本地机器的性能;
  • 第三点,是不会把执行命令记录到本地的命令历史文件。所以你看到了,–quick 参数的意思,是让客户端变得更快。

33 | 我查了那么多数据,会不会把数据库内存打爆?

全表扫描对 server 层的影响

实际上,服务端并不需要保存一个完整的结果集。取数据和发数据的流程是这样的:

  1. 获取一行,写到 net_buffer 中。这块内存的大小是由参数 net_buffer_length 定义的,默认是 16k。
  2. 重复获取行,直到 net_buffer 写满,调用网络接口发出去。
  3. 如果发送成功,就清空 net_buffer,然后继续取下一行,并写入 net_buffer。
  4. 如果发送函数返回 EAGAIN 或 WSAEWOULDBLOCK,就表示本地网络栈(socket send buffer)写满了,进入等待。直到网络栈重新可写,再继续发送。

MySQL 是“边读边发的”,这个概念很重要。这就意味着,如果客户端接收得慢,会导致 MySQL 服务端由于结果发不出去,这个事务的执行时间变长。

对于正常的线上业务来说,如果一个查询的返回结果不会很多的话,我都建议你使用 mysql_store_result 这个接口,直接把查询结果保存到本地内存。

实际上,一个查询语句的状态变化是这样的(注意:这里,我略去了其他无关的状态):

  • MySQL 查询语句进入执行阶段后,首先把状态设置成“Sending data”;
  • 然后,发送执行结果的列相关的信息(meta data) 给客户端;
  • 再继续执行语句的流程;
  • 执行完成后,把状态设置成空字符串。

全表扫描对 InnoDB 的影响

InnoDB 管理 Buffer Pool 的 LRU 算法,是用链表来实现的。

34 | 到底可不可以使用join?

Index Nested-Loop Join

  1. 对驱动表 t1 做了全表扫描,这个过程需要扫描 100 行;
  2. 而对于每一行 R,根据 a 字段去表 t2 查找,走的是树搜索过程。由于我们构造的数据都是一一对应的,因此每次的搜索过程都只扫描一行,也是总共扫描 100 行;
  3. 所以,整个执行流程,总扫描行数是 200。

怎么选择驱动表?

在这个 join 语句执行过程中,驱动表是走全表扫描,而被驱动表是走树搜索。

到这里小结一下,通过上面的分析我们得到了两个结论:

  1. 使用 join 语句,性能比强行拆成多个单表执行 SQL 语句的性能要好;
  2. 如果使用 join 语句的话,需要让小表做驱动表。

Simple Nested-Loop Join

Block Nested-Loop Join

这时候,被驱动表上没有可用的索引,算法的流程是这样的:

  1. 把表 t1 的数据读入线程内存 join_buffer 中,由于我们这个语句中写的是 select *,因此是把整个表 t1 放入了内存;
  2. 扫描表 t2,把表 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回。

35 | join语句怎么优化?

Multi-Range Read 优化

因为大多数的数据都是按照主键递增顺序插入得到的,所以我们可以认为,如果按照主键的递增顺序查询的话,对磁盘的读比较接近顺序读,能够提升读性能。

MRR 能够提升性能的核心在于,这条查询语句在索引 a 上做的是一个范围查询(也就是说,这是一个多值查询),可以得到足够多的主键 id。这样通过排序以后,再去主键索引查数据,才能体现出“顺序性”的优势。

Batched Key Access

理解了 MRR 性能提升的原理,我们就能理解 MySQL 在 5.6 版本后开始引入的 Batched Key Access(BKA) 算法了。这个 BKA 算法,其实就是对 NLJ 算法的优化。

BNL 算法的性能问题

大表 join 操作虽然对 IO 有影响,但是在语句执行结束后,对 IO 的影响也就结束了。但是,对 Buffer Pool 的影响就是持续性的,需要依靠后续的查询请求慢慢恢复内存命中率。

BNL 转 BKA

一些情况下,我们可以直接在被驱动表上建索引,这时就可以直接转成 BKA 算法了。

扩展 -hash join

这,也正是 MySQL 的优化器和执行器一直被诟病的一个原因:不支持哈希 join。并且,MySQL 官方的 roadmap,也是迟迟没有把这个优化排上议程。实际上,这个优化思路,我们可以自己实现在业务端。

36 | 为什么临时表可以重名?

临时表的特性

  1. 建表语法是 create temporary table …。
  2. 一个临时表只能被创建它的 session 访问,对其他线程不可见。所以,图中 session A 创建的临时表 t,对于 session B 就是不可见的。
  3. 临时表可以与普通表同名。
  4. session A 内有同名的临时表和普通表的时候,show create 语句,以及增删改查语句访问的是临时表。
  5. show tables 命令不显示临时表。

临时表的应用

由于不用担心线程之间的重名冲突,临时表经常会被用在复杂查询的优化过程中。其中,分库分表系统的跨库查询就是一个典型的使用场景。

为什么临时表可以重名?

MySQL 维护数据表,除了物理上要有文件外,内存里面也有一套机制区别不同的表,每个表都对应一个 table_def_key。

  • 一个普通表的 table_def_key 的值是由“库名 + 表名”得到的,所以如果你要在同一个库下创建两个同名的普通表,创建第二个表的过程中就会发现 table_def_key 已经存在了。
  • 而对于临时表,table_def_key 在“库名 + 表名”基础上,又加入了“server_id+thread_id”。

也就是说,session A 和 sessionB 创建的两个临时表 t1,它们的 table_def_key 不同,磁盘文件名也不同,因此可以并存。

临时表和主备复制

在 binlog_format='row’的时候,临时表的操作不记录到 binlog 中,也省去了不少麻烦,这也可以成为你选择 binlog_format 时的一个考虑因素。需要注意的是,我们上面说到的这种临时表,是用户自己创建的 ,也

37 | 什么时候会使用内部临时表?

通过今天这篇文章,我重点和你讲了 group by 的几种实现算法,从中可以总结一些使用的指导原则:

  1. 如果对 group by 语句的结果没有排序要求,要在语句后面加 order by null;
  2. 尽量让 group by 过程用上表的索引,确认方法是 explain 结果里没有 Using temporary 和 Using filesort;
  3. 如果 group by 需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大 tmp_table_size 参数,来避免用到磁盘临时表;
  4. 如果数据量实在太大,使用 SQL_BIG_RESULT 这个提示,来告诉优化器直接使用排序算法得到 group by 的结果。

38 | 都说InnoDB好,那还要不要使用Memory 引擎?

(内存表不支持事务)

内存表的数据组织结构

  • InnoDB 引擎把数据放在主键索引上,其他索引上保存的是主键 id。这种方式,我们称之为索引组织表(Index Organizied Table)。

  • 而 Memory 引擎采用的是把数据单独存放,索引上保存数据位置的数据组织形式,我们称之为堆组织表(Heap Organizied Table)。

从中我们可以看出,这两个引擎的一些典型不同:

  1. InnoDB 表的数据总是有序存放的,而内存表的数据就是按照写入顺序存放的;
  2. 当数据文件有空洞的时候,InnoDB 表在插入新数据的时候,为了保证数据有序性,只能在固定的位置写入新值,而内存表找到空位就可以插入新值;
  3. 数据位置发生变化的时候,InnoDB 表只需要修改主键索引,而内存表需要修改所有索引;
  4. InnoDB 表用主键索引查询时需要走一次索引查找,用普通索引查询的时候,需要走两次索引查找。而内存表没有这个区别,所有索引的“地位”都是相同的。
  5. InnoDB 支持变长数据类型,不同记录的长度可能不同;内存表不支持 Blob 和 Text 字段,并且即使定义了 varchar(N),实际也当作 char(N),也就是固定长度字符串来存储,因此内存表的每行数据长度相同。

hash 索引和 B-Tree 索引

内存表也是支 B-Tree 索引的。在 id 列上创建一个 B-Tree 索引

其实,一般在我们的印象中,内存表的优势是速度快,其中的一个原因就是 Memory 引擎支持 hash 索引。

但是,接下来我要跟你说明,为什么我不建议你在生产环境上使用内存表。这里的原因主要包括两个方面:

  1. 锁粒度问题;
  2. 数据持久化问题。

内存表的锁

我们先来说说内存表的锁粒度问题。

内存表不支持行锁,只支持表锁。因此,一张表只要有更新,就会堵住其他所有在这个表上的读写操作。

数据持久性问题

数据放在内存中,是内存表的优势,但也是一个劣势。因为,数据库重启的时候,所有的内存表都会被清空。

我们先看看 M-S 架构下,使用内存表存在的问题。

建议你把普通内存表都用 InnoDB 表来代替。但是,有一个场景却是例外的。

这个场景就是,我们在第 35 和 36 篇说到的用户临时表。在数据量可控,不会耗费过多内存的情况下,你可以考虑使用内存表。

内存临时表刚好可以无视内存表的两个不足,主要是下面的三个原因:

  1. 临时表不会被其他线程访问,没有并发性的问题;
  2. 临时表重启后也是需要删除的,清空数据这个问题不存在;
  3. 备库的临时表也不会影响主库的用户线程。

39 | 自增主键为什么不是连续的?

最喜欢这样的文章,以为比较简单和熟悉,也能打开一扇窗,让人看到一个不同的世界,并且无比丰富多彩。
在什么场景下自增主键可能不连续?
1:唯一键冲突
2:事务回滚
3:自增主键的批量申请
深层次原因是,不判断自增主键是否已存在和减少加锁的时间范围和粒度->为了更高的性能->自增主键不能回退->自增主键不连续
自增主键是怎么做的唯一性的?
自增值加1,自增锁控制并发
自增主键的生成性能如何?
这个需要测试一下,数据库的自增主键也用做生成唯一数字,作为其他单号,比如:并发量小的订单号,性能可能一般。
自增主键有最大值嘛?如果有,到了咋弄?
最大值应该有,因为数字总有个范围,到了当做字符串的一部分,然后再自增拼接上另一部分,貌似也可以。
自增主键的作用?保存机制?修改机制?
作用:让主键索引尽量地保持递增顺序插入,避免页分裂,使索引更紧凑。
保存机制:不同的存储引擎不一样。
MyISAM 引擎的自增值保存在数据文件中。
InnoDB 引擎的自增值,先是保存在了内存里,到了 MySQL 8.0 版本后,才有了“自增值持久化”的能力,放在了redolog里。
修改机制:
在 MySQL 里面,如果字段 id 被定义为 AUTO_INCREMENT,在插入一行数据的时候,自增值的行为如下:
1:如果插入数据时 id 字段指定为 0、null 或未指定值,那么就把这个表当前的 AUTO_INCREMENT 值填到自增字段;
2:如果插入数据时 id 字段指定了具体的值,就直接使用语句里指定的值。

根据要插入的值和当前自增值的大小关系,自增值的变更结果也会有所不同。假设,某次要插入的值是 X,当前的自增值是 Y。
1:如果 X 2:如果 X≥Y,就需要把当前自增值修改为新的自增值。

40 | insert语句的锁为什么这么多?

insert … select 语句

insert 循环写入

当然了,执行 insert … select 的时候,对目标表也不是锁全表,而是只锁住需要访问的资源。

insert 唯一键冲突

insert into … on duplicate key update

insert into … on duplicate key update 这个语义的逻辑是,插入一行数据,如果碰到唯一键约束,就执行后面的更新语句。

小结

今天这篇文章,我和你介绍了几种特殊情况下的 insert 语句。

insert … select 是很常见的在两个表之间拷贝数据的方法。你需要注意,在可重复读隔离级别下,这个语句会给 select 的表里扫描到的记录和间隙加读锁。

而如果 insert 和 select 的对象是同一个表,则有可能会造成循环写入。这种情况下,我们需要引入用户临时表来做优化。

insert 语句如果出现唯一键冲突,会在冲突的唯一值上加共享的 next-key lock(S 锁)。因此,碰到由于唯一键约束导致报错后,要尽快提交或回滚事务,避免加锁时间过长。

41 | 怎么最快地复制一张表?

今天这篇文章,我和你介绍了三种将一个表的数据导入到另外一个表中的方法。

我们来对比一下这三种方法的优缺点。

  1. 物理拷贝的方式速度最快,尤其对于大表拷贝来说是最快的方法。如果出现误删表的情况,用备份恢复出误删之前的临时库,然后再把临时库中的表拷贝到生产库上,是恢复数据最快的方法。但是,这种方法的使用也有一定的局限性:
    • 必须是全表拷贝,不能只拷贝部分数据;
    • 需要到服务器上拷贝数据,在用户无法登录数据库主机的场景下无法使用;
    • 由于是通过拷贝物理文件实现的,源表和目标表都是使用 InnoDB 引擎时才能使用。
  2. 用 mysqldump 生成包含 INSERT 语句文件的方法,可以在 where 参数增加过滤条件,来实现只导出部分数据。这个方式的不足之一是,不能使用 join 这种比较复杂的 where 条件写法。用 select … into outfile 的方法是最灵活的,支持所有的 SQL 写法。
  3. 但,这个方法的缺点之一就是,每次只能导出一张表的数据,而且表结构也需要另外的语句单独备份。

表当前的 AUTO_INCREMENT 值填到自增字段;
2:如果插入数据时 id 字段指定了具体的值,就直接使用语句里指定的值。

根据要插入的值和当前自增值的大小关系,自增值的变更结果也会有所不同。假设,某次要插入的值是 X,当前的自增值是 Y。
1:如果 X 2:如果 X≥Y,就需要把当前自增值修改为新的自增值。

40 | insert语句的锁为什么这么多?

insert … select 语句

insert 循环写入

当然了,执行 insert … select 的时候,对目标表也不是锁全表,而是只锁住需要访问的资源。

insert 唯一键冲突

insert into … on duplicate key update

insert into … on duplicate key update 这个语义的逻辑是,插入一行数据,如果碰到唯一键约束,就执行后面的更新语句。

小结

今天这篇文章,我和你介绍了几种特殊情况下的 insert 语句。

insert … select 是很常见的在两个表之间拷贝数据的方法。你需要注意,在可重复读隔离级别下,这个语句会给 select 的表里扫描到的记录和间隙加读锁。

而如果 insert 和 select 的对象是同一个表,则有可能会造成循环写入。这种情况下,我们需要引入用户临时表来做优化。

insert 语句如果出现唯一键冲突,会在冲突的唯一值上加共享的 next-key lock(S 锁)。因此,碰到由于唯一键约束导致报错后,要尽快提交或回滚事务,避免加锁时间过长。

41 | 怎么最快地复制一张表?

今天这篇文章,我和你介绍了三种将一个表的数据导入到另外一个表中的方法。

我们来对比一下这三种方法的优缺点。

  1. 物理拷贝的方式速度最快,尤其对于大表拷贝来说是最快的方法。如果出现误删表的情况,用备份恢复出误删之前的临时库,然后再把临时库中的表拷贝到生产库上,是恢复数据最快的方法。但是,这种方法的使用也有一定的局限性:
    • 必须是全表拷贝,不能只拷贝部分数据;
    • 需要到服务器上拷贝数据,在用户无法登录数据库主机的场景下无法使用;
    • 由于是通过拷贝物理文件实现的,源表和目标表都是使用 InnoDB 引擎时才能使用。
  2. 用 mysqldump 生成包含 INSERT 语句文件的方法,可以在 where 参数增加过滤条件,来实现只导出部分数据。这个方式的不足之一是,不能使用 join 这种比较复杂的 where 条件写法。用 select … into outfile 的方法是最灵活的,支持所有的 SQL 写法。
  3. 但,这个方法的缺点之一就是,每次只能导出一张表的数据,而且表结构也需要另外的语句单独备份。

你可能感兴趣的:(#,【SQL语句】)