617 MySQL 数据排序 实现?
- Order By 命中索引(包括索引字段),使用索引排序(⭐有序⭐),效率最高效
- 否则使用文件排序,文件少=> 内存排序 sort_buffer
- 文件大=>外部排序,归并排序
内部排序细节:
双路排序(待排序的列数据太大了):使用row_id(回查表) + sort_field
排好序后,使用row_id将完整的记录取出来
单路排序(待排序数据大小能接受)
直接拍,不会查表,直接把拍好的结果返回
外部排序:
拆分小的,外部多路归并排序,小=>大
外部归并排序 => 先分段排序,每一段调入内存执行快排
=> 归并阶段,因为每子段都是有序的 => 多路归并排序
589 一条SQL的执行过程
- 先通过连接器校验权限
- 利用分析器进行SQL语句词法分析和语法分析,构建解析树
- 利用优化器选择合适的索引和表连接顺序,最终选择一条最佳的执行计划
- 利用执行器,调用引擎层查询数据,返回结果集
具体:Select * from user where id = 1;
-
SQL => Server层连接器,权限校验,账号是否有资格获取。无=> Access denied for user。 连接成功后,空闲一段时间会断开
-
分析器(查询解析) =>
- 语法分析:SQL : Select类型✔️ user表✔️ id列 ✔️拆分成词,再组装为解析树。
- 语义分析:语法是否有误 => you have an error in your SQL syntax (字段、表|存在?) 分析解析树语法正确性
-
优化器(查询优化)=>
- 优化SQL,比如:选择哪个索引、调整多个表的连接顺序
-
执行器(查询执行)
- 校验用户对表的权限,根据存储引擎查询数据,遍历
=> 返回结果集
590 MySQL 存储引擎
-
InnoDB
-
提供事务、行级锁和外键
-
提供高并发性能
-
B+树索引
-
MVCC
-
591 MySQL的索引类型有哪些
数据结构角度:
- B+树索引:
- 通过树型结构 => 适合范围查询between和精确查=。
- 叶子节点双向链表连接
- 比B树具备更少的IO次数
InnoDB+树索引结构上看
- 聚簇索引(主键构建):
- 非叶子节点存储索引值
- 叶子节点存储完整的数据行数据,可以直接访问完整数据
- 每个表只能有一个聚簇索引
- 非聚簇索引 (二级索引)
- 非叶子节点存储索引值
- 叶子节点存储主键和对应索引列,查询非对应索引列,则需要回表(根据主键),增加额外的IO(SELELCT * 慎重)
索引:
-
主键索引
-
唯一索引
-
普通索引
-
单列索引 & 联合索引
1主键:
2PRIMARY Key(id)
3唯一:
4Create unique index xxx on user(username);
5联合:
6Create INdex xxx on user(username, email);
MySQL索引的最左前缀匹配原则?
针对联合索引
联合索引在B+树种的排列方式遵循“从左到右”的顺序 e.g. (first_name, last_name, age) 会按照 这个顺序进行索引
1 (Alice, Black, 35)
2 |
3 -------------------------------------------------------------
4 | | | |
5(Alice, Brown, 25) (Alice, Smith, 30) (Bob, Smith, 20) (Bob, White, 19)
查询时候WHERE 条件顺序最好和索引一致,否则跳过最左侧会导致无法利用该索引
=> 要保证索引命中,建议索引顺序一致
-
不能跳过第一个索引
-
可以部分匹配但第一个一定要在
index: (a, b, c)
1where a=1 ✔️
2where a=1 and b=1 ✔️
3where a=1 and b=1 and c=1✔️
4
5where b=1 and a=1 ❌❌❌
595 MySQL索引覆盖
指二级索引包含了查询所需的所有字段 - 一级索引(Primary key,唯一) ˈpraɪməri - 二级索引(其他索引)
1# INDEX idx_name_hiredate (last_name, first_name, hire_date)
2
3SELECT ...
4WHERE last_name = ? AND first_name = ?
5
6则不需要回表查询,因为这个SQL会根据联合索引进行查询,并且查询的条件列包含所需的数据
596 MySQL索引下推
应用在联合索引上,减少回表查询。MySQL自动应用的优化技术
主要是将条件索引列的过滤操作下推到存储引擎层,减少不必要的数据传输
通过二级索引查到主键id后回表再进行where条件过滤
=> 二级索引查到数据后直接where条件过滤一遍,再进行回表查询,减少回表的次数
1# example 联合索引:index_age_grade
2(1, 'Alice', 18, 90),
3(2, 'Bob', 19, 85),
4(3, 'Charlie', 18, 95),
5(4, 'David', 20, 80);
6=> SELECT ... WHERE age = 18 and grade > 90
7# 没有索引下推:
81. 先根据age查找到所有结果
92. 返回所有结果数据到服务器层
103. 服务器层过滤grade>90的数据
11
12# 索引下推
131. 先根据age查找到所有结果
142. 存储引擎层直接过滤grade>90的数据
153. 返回数据给服务器层
597 建立索引注意事项
- 不能盲目建立,因为维护需要代价
- 表的修改频率远大于查询频率 => 维护代价大
598 排查索引效果?
在SQL语句前,使用EXPLAIN关键字,查看SQL语句的执行信息
-
type:all, index, range, …, constanst, system
-
key: 使用的索引,如果未使用,那么为null
-
rows:扫描的行数
联合查询的时候:
满足最左匹配原则查询某个记录时,那么type index ref const. 对应的rows只有一行, key标识使用的索引是哪些;
如果不满足满足最左匹配原则查询某个记录时, (并且查询的字段符合覆盖索引),拿可能就是全盘扫描二级索引对应的 B+树。 type 也是index 但是 ref为null , key标识使用的索引.
如果不满足覆盖索引的话呢,可能就是走聚簇索引, type 为all,rows也非常多,key为null。慢sql。
索引失效:
-
索引中使用运算或者函数
-
like进行全盘扫描,对值进行了处理,则会使索引失效
-
破坏最左匹配
-
in字段 范围过大
-
order by 未使用索引
1 全盘扫描 2 索引失败
600 MySQL的B+树查询数据的全过程
自平衡多路查找树
- 查询从跟节点出发,比较数据键值与节点中存储的索引键值,确定数据落在哪个区间,从而确定往下哪个分支走,从上到下定位到叶子节点
- 叶子节点存储着数据行记录,但一页的大小有16kb,存储的数据行不只一条
- 叶子节点中的数据行以组的形式划分,利用页目录结构,通过二分查找定位到对应的组
- 定位到组后,利用链表遍历=>row
页数据,包括页目录和对应的数据(顺序存放的),页目录有多个槽,并且数据是分组的,每个槽指向组内的开头的记录行,这样我们可以
利用二分查找Ologn复杂度查找对应的槽,然后顺序的遍历找到对应的数据。
非命中索引的话,因为B+树,叶子节点呢,使用前后指针链接,这样可以便于范围查找。 从左往右遍历叶子节点进行全盘扫描.
601 为什么使用B+树作为索引结构
- 高效的查询性能
- 自平衡,根到叶子路径长度相同, CRUD操作都是 O(logn)复杂度,比较快.
- 树高长的不会过快,使得查询磁盘的IO次数减少
- 非叶子节点只存放索引值和页面指针
- 每一页中能够容纳更多的索引字段, B+树层级不会高,减少查询的IO次数(从磁盘调入节点的次数).
- 范围查询能力强. 叶子节点通过链表连接,定位到叶子节点,根据范围查询只需要顺序扫描链表
- 根节点到叶子节点均一样高
- 叶子节点是双向链表,支持范围查询
- 非叶子节点仅存放索引记录,每页大小能存放更多的索引值,减少IO
B树
- 每个节点都存储了完整的数据
- B树叶子节点没有连接指针
602 MySQL怎么实现事务 ❌❌❌
ACID
-
原子性: undo log 回滚. 分布式事务,两阶段提交(seata XT AT)
-
一致性:
-
隔离性: 并发事务的相互影响, 加锁, MVCC实现(版本链 和 读视图) 可重复读 - 快照读, 读已提交 - 当前读.
-
持久性: redo log 实现宕机恢复
- 双写缓冲
- 刷盘时机: 每次提交, 异步
通过锁、Redo Log、Undo Log、 MVCC实现事务
-
利用锁(行锁、间隙锁等)机制,控制数据的并发修改,满足事务的隔离性
-
Redo Log(重做日志),它记录事务对数据库的修改,当MySQL宕机了,通过重放redo log可以恢复数据,满足事务的持久性
-
Undo Log(回滚日志),记录事务的反向操作,保持数据的历史版本,用于事务的回滚,使事务执行失败后可以恢复到之前的样子。实现原子性和隔离性
-
MVCC 多版本并发控制,满足非锁读的需求,提供读写并发,实现了读已提交和可重复读两种隔离级别
- 读已提交:每次查询生成新的 ReadView,可能导致多次查询结果不一致
- 可重复读:事务启动时生成 ReadView,保证整个事务中查询结果一致
事务工作流程:
Redo Log(重做日志):
=> 保证事务的持久性,即使宕机了也能恢复提交的数据
版本链示意
重点:

106 bin log, redo log, undo log日志
查询分为: [服务层 => Buffer Pool => MySQL磁盘]
利用Buffer Pool来进行加速.
-
查询数据会先查BP,中了直接返回. 未命中再查询磁盘 并将数据页从磁盘读入到 buffer pool.
-
写的话,先写Buffer Pool再标记为脏页, 后台线程会定期刷新脏页到磁盘中.

但是呢, 万一服务宕机了, 内存数据就没了.
为了保证持久性. 因此,具体做法为,当有记录需要更新,InnoDB引擎就会先更新内存标记脏页,将本次对这个页的修改在redo log中记录下来.后续后台线程会定期刷新脏页到磁盘中. 这就是 WAL 先写日志再写磁盘.
redo log是追加写方式, 磁盘操作是顺序写. 而写入数据需要找到写入位置然后写磁盘是随机写. (磁盘头相关了 … 寻道时间, 顺序时间, 传输时间)
一条update语句的执行过程
当优化器分析出成本最小的执行计划后(连接-分析-优化),执行器就按照执行计划开始进行更新操作。
-
执行器负责具体执行,会调用存储引擎的接口,通过主键索引树搜索获取 id = 1 这一行记录:
- 如果 id=1 这一行所在的数据页本来就在 buffer pool 中,就直接返回给执行器更新;
- 如果记录不在 buffer pool,将数据页从磁盘读入到 buffer pool,返回记录给执行器。
-
执行器得到聚簇索引记录后,会看一下更新前的记录和更新后的记录是否一样:
- 如果一样的话就不进行后续更新流程;
- 如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作;
-
开启事务, InnoDB 层更新记录前,先记录undo log(记录旧值), 执行引擎修改buffer pool的数据为新数据并标记对应的buffer pool中的页为脏页, 并记录到redo log中. (WAL技术, 写前先日志)
-
后台线程定期的刷盘. 之后, 记录该update语句到bin log cache中, 等事务提交再刷盘到bin log中.
-
接下来事务的两阶段提交
因为redo log 和 bin log都需要刷盘, 两步操作,可能出现不一致现象
⭐redo log 和 binlog 都要持久化到磁盘.
redo log 分为 prepare 和 commit 中间

609 Mysql有哪些锁
粒度分类:
-
全局锁:整个数据库加锁,只读
-
行级锁:细粒度
- 对特定的行加锁,允许其他事务并发访问不同的行,适用于高并发的场景
-
表级锁 粗粒度
- 对整个表进行加锁,其他事务无法对该表进行操作
属性分类:
-
共享锁 S锁 Shared lock
- 允许多个事务并发读同一资源,但是不允许修改,只有释放共享锁后,其他事务才能获取排他锁
-
排他锁 Exclusive lock X锁
- 只允许一个事务读写资源,互斥,独享
1-- 添加共享锁
2SELECT ... LOCK IN SHARE MODE;
3-- 共享锁
4SELECT ... FOR SHARE;
5-- 排他锁
6SELECT ... FOR UPDATE;
- 记录锁:锁定特定的行记录
- 间隙锁
- 针对索引中两个记录之间的间隙加锁,防止其他事务在这个间隙中插入新的数据,避免幻读。锁定行与行之间的空间 阻止新的记录的插入
- 临键锁
- 锁定具体行和其前面的间隙,确保范围内不会幻读
是怎么加锁的呢? 在默认的可重复读隔离级别下? 防止幻读
“唯一索引” 的等值查询
数据有 id [1, 5, …]
- 查询存在的
SELECT * FROM xxx WHERE id = 1 查询成功!
这里 临键锁 退化为表锁, 防止幻读
- 查询不存在的
SELECT * FROM xxx WHERE id = 2 查询为空!
这里 临键锁 退化为间隙锁, 锁住(1,5), 防止新的数据插入, 避免幻读
非唯一索引的等值查询
1select * from user where age >= 22 for update;

二级索引还是临键锁 多个
主键索引 加行锁啦
⭐⭐⭐ 所以在线上千万不要执行没有带索引条件的 update 语句,不然会造成业务停滞
603 MySQL长时间事务可能造成什么问题?
问题:
- 长时间事务 => 长时间的锁竞争+阻塞资源
- 其他事务也需要对应的锁,但是这个锁长时间拿不到,阻塞
- 部分业务的阻塞会影响到其他服务,导致系统出现严重的服务崩盘
- 死锁风险
- 多个事务相互等待对方释放锁,导致系统无法继续执行
- 回滚导致时间的浪费
- 执行了很长,但是快结束出现异常 => 事务回滚需要很长的时间
长时间事务如何解决
-
拆分成一小份一小份的短事务
时间? 创造索引条件,得到表的主键最大值和最小值,切分成一小份一小份的区间在执行
-
删除大量的数据 => 在新的表中插入相对小的数据
604 MVCC
⭐事务的数据快照 + 版本链
[数据版本控制隐藏字段和指针、事务视图ReadView]
Multi-Version Concurrency Control 多版本并发控制 => 允许事务同时读写数据库,而无需相互等待
- 提高并发性能 & 避免读写冲突 & 减少锁竞争
MVCC中,为每个事务创建一个数据快照,当数据被修改时,MySQL不会立即覆盖原有数据,而是生成新版本的记录。每个记录都保留着相应的版本号
- 读写冲突:传统读写或互斥。解决方法:多版本方式,读旧版本数据,写在新版本上,从而避免读写冲突。
- 锁竞争:传统方式需要竞争锁,解决方式:通过版本控制替代锁。
- 事务隔离级别实现:传统机制:锁。解决方式:版本控制。
MVCC为每条数据维护多个版本
每个版本包括:
- 版本号:标识创建版本的事务ID
- 时间戳:创建时间
- 数据内容
读操作:
根据事务的隔离级别和当前时间戳,选择合适的数据版本。
- 在读已提交下隔离级别下:读操作会读取已提交的最新版本
- 在可重复读隔离级别下:读操作会读取事务开始时的数据版本
写操作:
- => 创建新的数据新版本,而不是修改旧版本数据
- 旧版本数据会被保留,直到没有任何事务引用 <= 垃圾回收
MVCC优势:
- 高并发; 灵活的隔离级别; 减少锁竞争; 一致性视图
隐藏字段:
每行记录除了自己定义的字段外,还有一些重要的隐藏字段
- db_row_id: 没有定义Primary Key,默认用这个为Index
- db_trx_id:最近对这个记录修改的事务ID
- db_roll_ptr: 回滚指针,指向这个记录的上一个版本,指向Undo Log中的上一个快照版本的地址
多版本之间串联起来形成一条版本链。 不同事务可以无锁的读取不同版本的数据(普通读)。普通读和写不会阻塞
写操作可以继续写,就是新的数据版本不会立即对其他事务可见,只有事务提交后,新版本的记录才会可见
MySQL InnoDB通过Undo Log 操作 和 ReadView实现
Undo Log 记录数据修改之前的状态的日志,作用如下:
- 事务回滚:撤销未提交的事务修改
- 实现MVCC:提供历史版本数据,支持一致性读
=> 当事务修改数据时,InnoDB会将数据的旧版本写入Undo Log中
=> 当Undo Log没被任务事务引用时,会被垃圾回收
ReadView 读视图
事务某时刻创建的数据库快照,用于确定哪些数据版本对事务可见 => 实现事务隔离性
-
核心字段
- m_ids: 当前活动事务ID列表
- min_trx_id: m_ids中最小事务的ID
- max_trx_id
- creator_trx_id: 创建该ReadView事务的ID
-
可见性规则
-
对于每条数据的版本,ReadView会根据一下规则判断是否可见
-
-
当数据版本的事务ID < min_trx_id
=> 数据版本以及提交(过去的事务),可见
-
当数据版本的事务ID >= max_trx_id
-
-
=> 说明数据版本是未来事务创建的,不可见
3. 数据版本的事务ID 在 m_ids中 ⭐⭐⭐
=> 说明他是由未提交的事务创建的, 不可见, 否则可见
如果当前数据版本的 trx id 大小在 min trx id 和 max trx id 之间,此时 trx id 若在 m ids 中,说明修改这条数据的事务此时还未提交,所以不可见,若不在m ids 中,表明事务已经提交,可见。
4. 数据版本的事务ID == creator_trx_id
=> 自己创建的,可重入,当然可见了
可见性取决于ReadView的快照机制 => ReadView创建时间 决定了事务能看到哪些已提交的数据。
MVCC
解决脏读:事务只能读取已提交的数据记录;可见性控制 => 去UndoLog中找历史记录
解决不可重复读:ReadView
❗不能解决幻读:比当前事务Id小的事务提交了,那么新出现的记录可以被看到,特别是范围查询的时候
使用场景:
- MVCC:读多写少的场景 => 商品库存查询? 读事务不会被写事务阻塞
- 对于银行金额变更场景 => 锁

事务隔离级别
事务的ACID特性:Atomicity, Consistency, Isolation, Durability
数据一致性问题:
- 脏读:事务A读取了事务B尚未提交的数据,此时事务B回滚,那么A读的数据是脏数据(无效)
- 幻读:同一事务中,多次执行的相同查询,得到的结果集不一致。
- 其他事务插入新的数据并提交
- 不可重复读:同一事务,多次读取的同一数据,结果不一致
- 因为事务执行期间,其他事务对结果进行了修改
事务隔离级别:避免上述数据不一致问题
- 读未提交:允许事务读取其他事务未提交的数据。 会引起:脏读、幻读、不可重复读;
- 读已提交:事务只能读取其他事务已提交的数据。避免了:在脏读;(默认)
- 可重复读:加锁(行锁),确保事务多次读取同一数据的一致性。避免了:脏读、不可重复读;可能:幻读
- 串行化:事务完全串行执行:锁定整个表或者范围来实现。避免:脏读、幻读、不可重复读
MySQL主从复制
核心是基于二进制日志(BinLog File)进行数据同步
- 主库(Master)记录变更
- 任何对数据库进行变更的操作都会被记录到BinLog中。Update Delete Insert
- 从库Slave获取Binlog
- 从库通过IO线程连接主库,读取Binlog,并存入本地中继日志中(Relay log)
- 从库Slave 回放Binlog
- 从库SQL线程解析Relay Log,执行相同的SQL语句,进而保持与主库相同的数据
一主多从
- 一个主库,多个从库,每个从库独立同步主库数据
主从复制的优势
- 数据冗余 => 更安全
- 读写分离 => 主库写,从库读,提高性能
- 高可用性 => 主库故障 切换从库,提高系统可靠性
610 MySQL乐观锁&悲观锁
乐观锁:比如CAS机制,不加锁实现并发。并发性能高。 比较版本号和时间戳实现
悲观锁:每次操作都加锁,互斥操作,数据一致性要求高的场景。并发性能低
612 ❌❌❌MySQL EXPLAIN
Explain Select
- type: 查询类型, all index range
- rows: 扫描的行数
- key: 实际用到的索引
616 MySQL SQL调优
通过分析慢SQL,利用explain分析查询语句的执行计划,识别性能瓶颈,优化查询语句
-
设计:索引覆盖,避免回表
-
索引利用:一定利用上索引,利用索引的有序性<=最左原则 命中索引
-
查询范围:扫描范围尽可能小<= 避免全盘扫描 减少回表
-
返回内容:仅返回必要的信息 减少IO
-
合理设计索引,利用联合索引进行覆盖索引的优化
- 覆盖索引:让常用的字段都在索引中,这样就可以减少回表查询的开销
-
避免 **SELECT ***,只查询必要的字段
-
避免在SQL WHRER中索引执行函数或者运算,使得索引失效
-
避免**%LIKE**,导致全屏扫描
-
注意联合索引需满足最左匹配原则
-
对Where中常见的字段建立索引. 排序的字段建立索引
通过业务的优化,进行缓存,减少对数据库的访问
-
减少多表查询的情况
- JOIN操作,数据量大时,容易导致查询速度慢,影响数据库性能
- 避免频繁使用JOIN多张大表,而是分步查询或缓存
- 必要时使用冗余存储,减少JOIN。
- JOIN操作,数据量大时,容易导致查询速度慢,影响数据库性能
一次查大量数据的优化方式
-
分页查询
-
使用LIMIT和OFFSET,每次只查一部分数据
1SELECT ... FROM ... WHERE ... LIMIT ??? OFFSET ...
-
-
分批次查询:
```sql SELECT ... FROM ... WHERE id > 1000 ORDER BY id LIMIT 100; ```
回答:索引相关:
- 查询的时候,满足最左前缀匹配原则
- 最小查询,只查询所需要的必要信息。减少回表情况
- 利用好索引,加速检索。 避免全盘扫描的情况
-
- 避免 Like % 全盘匹配
- 避免WHere中对字段进行函数操作,使得索引失效
- 不要对无关字段进行排序
-
- 利用好缓存,直接减少数据库的访问
619 MySQL实现读写分离
读操作次数远大于写操作
将读操作分摊到从数据库中。主数据库负责写操作。然后通过主从复制,同步数据。
数据一致性要求高的场景
⭐1. 关键事务必须强制从主库中读取数据
- 支付订单状态
- 扣减库存等等
2.半同步控制
当事务提交后,至少有一个从库确认接收到数据,才认为数据写入成功
3.业务层解决,将数据写入Redis中,优先从缓存中读取数据
读写分离 => 将压力分摊到子库中,提高性能
- 同步 => 数据一致性 => 全同步, 半同步
620 MySQL主从复制机制
客户端 <=> 主MYSQL <=> 从MYSQL
应用场景数据备份or主从数据同步
- 同步复制:将binlog file复制到所有从库,等所有从库响应了,主库才响应客户端 (性能差,数据一致性高)
- 半同步复制:主库等待至少一个从库确认收到数据(性能折中,数据一致性较高)
- 异步复制:主库不用等待从库确认(性能高,数据一致性差)
MySQL默认是异步复制的, 数据同步过程
主库:
- 提交事务请求 => 主库 => 同时写入binlog file,更新数据并相应客户端,并推送binlog更新事件
- binlog file 被主库的dump线程发送给从库
从库:
- 从库IO线程接收binlog,并写入Relay log(中继日志)中,缓冲
- 从库SQL线程从Relay log重写事件到从库数据库中。
621 如何处理MySQL的主从同步延迟
- 二次查询: 查询MySQL从节点数据 未命中 ,再次查询MysQL主节点
- 关键业务走主节点: 直接规避掉不一致问题
- 使用缓存,缓解,但又引入缓存一致性问题
622 数据库进行分库分表以及带有的问题
分库分表策略
- **水平分表:**将一张表的数据,放在两个表中。例如根据ID
- **垂直分表:**将一张表不同列拆分到多个表中,⭐减少每张表的字段数提高查询效率。例如,用户表可以拆分为基本信息和详细信息表。
- **水平分库:**将相同表结构复制一份到另一张表中,减少单一数据库的读写压力。
- **垂直分库:**根据业务功能,将表分到不同数据库中。例如将用户数据、商品数据分别存储到不同的数据库中。
为什么需要分库分表?
随着用户越来越多,导致表数据量大(数据堆积),读写请求多(并发量高)。
=>导致 性能瓶颈,单一数据库性能有限。
- 分表:100w中找,和1w中找还是不一样的。可以先hash计算到底在哪个数据库,再具体执行。
- 分库:可以按业务or功能不同,将请求分配到多态服务器上,降低单一服务器压力。
624 数据库分库分表可能引发的问题
单机 => 分布式
- 事务问题:会出现事务的数据不一致问题,需要使用分布式事务解决。
- JOIN连表问题: 跨服务器无法JOIN表,业务中实现关联,或者使用冗余字段。
- **全局唯一ID问题:**单机,使用自增长的ID即可,分布式必须使用全局唯一ID发号器生成唯一ID。
- **排序问题:**上推到业务中实现。
- **count问题:**业务代码中累加。
625 MySQL获取数据,从磁盘中写的吗?
MySQL中,并不总是从磁盘中读取。利用缓存机制,提高读取性能。
mysql8.0中有查询缓存,只有sql相同时才会命中,命中率低,在8.0后移除了
buffer pool(old sublist, young sublist), 里面存储了一个一个的数据页,mysql会从buffer pool中找,找到就返回。
=> 一块内存空间,访问某个数据(磁盘中)时,会将包括该数据的页加载到内存中。页大小16KB,局部性原理,以后对该页的修改和访问都在buffer pool中进行。
=> 内存淘汰策略:变体的LRU(least recently used最近最少使用)。 buffer pool将数据分为年轻代和老年代(默认5:3),当数据从磁盘调到内存中时,因为空间时间局部性,新的数据会放在老年队列中,当1s中没有再次访问时才转到年轻队列中。
=> 如果直接放在年轻代中,可能会把热点数据淘汰掉
=> 1s,时间窗口,渡过这个时间还被访问认为是热点数据。
629 为什么不推荐多表JOIN
数据量大的时候,多表JOIN,数据库需要对联接的每个表进行扫描,会消耗大量的CPU和内存资源。 性能比较低
数据库往往是我们系统的弱点,很多情况性能瓶颈都在数据库,因此我们需要尽可能避免把压力放在数据库上。
JOIN连接表的时候,需要关注被驱动表的查询是否能够命中索引,不然会导致全表扫描。
⭐⭐⭐尽可能让小表做驱动表,因为驱动表需要全表扫描,而被驱动表是通过索引查询的。且被驱动表最好能够命中索引。
例子
1SELECT * FROM ... a1 join ... a2 on (a1.a == a2.a)
小表有A行,大表有B行。每次扫描A,每行都需要去大表里面查,时间LogB(命中索引),假设存在回表,那么为2*LogB的查询时间。有A行,那么查B表的时间为=>A*2*LogB;查A表的时间为A,total => A+A*2*LogB。
JOIN SQL查询的流程
- 扫描A表(小表)
- 读取A表的所有行,及A次操作
- 查询B表
- A行 * LogB(命中索引的查询时间) * 2(回表时间)
- => A + A*2*LogB
630 MySQL中解决深度分页问题
深度分页是指数据量很大的时候,按照分页访问后面的数据,例如limit 99999, 10,这回使得数据库扫描前面的99999条数据,才能得到最终的10条数据。 ⭐因为B+树只存储键值和指针,不存储"这是第几条记录"的信息
1❌❌❌LIMIT num1, num2; -- 偏移量, 返回行数。 会扫描起点到num1整段记录
2✔️✔️✔️LIMIT num1; -- 返回行数
尽量避免LIMIT num1, num2;写法.这样会遍历前面的num1行,再取num2行
Where 中 添加**⭐走索引+范围限制⭐+Limit**
解决方法:
- 子查询: 子查询 + 记录上一次查询的 Last ID
1SELECT id, name
2FROM tabel
3WHERE name = 'A'
4and id >= (SELECT id FROM tabel WHERE name = 'A' order by id limit 99999, 1)
5order by id limit 10;
6
7-- 传递上一次查询的最后一个ID
8SELECT * FROM users WHERE id > last_id ORDER BY id LIMIT 10;
627 MySQL的Doublewrite Buffer双写
确保数据安全的,因为MysQL是基于硬盘的,万一写数据时宕机了,没写完那么就会出现问题.因此,先写入内存中(快),这样MySQL写操作异常了,恢复后可以进行弥补. => 内存页不损害(没写完)
628 MySQL的LogBuffer作用
利用内存缓存Redog Log,再批量写入硬盘. 减少IO,使得每次IO数据量更大,降低申请IO所需要切换进程的开销
631 优化慢SQL
使用EXPLAIN分析SQL的执行计划
查看SQL的执行顺序, 索引的使用, 扫描的行数等.
=> 利用索引
=> 复杂的JOIN查询,拆分成多个简单查询,利用小表作为驱动表
=> 避免SELECT *
1. 索引 2. 回表 3. IO
1418 数据库视图
数据库视图是虚拟表,作用:简化复杂查询、安全性、数据抽象、可重用性
1CREATE VIEW employee_salaries AS
2SELECT
3 e.employee_id,
4 CONCAT(e.first_name, ' ', e.last_name) AS full_name,
5 d.department_name,
6 e.salary
7FROM
8 employees e
9JOIN
10 departments d ON e.department_id = d.department_id;
11
12-- CREATE VIEW view__ AS
1214 MySQL中 Limit 100000, 10 和 Limit 10; 执行速度
Limit 100000, 10 会遍历前面100000再返回后面的10条记录
而 Limit 10 只返回第一个记录和后面的10条
Limit 100000, 10就是深度分页的元凶!!!
解决:通过索引定位到第一个记录再Limit num;
1479 什么情况下,不推荐为数据库建立索引
有以下几种情况:
| 原因 | |
|---|---|
| 数据量小的表 | 建议索引并不会显著提高查询性能,反而增加复杂性 |
| 频繁更新的表 | 每个插入删除操作,都需要更新索引,导致过高的维护索引的开销 |
| 高度重复的列 | 例如,性别,索引效果不明显,反而增加存储空间 |
| 长文本字段 | 这些类型的列包含大量数据,添加索引,导致无法使用内存排序,需要利用磁盘,导致大量IO |
3179 数据库不停服迁移
通过数据双写实现
- **旧数据同步:**采用主从同步方式,将新库作为旧库的从库,实现历史数据的迁移;
- **新数据同步:**采用数据双写方式,将新数据同时写入新旧两个库中。
- **一致性检查:**定时任务,抽检两个库的数据一致性;
- 灰度切流: 逐渐将用户的数据请求,迁移到新库中。
4040 MySQL数据库的性能优化方法
SQL 和 库表两部分设计,优化MySQL性能问题。
SQL优化:
- 避免 SELECT * ,只查询必要字段;
- 避免在SQL中进行函数等计算操作,导致索引失效;
- 避免使用 %Like,导致全表扫描;
- 注意联合索引需要满足最左匹配原则;
- 不要对无索引字段进行排序。
库表设计
- 合理表结构:合理的数据类型;
- 合理冗余字段:冗余设计,减少关联查询;
- 索引优化:根据查询频率和条件,创建合适的索引;
- 分库分表:提高读写性能。
1219 数据库三大范式
第一范式:原子性,字段只包含单一数据项;
第二范式:非主键必须依赖于整个主键;
第三范式:非主键字段只依赖于主键,不应该相互依赖。
数据库中存储金额数据使用的数据类型:bigint and decimal
sql:java
bigint: long;
decimal:BigDecimal
1482 MySQL中EXISTS和IN的区别是什么
- EXISTS ɪɡˈzɪsts
- 判断子查询是否返回任何行,通常用于检查某个条件是否满足; 满足条件后则返回,大数据量时性能好
- IN
- 检查某个值是否在指定集合中;
EXISTS 外=>内判断,满足即停止。最好子表有索引。
IN => 先内, 再外匹配内。
11175 SQL中SELECT、FROM… 的执行顺序是什么?
- FROM
- WHERE: 淘汰的单位是行
- GROUP BY
- HAVING:聚合数据,淘汰的单位是整组
- SELECT
- ORDER BY
- LIMIT
B+树、聚簇索引、主键索引、二级索引
B+树:数据结构
聚簇索引(主键索引):叶子节点存储了整行数据,一张表只能有一张(Primary Key)。 使用B+树实现
二级索引:叶子和非叶子都只存储了Index比较需要的数据。使用B+树实现。因此,索引完,SELECT有其他字段,需要回表根据Primary Key再查一次
=> 索引覆盖:将SELECT 所需的字段均放置在Index中,就不用回表查询了
9498 MySQL事务的二阶段提交
为的是保证redolog 和 bin log的一致性
事务开始 => redolog(预备) => bin log => 事务完成
这里可能中间回滚