什么是 Buffer Pool ?

MySQL 数据是存放在磁盘中的,如果每次操作都是对磁盘进行,那么肯定很影响性能。所以一定是先把数据从磁盘中取出来,放在内存中,再在内存中操作。 这一块区域就是 buffer pool。它由许多的数据缓存页和控制块组成。默认大小时 128M,每页大小是 16k,控制块为数据页的 5%。实际应用中,对数据的增删改 都是在 buffer pool 中操作,然后再由 IO 线程写入磁盘。同时为了避免数据库崩溃等异常情况,导致未成功写入磁盘,引入了 redo 日志,记录了之前做过的增删改操作, 重启后,读取文件内操作,重新执行一遍,即可恢复。当然,对于数据更新过程,还有一套严格的步骤,涉及到 undolog、binlog、提交事务、buffer pool 占数据刷回磁盘等。 通过这种方式,保证在内存中操作,然后顺序写回磁盘,提高性能。(内存速度高于磁盘,顺序写高于随机读写)

工作流程为:在查询的时候,先在 buffer pool 中查询对应的数据页,如果没有从磁盘读取,存在 buffer pool 中,下次查询直接从 buffer pool 中获取。

不同于查询缓存,查询缓存是以 key-value 的形式,存储相同查询 sql 的结果。buffer pool 是属于引擎层的。

buffer pool 是以页为单位进行存储的,每个缓存页都对应一个控制块,里面包含数据页所属的表空间、编号以及在 buffer pool 中的地址。

buffer pool 中页分三种, 空闲页、干净页、脏页,空闲页是未被使用的页,干净页是指内存的数据和磁盘数据一致,脏页是指内存中的数据已被修改,还没有刷回磁盘,当刷回磁盘后变为干净页。

InnoDB 如何管理 Page 页?

基于链表 + LRU + 冷热分离来管理的。

MySQL 为什么要改进 LRU 算法?

LRU 是指最近最少使用算法,原理就是将使用的元素放在队列头部,这样尾部即为最少使用的元素,当队列满了,淘汰最末尾的即可。 但是在 MySQL 使用中会出现 2 个比较常见的问题:

  • 第一个是预读失效,所谓预读就是在读取一部分数据后,其相邻的数据也有可能下次被访问,所以可以提前预读提高性能,但是并不一定会被读取,所以预读后,导致淘汰了页,但是淘汰的页却是需要被访问的。

  • 第二个是缓存池污染,当需要读取大量的页时,会同时加入多个页,淘汰已经存储的页,同样是不合理的,这就是缓存池污染,会导致 MySQL 性能下降。

MySQL 因此改进了 LRU 算法,将其分为2个区域,热数据区和冷数据区,当加载页时,先放在冷数据区,1s 内被再次访问就移动至热数据区,时间可设置。 为何设置成1s,是因为 1s 内如果数据不在被访问,那么再次被访问的几率就不大了。时间可以调整。

使用索引一定可以提高效率吗?

视情况而定,有好处也有坏处。

优点: - 提高检索效率,降低数据库IO成本 - 通过对索引列进行排序,降低数据排序的成本,降低cpu的消耗 缺点: - 创建和维护索引需要耗费时间,随数据量增加而增加 - 需要占用物理空间 - 数据进行增加、修改、 删除,索引也需要维护 创建索引的原则: - 在经常需要搜索的列创建索引 - 主键、外键 - 范围搜索 - 排序 不建或少建索引: - 用不到的字段查询 - 表记录比较少 - 经常需要修改的字段 - 参与列计算的 - 区分度不高的字段

介绍一下 Page 页的结构

  • File Header: 文件头,描述页的信息

  • Page Header: 页头,页的状态信息

  • Infimum/Supermum: 最小最大记录

  • User Records: 数据行

  • Free Space: 空闲空间,页中未被使用的空间

  • Page Directory: 页目录,存储用户记录的相对位置

  • File Trailer: 文件尾,校验页是否完整

说以下聚簇索引和非聚簇索引

聚簇索引,索引和数据在一起,非聚簇索引,索引和数据分开存储,索引的叶子节点指向了数据对应的位置

主键采用聚簇索引,采用B+数结构,叶子节点就是行记录。

聚簇索引的优点 - 范围查找 - 查找目标数据,不需要再次io回表查询 - 覆盖索引

缺点 - 插入速度严重依赖插入顺序 - 更新主键的代价很高,因为会导致行移动 - 二级索引访问,需要回表查询

索引有那几种类型

  • 普通索引 没有任何限制

  • 唯一索引 字段唯一,允许控制

  • 主键索引,只能有一个,不允许控制

  • 复合索引 可以在多个列创建索引

  • 全文索引大数据量检索,使用全文索引会比like快很多,字符串、文本字段。

介绍一下最佳左前缀法则

根据联合索引最左边的字段进行排序,然后再按照第二个排序,依次…​…​…​

什么是索引下推

没有索引下推时,会根据索引来查找记录,然后根据 where 进行数据过滤,会造成多次回表, 使用索引下推后,在取出索引的同时,判断是否可以根据where条件进行数据过滤,减少回表次数,提高性能。

什么是自适应哈希索引

InnoDB 引擎根据查询统计发现某一查询满足hash索引的数据结构特点并且达到一定次数,就建立一个hash索引,底层是散列表,存在于缓存中,只适合搜索等值的查询,范围查询是不能使用的。 其目的减少b+树路径搜索,可以根据索引快速定位到叶子节点。

为什么 like 以 % 开头索引会失效

索引的创建是有序的,如果首字母无法确认,也即%在左侧,这意味着所有的索引都要进行判断,如果在右侧,则可以根据首字母从左到右依次比较,可以过滤一部分数据,是可以用到索引的。 当然还有一种特殊情况,如果查询的字段中都在组合索引中,那么也会走索引,也是全部扫描,但是组合索引由于不会存储完整的数据行,那么检索效率还是高于全表扫描的。

自增还是UUID

  1. 自增长度小

  2. 占用空间小

  3. 容易排序

  4. 容易暴露业务量

  5. 并发高,竞争自增锁会增加数据库吞吐能力

  6. 数据迁移或分库分表场景,不再适用

UUID - 不会冲突,数据拆分合并,都能保证主键全局唯一性 - 可以在应用层生成,提高数据库吞吐能力 - 影响插入速度,与自增相比,最大缺陷就是随机io,新增的uuid,值不一定比以前的大,无法按顺序放在数据后,涉及到插入旧的数据之间,造成数据移动,影响性能。 - 会消耗更多的存储空间,操作比整型慢

推荐还是使用自增。

InnoDB 和 MyISAM 的区别

  • 事务和外键

  • 锁机制

  • 索引结构

  • 并发处理能力

  • 存储文件

B 树和 B+ 树的区别

B树 - 每个节点包含索引和数据,如果数据量过多,会导致树的高度增加,增加磁盘io

B+树 - 一个节点有多个元素 - 叶子节点都冗余了非叶子节点数,有数据冗余 - 叶子节点有单项指针

为什么采用 B+ 树而不是 B 树

B+ 树在提高IO性能的同时还能解决 B 树遍历效率低的问题。B+ 树只要遍历叶子节点即可实现整颗树的遍历。

Innodb中的 B+ 树是怎么产生的?

MySQL 以页为单位进行读取存储。页包含双向指针、页目录以及用户数据区域。页目录分组缩小查询范围、提高查询效率,以空间换取时间。

一个B+树大概能存放多少条索引记录

show global status like 'Innodb_page_size' - 16384 , 16kb

MySQL设计者将 B+ Tree 的节点大小设置为一个页,这样只需要一次 io 就能全载入,每页大小16k,所以节点也是 16k,B+Tree的根节点保存在内存中,子节点才是存储在磁盘中。

计算公式: 根节点指针数 * 单个叶子节点的记录行数

  • 根节点指针数: 假设用的int,占用4个字节,指针6个字节,那么一个节点就可以存储 1638/(4+6) = 1638 个索引指针

  • 计算每个叶子节点记录数,假设每行1k,那么一页就可以存储 16/1 行数据

  • 一颗高度为2的树可以存放的记录数是 1638 * 16 = 26208, 同理,每增加一层高度,就乘以 1638即可。

explain

  • 表的读取顺序

  • 那些索引可以被使用

  • 那些索引真正被使用

  • 表的直接引用

  • 每张表有多少行被优化器查询了

id:操作顺序 select_type: 查询类型 table:查询的表 partitions:分区 type: 表示连接类型,以什么样的方式来获取数据,简化后的从好到坏 system const eq_ref ref rang index all possible_keys: 可能使用到的索引 key:使用的索引 key_len: ref: rows: 读取行数 filtered:过滤行数 extra: 额外信息

type: - system 不进行磁盘id,查询系统表,仅返回一条数据 - const 查询主键索引,返回 1 条或 0 条数据,属于精确查找 - eq_ref 查找唯一性索引,返回数据最多一条,属于精确查找 - ref 查询非唯一性索引,返回多条数据,属于精确查找 - range 查找某个索引的部分索引,只检索给定范围的行 - index 查找所有索引树,比all快 - all 不使用任何索引,全表扫描

extra:

  • using filesort: 无法利用索引进行排序

  • using index: 直接通过索引就能获取到数据,也即覆盖索引,不需要回表

  • using index condition: 使用了索引,但是部分条件无法使用索引,会先用索引匹配一遍,在去匹配无法使用索引的条件

  • using join buffer: 使用了连接缓存 join 连接查询时,join 字段非索引字段

  • using temporary: 使用了临时表,来进行存储结果,常用于排序和分组查询

  • using where: 全表扫描

如何进行分页查询优化

一般性分页,使用 limit offset,rows, 第一个是偏移量,0 开始,rows 表示返回的行数

  • 使用覆盖索引

  • 利用子查询,先定位偏移位置id,然后 id 值向后查询 select * from t5 where id>=(select id from t5 order by text limit 1000000, 1) limit 10;

  • 延迟关联,可以先用join进行分页,然后回表查询数据

如何做慢查询优化

开启慢查询日志: - slow_query_log 开启还是关闭 默认关闭 - slow-query-log-file 日志储存路径 - long-query-time 慢查询阈值,大于此时间就是慢查询

性能下降原因: - 等待时间长,锁表导致 - 执行时间长,查询语句烂,索引失效,join太多,服务器参数配置

慢查询优化思路 - 优先选择高并发的sql - 定位性能优化的性能瓶颈, io数据访问,看是否用到了索引,cpu,看数据预算是否花费了太多时间,网络带宽 - 明确优化目标,确定优化程度用户体验好 - 通过 explain 入手,明确 sql 的执行状态 - 永远用小的结果集驱动大的结果集,获取连接次数少,减少资源消耗 - 尽可能在索引中完成排序 - 只获取自己需要的列 - 只使用最有效的过滤条件,最短路径获取数据 - 避免复杂的 join 和子查询,建议不超过 3 张表,将复杂的 sql 拆分成小 sql,通过代码层进行封装 - 合理设计索引并利用

Hash 索引的优缺点

  • 没有 hash 冲突的情况,查询数据比较快,如果有,则按链表进行逐行比较

  • 只存储hash值和行指针,不存储数据

  • 只支持等值比较查询

  • 无法用于排序,范围查找

说一下 InnoDB 内存相关的参数优化

Buffer Pool, 增大值,可减少对磁盘的 io 操作,在有大量事务进行更新、删除、插入时,可有效提高性能。

增加依据: 根据命中率,低于90,则可以考虑增加缓冲池大小。缓存查询次数 / (缓存查询次数 + 磁盘查询次数) * 100

InnoDB 日志相关参数优化

  • 默认16M

  • 有过有 text/blog 大字段,

  • innodb_lob_buffer_size 缓存区大小

  • innodb_log_file_size 日志文件大小

  • 设置过小,会导致频发触发检查,刷新脏页到磁盘,增加 io 次数,影响性能

  • 设置过大,如果宕机,恢复时间过长

  • 官网建议最少要承载一个小时业务日志量

  • 可以通过抓取一段时间内的 log sequence number,这是一个全局参数(MySQL设计)

  • pager 分页工具获取一分钟获取的 log sequence number,在判断一个小时即可

  • 存在业务量大小负载,导致算出比较大、比较小,实际还需要集合业务经验判断。

InnoDB io线程相关的参数优化?

  • 将磁盘 io 转为内存查询

  • query_cache_size&have_query_cache 查询缓存

什么是写失效?

数据库一页 16k,操作系统一页 4k,需要分 4 次写入,如果只写了一部分,那么就会造成数据丢失。

  • 双写缓冲

  • 先写 DoubleWriter Buffer 内存缓存

  • 写入共享表空间 2m,连续的内存空间,如果下一步失败,从这里进行恢复

  • 再写入数据文件中

如何进行join优化

  • 永远用小结果的表驱动大结果,本质是减少外层循环数据量

  • 为匹配的条件增加索引,减少内层表的循环匹配次数

  • 增大 join buffer size 的大小,缓存数据越多,内层表扫描次数就越少

  • 减少不必要的字段查询。

索引那些情况会失效

  • 查询条件包含 or

  • 隐式类型转换

  • 左 like

  • 联合索引,违反最左匹配原则

  • 对索引进行函数运算

  • 多索引字段使用 != <> not in

  • 索引使用 is null, is not null, 可能会失效

  • mysql 估计使用全表扫描会比使用索引快,则不适用索引。

什么是覆盖索引

获取的数据都在索引上,不需要回表

MySQL 事务的特性

  • 原子性 事务作为一个整体被执行,那要全部执行,那么全部不执行,实现回滚是 undo log。

  • 一致性 事务应确保数据库状态从一个一致性状态转变为另一个一致性状态,一致性是数据库中的数据应满足完整性约束

  • 隔离性 一个事务的执行,不能被其他事务影响,有是个隔离级别,从低到高一次 读未提交,读已提交,可重复读、串行化

  • 持久性 一个事务一旦提交,在数据库中的改变是永久性的,后续操作或故障都不会受影响,丢失,主要靠 redo log

事务的持久化是为了应对系统崩溃造成的数据丢失,只有保证了事务的一致性,才能保证执行结果的正确性。

MySQL 可重复读是怎么实现的?

MVCC 多版本控制器,核心就是 Undo log 多版本链 加 Read view, 通过 undo log 来保存数据的历史版本,实现多版本管理。CC 是通过 read view 来实现管理的,通过read view 原则来觉得数据是否显示。针对不同的隔离级别,read view 的生成策略不同,也就实现了不同的隔离级别。

Undo log 多版本链,每条数据都有两个隐藏字段 - trx_id: 事务id,记录最近一次更新这条数据的事务id - roll_pointer: 回滚指针,指向之前生成的 undo log

Read view: 将某个时刻的数据记录下来,查询时从 undo log 最新一条记录进行对比,如果不符合比较规则,则回滚到上一条记录进行对比,直到得到符合比较条件的查询结果

  • m_ids: 那些事务正在执行,没有提交

  • min_trx_id:

  • max_trx_id:

  • creator_trx_id:

当一个事务查询sql时,会生成一致性视图 read-view, 按照规则来查找合适的数据

规则 - trx_id < min_id: 事务已提交,可读 - trx_id > max_id: 将来事务生成的,不可读 - 中间情况,分,当前在未提交的事务中,那么不可读,否则可读。

Repeatable Read 解决了幻读问题嘛?

普通的查询时快照读,是不会看到别的事务插入的数据的, 只有在当前读时才会出现,例如 select …​ for update 语句。 当前读读取的是数据库最新的数据。

数据库锁的种类

按操作类型:分读锁和写锁 按操作粒度分:表级锁、行级锁、页面锁 开销、速度、粒度、死锁、冲突概率、并发读 按操作性能: 乐观锁(数据版本比较,提交时检查)、悲观锁(提前锁定,避免其他人操作)

请说下共享锁和排他锁

行级锁包含共享锁和排他锁,必须在使用 InnoDB 引擎以及开始事务隔离级别为可重复读。

共享锁,当事务对数据加上共享锁后,其他用户可以并发读取数据,但不能进行修改,直到释放 排他锁,当事务加排他锁后,其他事务不能加任何类型的锁,获取锁,既可以读,也可以写

一般情况,insert update delete 都是默认加排他锁,select 默认不加锁,但是可以显示的镜像加共享锁和排他锁,lock in share mode, for update

共享锁兼容共享锁,其他组合都不兼容。

InnoDB 的行锁时怎么实现的?

通过对索引数据页上的记录加锁的,主要算法有3种: record lock/ gap lock / next-key lock

  • readlock 锁定当个行的记录

  • gap lock 间隙锁,锁定索引记录的间隙,确保索引记录的间隙不变

  • next-key lock 记录锁和间隙锁的组合,同时锁住数据以及数据前后范围 注:只有在索引上检索数据才会使用行级锁,否则会使用表锁

在可重复读的隔离级别下,先采用 next-key lock,当操作含有唯一所有时,会进行降级,recordlock,仅锁住索引本身而非范围

并发事务会产生那些问题?

  • 更新丢失 多个事务同事更新一行记录

  • 脏读 一个事务读取到另一个事务修改但是未提交的数据

  • 不可重复读

  • 幻读

mvcc 内部细节

数据库为了高并发的数据访问,对数据进行了多版本处理,并通过事务的可见性来保证事务看到自己应该看到的数据版本,只在 Read Commited,Repeatable Read 下工作

行记录有三个隐藏字段,db_row_id/db_trx_id/db_roll_ptr - 没有显示定义主键,也没有定义唯一索引,会自动添加一个 row_id 隐藏列作为主键 - 事务进行增删改时,会将这个事务 id 插入到 trx_id 中 - roll_ptr: 回滚指针,指向 undo log

undo log 多版本链 read view

读已提交,每次都会生成一个 readview,导致每次都读到不同的数据 可重复读,一个事务中,只有第一次生成,可以解决不可重复读问题。

说一下 MySQL 死锁的原因和处理方法

表锁,一个事务锁住了a表,然后访问b表,此时b被另一个事务锁住,同时需要访问a表,互相等待释放,造成死锁,调整程序操作逻辑

行级锁 - 全表扫描,行级锁上升为表锁,多个事务执行后,很容易产生死锁和阻塞,解决办法就是 explain 分析,处理全表扫描 sql,建立索引优化 - 两个事务想拿到对方持有的锁,互相等待,产生死锁。对索引加锁的顺序不一致导致,如果可以,尽量以相同的属性来访问索引记录,在程序批量处理数据时,如果对数据进行排序,以固定的顺序来处理记录,可降低死锁可能

MySQL 体系结构

  • 客户端连接器

  • 连接池、sql 接口、解析器、查询优化器、缓存

  • 存储引擎层,可插拔

  • 系统文件层、 文件和日志

undo log、redo log、bin log

undo log 用于回滚的日志,更新之前会记录在这个里面,回滚时通过这个来回滚,在事务开始前产生,事务提交后,不会立即删除,放入待删除列表,交给后台线程处理。 - 提供回滚操作,记录回滚时需要操作的sql记录 - 提供多版本控制 mvcc

redo log:用于数据库持久性,事务提交成功,那么数据库修改就永久保存下来。 如果每执行成功一个事务,就将数据写入磁盘,会有严重的性能问题。因此设计了 redo log,来记录事务对那些数据页做了修改,就能解决性能问题,文件更小而且是顺序IO。 先将记录写入 redo log buffer,后续将多个操作写入redo log file。当发生故障、崩溃后,重启服务,可以通过此文件进行恢复。

bin log 用于记录数据库执行的写入性操作,不包含查询,以二进制的形式保持在磁盘中。 - 主从复制 - 数据恢复

说一下 redo log 和 undo log 的持久化策略

  • redo log 是 Innode 引擎独有的,binlog 是服务 server层的,其他层都可以用

  • redo logs 是物理记录,记录修改内容,binlog 是逻辑记录,记录 sql

  • redo log 是循环写,bin log 是追加写,不会覆盖以前的日志

  • redo 是事务自动恢复使用,binlog 用于主从复制以及数据恢复

MySQL 在线修改大表影响

  • 时间不可预估,一般时间长

  • 表级锁,影响写入操作

  • 中断操作,会还原,会锁表

  • 修改大表结构会导致cpu,io等性能消耗,使服务器性能降低

  • 会导致主动延迟,影响业务读取

  • 对表进行加锁

  • 复制原表物理结构

  • 修改表物理结构

  • 将原表数据导入中间表

  • 锁定中间表,删除原表

  • 改名中间表

  • 释放锁

count(列名) count(1) count(*) 区别

  • count(*) 和count(1)不会忽略null数据,count(列名) 会忽略 null 数据

  • 执行效率, innodb 通过遍历最小的二级索引来处理 count(*)和count(1),性能是相同的,大于 count 列名

什么时候进行分库分表?

将同一个数据库中的数据分散存放到多个数据库上,以达到分散单台设备负载效果。主要解决因数据量过大导致数据库性能降低。

  • 单机容量达到瓶颈

  • 连接达到上限

说说 Mysql 的主从复制

  • 实时灾备,用于故障切换

  • 备份

  • 读写分离

是通过将主服务器的 binlog 负责到从服务器,然后执行。从服务器会启动一个 io 线程读取日志,以及一个执行线程,执行 binlog

sql执行流程

客户端发送sql请求 -》查询缓存 -》 解析器-》解析树-》预处理器-》新解析树-》查询优化器-》执行计划-》查询执行引擎-》返回结果

Mysql 支持缓存查询嘛?

5.7 支持,8.0 之后废弃

  • 管理不灵活

  • sql 必须完全一致才会 cache 命中

  • 结果太大不会缓存

  • 分库分表不起作用

  • 有自定义函数、触发器,不起作用

  • 表结果、数据变化是,关联 cache 全部失效

可在应用层进行缓存,例如 redis encached 等等。

ACID 靠什么保证?

A: 原子性由 undo log 保证,他记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql C:一致性由其他三大特性保证,程序代码要保证业务上的一致性 I: 隔离性由 MVCC 来保证 D:持久性由内存 + redo log 来保证,宕机可以从 redo log 恢复。

MySQL 主从同步原理

Master 一个线程,binlog dump thread, 当 binlog 日志有变动时,线程读取内容发送给从节点。 从节点 IO 线程接收 binlog 内容,写入 relay log 文件中,从节点SQL线程读取 relay log 来进行重放。 默认采用异步负责,主库发送日志,不关心从库是否处理。存在丢失问题、但是性能高。可以选择全同步复制,必须等待从库响应才结束。 还有一种折中方式半同步复制,一个节点确认没问题,即可视为成功,其他节点就不考虑了。