617 MySQL 数据排序 实现?

  1. Order By 命中索引(包括索引字段),使用索引排序(⭐有序⭐),效率最高效
  2. 否则使用文件排序,文件少=> 内存排序 sort_buffer
  3. 文件大=>外部排序,归并排序

内部排序细节:

双路排序(待排序的列数据太大了):使用row_id(回查表) + sort_field

​ 排好序后,使用row_id将完整的记录取出来

单路排序(待排序数据大小能接受)

​ 直接拍,不会查表,直接把拍好的结果返回

外部排序:

拆分小的,外部多路归并排序,小=>大

外部归并排序 => 先分段排序,每一段调入内存执行快排

​ => 归并阶段,因为每子段都是有序的 => 多路归并排序

589 一条SQL的执行过程

  1. 先通过连接器校验权限
  2. 利用分析器进行SQL语句词法分析和语法分析,构建解析树
  3. 利用优化器选择合适的索引和表连接顺序,最终选择一条最佳的执行计划
  4. 利用执行器,调用引擎层查询数据,返回结果集

具体:Select * from user where id = 1;

image-20250305222939319
  • 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 建立索引注意事项

  1. 不能盲目建立,因为维护需要代价
  2. 表的修改频率远大于查询频率 => 维护代价大

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
image-20250510205629216

页数据,包括页目录和对应的数据(顺序存放的),页目录有多个槽,并且数据是分组的,每个槽指向组内的开头的记录行,这样我们可以

利用二分查找Ologn复杂度查找对应的槽,然后顺序的遍历找到对应的数据。

非命中索引的话,因为B+树,叶子节点呢,使用前后指针链接,这样可以便于范围查找。 从左往右遍历叶子节点进行全盘扫描.

601 为什么使用B+树作为索引结构

  • 高效的查询性能
    • 自平衡,根到叶子路径长度相同, CRUD操作都是 O(logn)复杂度,比较快.
  • 树高长的不会过快使得查询磁盘的IO次数减少
    • 非叶子节点只存放索引值和页面指针
    • 每一页中能够容纳更多的索引字段, B+树层级不会高,减少查询的IO次数(从磁盘调入节点的次数).
  • 范围查询能力强. 叶子节点通过链表连接,定位到叶子节点,根据范围查询只需要顺序扫描链表
  1. 根节点到叶子节点均一样高
  2. 叶子节点是双向链表,支持范围查询
  3. 非叶子节点仅存放索引记录,每页大小能存放更多的索引值,减少IO

B树

  • 每个节点都存储了完整的数据
  • B树叶子节点没有连接指针

602 MySQL怎么实现事务 ❌❌❌

ACID

  • 原子性: undo log 回滚. 分布式事务,两阶段提交(seata XT AT)

  • 一致性:

  • 隔离性: 并发事务的相互影响, 加锁, MVCC实现(版本链 和 读视图) 可重复读 - 快照读, 读已提交 - 当前读.

  • 持久性: redo log 实现宕机恢复

    • 双写缓冲
    • 刷盘时机: 每次提交, 异步

通过锁、Redo Log、Undo Log、 MVCC实现事务

  1. 利用(行锁、间隙锁等)机制,控制数据的并发修改,满足事务的隔离性

  2. Redo Log(重做日志),它记录事务对数据库的修改,当MySQL宕机了,通过重放redo log可以恢复数据,满足事务的持久性

  3. Undo Log(回滚日志),记录事务的反向操作,保持数据的历史版本,用于事务的回滚,使事务执行失败后可以恢复到之前的样子。实现原子性和隔离性

  4. MVCC 多版本并发控制,满足非锁读的需求,提供读写并发,实现了读已提交和可重复读两种隔离级别

  • 读已提交:每次查询生成新的 ReadView,可能导致多次查询结果不一致
  • 可重复读:事务启动时生成 ReadView,保证整个事务中查询结果一致

事务工作流程:

image-20250306141944766

Redo Log(重做日志):

=> 保证事务的持久性,即使宕机了也能恢复提交的数据

版本链示意

image-20250306142039417

重点:

image-20250510211052450

106 bin log, redo log, undo log日志

查询分为: [服务层 => Buffer Pool => MySQL磁盘]

利用Buffer Pool来进行加速.

  • 查询数据会先查BP,中了直接返回. 未命中再查询磁盘 并将数据页从磁盘读入到 buffer pool.

  • 写的话,先写Buffer Pool再标记为脏页, 后台线程会定期刷新脏页到磁盘中.

image-20250510212655692

但是呢, 万一服务宕机了, 内存数据就没了.

为了保证持久性. 因此,具体做法为,当有记录需要更新,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 中间

image-20250510220034401

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;

image-20250510224644966

二级索引还是临键锁 多个

主键索引 加行锁啦

⭐⭐⭐ 所以在线上千万不要执行没有带索引条件的 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中的上一个快照版本的地址
image-20250306211450491

多版本之间串联起来形成一条版本链。 不同事务可以无锁的取不同版本的数据(普通读)。普通读和写不会阻塞

写操作可以继续写,就是新的数据版本不会立即对其他事务可见,只有事务提交后,新版本的记录才会可见

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会根据一下规则判断是否可见

      1. 当数据版本的事务ID < min_trx_id

        => 数据版本以及提交(过去的事务),可见

      2. 当数据版本的事务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:读多写少的场景 => 商品库存查询? 读事务不会被写事务阻塞
  • 对于银行金额变更场景 => 锁

image-20250510221201625

事务隔离级别

事务的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分析查询语句的执行计划,识别性能瓶颈,优化查询语句

  1. 设计:索引覆盖,避免回表

  2. 索引利用:一定利用上索引,利用索引的有序性<=最左原则 命中索引

  3. 查询范围:扫描范围尽可能小<= 避免全盘扫描 减少回表

  4. 返回内容:仅返回必要的信息 减少IO

  • 合理设计索引,利用联合索引进行覆盖索引的优化

    • 覆盖索引:让常用的字段都在索引中,这样就可以减少回表查询的开销
  • 避免 **SELECT ***,只查询必要的字段

  • 避免在SQL WHRER中索引执行函数或者运算,使得索引失效

  • 避免**%LIKE**,导致全屏扫描

  • 注意联合索引需满足最左匹配原则

  • 对Where中常见的字段建立索引. 排序的字段建立索引

    通过业务的优化,进行缓存,减少对数据库的访问

  • 减少多表查询的情况

    • JOIN操作,数据量大时,容易导致查询速度慢,影响数据库性能
      • 避免频繁使用JOIN多张大表,而是分步查询或缓存
      • 必要时使用冗余存储,减少JOIN。

一次查大量数据的优化方式

  • 分页查询

    • 使用LIMIT和OFFSET,每次只查一部分数据

      1SELECT ... FROM ... WHERE ... LIMIT ??? OFFSET ...
      
  • 分批次查询:

               ```sql
               SELECT ... FROM ... WHERE id > 1000 ORDER BY id LIMIT 100;
               ```
    

回答:索引相关:

  • 查询的时候,满足最左前缀匹配原则
  • 最小查询,只查询所需要的必要信息。减少回表情况
  • 利用好索引,加速检索。 避免全盘扫描的情况
      1. 避免 Like % 全盘匹配
      2. 避免WHere中对字段进行函数操作,使得索引失效
      3. 不要对无关字段进行排序
  • 利用好缓存,直接减少数据库的访问

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重写事件到从库数据库中。

image-20250321101913192

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后移除了

image-20250321102938732

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… 的执行顺序是什么?

  1. FROM
  2. WHERE: 淘汰的单位是行
  3. GROUP BY
  4. HAVING:聚合数据,淘汰的单位是整组
  5. SELECT
  6. ORDER BY
  7. LIMIT

B+树、聚簇索引、主键索引、二级索引

B+树:数据结构

聚簇索引(主键索引):叶子节点存储了整行数据,一张表只能有一张(Primary Key)。 使用B+树实现

二级索引:叶子和非叶子都只存储了Index比较需要的数据。使用B+树实现。因此,索引完,SELECT有其他字段,需要回表根据Primary Key再查一次

=> 索引覆盖:将SELECT 所需的字段均放置在Index中,就不用回表查询了

9498 MySQL事务的二阶段提交

为的是保证redolog 和 bin log的一致性

事务开始 => redolog(预备) => bin log => 事务完成

​ 这里可能中间回滚