【秋招必看】MySQL 面试热题(三)

目录

1.MySQL 中有哪些锁类型?(中)

2.MySQL 的乐观锁和悲观锁是什么?(中)

3.MySQL 中如果发生死锁应该如何解决?(中)

4.MySQL 中 count(*)、count(1) 和 count(字段名) 有什么区别?(易)    

5.MySQL 中如何进行 SQL 调优?(中)    

6.如何使用 MySQL 的 EXPLAIN 语句进行查询分析?(中)    

7.MySQL 中如何解决深度分页的问题?(中)

8.什么是 MySQL 的主从同步机制?它是如何实现的?(中)

9.如何处理 MySQL 的主从同步延迟?(中)    

10.什么是分库分表?分库分表有哪些类型(或策略)?(易)


1.MySQL 中有哪些锁类型?(中)

MySQL的锁机制主要围绕并发控制和数据一致性设计。核心分类按粒度全局锁(影响整个实例)、表级锁(包括基本表锁、元数据锁MDL和InnoDB特有的意向锁IS/IX)、以及InnoDB支持的行级锁(记录锁、间隙锁、临键锁、插入意向锁)。按读写属性则分为共享锁S(允许多读)和排他锁X(独占读写)。

拓展:

I.详细区分:

  1. 按粒度划分:

    • 全局锁: 锁定整个数据库实例(FLUSH TABLES WITH READ LOCK)。主要用于全库逻辑备份,阻塞所有写操作和大部分DDL,影响非常大。

    • 表级锁:

      • 表锁: 最基本的锁,锁定整张表(LOCK TABLES ... READ/WRITE)。MyISAM引擎默认使用。写锁阻塞其他所有读写,读锁阻塞写但不阻塞读。InnoDB通常避免使用。

      • 元数据锁: 在访问表时自动加锁(MDL)。保证表结构不被修改(如DDL操作)。读操作加读锁,写操作加写锁。DDL操作需要排他MDL锁,会阻塞读写操作,也容易被长事务阻塞。

      • 意向锁: InnoDB特有。表级锁,表示事务稍后会在表中的某些行上需要哪种类型的锁(共享或独占)。

        • 意向共享锁: 表示事务打算在某些行上加共享锁

        • 意向排他锁: 表示事务打算在某些行上加排他锁

        • 作用: 快速判断表内是否有行被锁定(避免逐行检查),实现行锁与表锁的共存。例如,事务A对行加了S锁(行级),同时会对表加IS锁(表级)。事务B想加表锁时,看到表上有IS锁,就知道表内有行被S锁锁定了,如果事务B想加的是独占表锁(X锁),它就需要等待。

    • 行级锁: InnoDB引擎支持。锁定索引记录(即使表没有显式索引,InnoDB也会创建隐藏聚簇索引)。是支持高并发的基础。细分为:

      • 记录锁: 锁定索引中的单条记录。其他事务无法修改或删除(取决于锁类型)这条记录。

      • 间隙锁: 锁定索引记录之间的间隙(一个开区间)。防止其他事务在这个间隙中插入新记录,从而解决幻读问题(在REPEATABLE READ隔离级别下)。

      • 临键锁: 记录锁 + 间隙锁的组合。锁定一条记录及其之前的间隙(一个左开右闭区间)。InnoDB行锁的默认加锁方式(在REPEATABLE READ下)。同样用于防止幻读。

      • 插入意向锁: 一种特殊的间隙锁。在INSERT操作前设置。表示事务想在某个间隙插入新记录,但正在等待。允许多个事务在同一个间隙的不同位置等待插入(只要不冲突),提高并发插入性能。

  2. 按读写属性划分:

    • 共享锁: 也称为读锁S Lock)。允许多个事务同时读取同一资源(行/表),但阻止任何事务获取该资源的排他锁(即阻止写)。

    • 排他锁: 也称为写锁X Lock)。只允许一个事务读写资源。阻止其他事务获取该资源的任何共享锁或排他锁(即阻止读和写)。

  3. 其他相关概念:

    • 自增锁: 一种特殊的表级锁,用于处理具有AUTO_INCREMENT列的表上的并发插入操作。保证自增值的唯一性和连续性。

    • 死锁: 两个或多个事务互相等待对方释放锁,导致都无法继续执行。InnoDB有死锁检测和回滚机制。

    • 锁等待超时: 事务在获取锁时等待超过配置的时间(innodb_lock_wait_timeout)会报错。

II.表格总结

锁类型级别锁模式/意图主要应用场景/触发方式冲突对象 (主要)
全局锁全局只读FLUSH TABLES WITH READ LOCK所有其他会话的写操作
表级锁
表共享读锁共享 (S)LOCK TABLES table_name READ表独占写锁
表独占写锁排他 (X)LOCK TABLES table_name WRITE表共享读锁、表独占写锁
意向共享锁 (IS)意向共享事务准备给某些行加 S 锁时自动获得表独占写锁
意向排他锁 (IX)意向排他事务准备给某些行加 X 锁时自动获得表共享读锁、表独占写锁
元数据锁 (MDL)读/写访问表时自动加读锁,修改结构时自动加写锁读锁间兼容;写锁阻塞所有读写
AUTO-INC 锁特殊向有 AUTO_INCREMENT 列的表插入数据时其他向同一表插入的事务
行级锁 (InnoDB)
记录锁 (Record Lock)共享 (S)SELECT ... LOCK IN SHARE MODE同一行的 X 锁
排他 (X)SELECT ... FOR UPDATEUPDATEDELETEINSERT同一行的 S 锁、X 锁
间隙锁 (Gap Lock)间隙特殊在 REPEATABLE READ 下,防止幻读试图在该间隙插入的插入意向锁
临键锁 (Next-Key Lock)行+间隙组合InnoDB REPEATABLE READ 的默认行锁覆盖记录锁和间隙锁的冲突规则
插入意向锁间隙特殊意向INSERT 操作前同一确切插入位置的插入意向锁,该位置的间隙锁

III.锁兼容性矩阵(表级)

IS和IX的作用就是在上表级锁的时候,可以快速判断是否可以上锁,而不需要遍历表中的所有记录。IS和IX相互之间是不会冲突的,因为它们的作用只是打个标记。

XIXSIS
X冲突冲突冲突冲突
IX冲突兼容冲突兼容
S冲突冲突兼容兼容
IS冲突兼容兼容兼容

2.MySQL 的乐观锁和悲观锁是什么?(中)

(乐观锁和悲观锁都不是具体的锁,而是一种设计思想

悲观锁的核心思想是‘防患于未然’,它在操作数据前就通过 MySQL 的锁机制(如 SELECT ... FOR UPDATE)显式锁定数据,确保独占访问,适合写冲突严重的场景,但牺牲了并发性。乐观锁的核心思想是‘事后校验’,它利用版本号或时间戳机制,在提交更新时才检查数据是否被修改(WHERE ... AND version=...),无锁读取提升了并发性能,适合读多写少的低冲突场景,但需要应用层处理提交失败(如重试)。

拓展:

I.表格总结

特性悲观锁 (Pessimistic Lock)乐观锁 (Optimistic Lock)
核心思想“先锁再改” - 假设冲突很可能发生“先改再验” - 假设冲突不太可能发生
实现机制依赖数据库锁 (SELECT ... FOR UPDATE)使用版本号/时间戳 + WHERE 条件检查
加锁时机操作数据立即加锁操作数据期间不加锁
冲突检测通过加锁阻塞其他事务来避免冲突提交更新时检测版本冲突
性能开销大,并发度低 (锁竞争)开销小,并发度高 (无锁读)
失败处理较少发生 (锁保证成功)可能发生冲突导致更新失败,需重试/处理
适用场景写多读少,强一致性要求高,冲突概率高读多写少,高并发要求,冲突概率低

II.悲观锁示例:

START TRANSACTION;
-- 查询商品库存并锁定该行记录 (id=1)
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
-- ... 在应用层判断库存是否足够 ...
-- 更新库存 (在锁的保护下)
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT; -- 提交时释放锁

III.乐观锁示例:

-- 1. 读取数据 (假设当前 version=5)
SELECT stock, version FROM products WHERE id = 1;
-- ... 在应用层判断库存足够,并计算新库存 new_stock ...
-- 2. 更新数据 (同时检查版本号并更新它)
UPDATE products
SET stock = new_stock, version = version + 1
WHERE id = 1 AND version = 5; -- 关键:WHERE 条件包含之前读到的 version
-- 3. 检查 affected_rows (应用层代码判断)
--   如果返回 1:更新成功。
--   如果返回 0:更新失败(数据已被修改),触发重试逻辑或报错。

3.MySQL 中如果发生死锁应该如何解决?(中)

MySQL InnoDB 在检测到死锁时会自动回滚代价较小的事务来解除死锁(报错1213),这是最常见的解决方式。开发者也可通过 SHOW ENGINE INNODB STATUS 分析 LATEST DETECTED DEADLOCK 信息定位根本原因(如冲突 SQL、锁资源、加锁顺序),必要时用 KILL 命令人工干预。

拓展:

I.死锁排查步骤

1)查看最新死锁日志

SHOW ENGINE INNODB STATUS;  -- 查看日志中的 "LATEST DETECTED DEADLOCK" 部分

关键信息解读:

LATEST DETECTED DEADLOCK
------------------------
2024-07-30 12:00:00 0x7f8e1c0b6700
*** (1) TRANSACTION:  # 事务1信息
TRANSACTION 123456, ACTIVE 10 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 1001, OS thread handle 12345, query id 20001 localhost root updating
UPDATE table_a SET col1=1 WHERE id=10  # 正在执行的SQL

*** (1) HOLDS THE LOCK(S):  # 事务1当前持有的锁
RECORD LOCKS space id 10 page no 5 n bits 72 index PRIMARY of table `db`.`table_a` 
trx id 123456 lock_mode X locks rec but not gap  # 持有主键X锁

*** (1) WAITING FOR THIS LOCK:  # 事务1等待的锁
RECORD LOCKS space id 10 page no 6 n bits 72 index idx_col of table `db`.`table_b` 
trx id 123456 lock_mode X waiting  # 等待table_b的索引X锁

*** (2) TRANSACTION:  # 事务2信息
TRANSACTION 123457, ACTIVE 8 sec starting index read
UPDATE table_b SET col2=2 WHERE id=20  # 事务2的SQL

*** (2) HOLDS THE LOCK(S): 
RECORD LOCKS space id 10 page no 6 n bits 72 index idx_col of table `db`.`table_b` 
trx id 123457 lock_mode X  # 持有table_b的索引X锁

*** (2) WAITING FOR THIS LOCK:
RECORD LOCKS space id 10 page no 5 n bits 72 index PRIMARY of table `db`.`table_a` 
trx id 123457 lock_mode X waiting  # 等待table_a的主键X锁

2)分析死锁原因(根据日志)

组件说明
事务冲突路径事务1 持有 A 锁 → 请求 B 锁 ↓
事务2 持有 B 锁 → 请求 A 锁 ↑ (循环等待)
锁类型记录锁(X)、间隙锁(GAP)、Next-Key 锁等
涉及表/索引死锁发生的表、索引位置
SQL 语句导致死锁的具体 SQL(重点关注 UPDATE/DELETE/SELECT...FOR UPDATE)

3)监控死锁发生频率

-- 开启全局死锁日志记录(MySQL 5.6+)
SET GLOBAL innodb_print_all_deadlocks = ON;  -- 记录到错误日志中

-- 查询死锁统计信息
SELECT * FROM information_schema.INNODB_METRICS 
WHERE NAME LIKE 'lock_deadlocks%';

4)解决方案:

  • 事务重试(最常用)
# 伪代码示例(应用层重试)
max_retries = 3
for attempt in range(max_retries):
    try:
        execute_transaction()  # 执行事务
        break
    except DeadlockError:
        if attempt == max_retries - 1:
            raise
        sleep(random.uniform(0.1, 0.3))  # 随机等待避免集体重试
  • 手动 kill 事务
# 使用INFORMATION_SCHEMA中的INNODBLOCKS和INNODB_LOCK_WAITS表,查看当前锁和锁等待情况,得到事务ID
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

# 确定线程id后终止线程
KILL <thread_id>;

II.预防死锁

  • 约定一致的访问顺序: 在应用程序中,确保不同的事务总是以相同的顺序访问表或行。例如,总是先更新表A再更新表B;或者在更新同一组行时,总是按主键升序处理。这破坏了死锁的“循环等待”条件。

  • 合理使用索引: 确保查询(尤其是UPDATE/DELETE)能有效利用索引。没有索引或索引不当会导致锁升级(如行锁升级为表锁)或锁定过多的行(间隙锁范围过大),大大增加死锁概率。

  • 降低事务隔离级别: 如果业务允许,将隔离级别从 REPEATABLE READ 降到 READ COMMITTED,可以减少间隙锁(Gap Locks)的使用(尽管不能完全消除),从而降低某些类型死锁的发生概率。但需评估对一致性的影响。

  • 避免长事务: 长时间运行的事务会长时间持有锁,增加与其他事务冲突的机会。

  • 使用乐观锁: 对于冲突概率不高的场景,考虑使用版本号/时间戳机制实现乐观锁,避免在数据库层面长时间持有悲观锁。

  • 重试机制: 在应用层代码中,捕获死锁错误(1213)或锁等待超时错误(1205),并进行有限次数的重试。这是处理偶发死锁的一种常见且有效的补偿策略。

4.MySQL 中 count(*)、count(1) 和 count(字段名) 有什么区别?(易)    

COUNT(*) 和 COUNT(1) 在 MySQL 中性能完全等价,都是高效统计查询结果总行数的最佳选择,COUNT(*) 语义更清晰故更推荐。而 COUNT(字段名) 的本质是统计该字段非 NULL 值的数量,语义和结果与前两者截然不同。性能上,COUNT(字段名) 依赖该字段索引(有索引效率较高,无索引需全表扫描判空),且当字段允许 NULL 时结果会小于总行数。因此,COUNT(*)/COUNT(1) 用于统计总记录数,COUNT(字段名) 用于统计特定字段的有效值个数。

形式统计内容是否统计 NULL 行性能考虑使用场景
COUNT(*)结果集总行数通常最优 (引擎优化)需要知道总共有多少行记录
COUNT(1)结果集总行数等同于 COUNT(*)同 COUNT(*) (风格偏好)
COUNT(字段名)字段非 NULL 值的个数依赖索引 (有索引好,无索引差)需要统计某字段有效(非空)值的数量

5.MySQL 中如何进行 SQL 调优?(中)    

MySQL SQL 调优是一个数据驱动、迭代分析的过程。核心步骤是:

1) 启用慢日志定位问题 SQL。

2) 使用 EXPLAIN/EXPLAIN ANALYZE 深入分析执行计划,识别瓶颈(如全表扫描、文件排序)。

3) 针对性优化索引(覆盖索引、复合索引最左前缀、高区分度),这是最高效的手段。

4) 优化 SQL 写法(避免 SELECT *, 优化 JOIN/WHERE/ORDER BY/GROUP BY, 谨慎处理分页)。

5) 必要时考虑数据库设计调整(反范式、分库分表)和配置优化(重点 Buffer Pool)。

拓展:

经典调优场景案例:

问题场景: 分页查询越来越慢
原始SQL:

SELECT * FROM orders ORDER BY id LIMIT 1000000, 10; -- 耗时 2.3s

优化方案:

-- 方案1:游标分页(需记录上次ID)
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10; -- 0.01s

-- 方案2:延迟关联
SELECT * FROM orders o
JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 10) AS tmp
ON o.id = tmp.id; -- 0.15s

6.如何使用 MySQL 的 EXPLAIN 语句进行查询分析?(中)    

使用时,重点关注 type(避免 ALL,追求 const/ref/range)、key(是否使用正确索引)、rows(预估扫描行数)和 Extra 列(警惕 Using filesort/Using temporary,欢迎 Using index/Using index condition)。

拓展:

I.使用方法

在你要分析的 SELECT 语句前加上 EXPLAIN 关键字:EXPLAIN SELECT ... FROM ... WHERE ...

II.EXPLAIN 结果的核心字段及其优化意义:

字段描述理想值优化意义
id查询标识符数字执行顺序(id相同从上到下,id不同从大到小)
select_type查询类型SIMPLE, PRIMARY查询复杂度级别
table访问的表名-显示查询涉及的表
partitions匹配的分区NULL分区表使用情况
type访问类型const > ref > range > index > ALL核心性能指标
possible_keys可能使用的索引非空可选的索引列表
key实际使用的索引非空实际选择的索引
key_len使用的索引长度越小越好索引使用效率
ref索引比较值const, 列名索引使用方式
rows预估扫描行数<1000关键性能指标
filtered过滤后剩余百分比接近100%WHERE 条件效率
Extra附加信息Using index优化关键线索

III.type 访问类型(性能从优到劣)

  • const/system:通过主键或唯一索引直接定位单行(最佳性能)
  • ref:使用非唯一索引查找
  • range:索引范围扫描
  • index:全索引扫描(比ALL好)
  • ALL:全表扫描(需优化)

IV.Extra 字段关键值解析

含义优化建议
Using index覆盖索引理想状态,无需回表
Using where服务器层过滤考虑索引优化
Using temporary使用临时表优化GROUP BY/ORDER BY
Using filesort额外排序添加索引避免排序
Select tables optimized away优化器直接返回值无需优化
Impossible WHEREWHERE条件永不成立检查业务逻辑
Using join buffer使用连接缓冲区增大join_buffer_size

7.MySQL 中如何解决深度分页的问题?(中)

解决 MySQL 深度分页(大 OFFSET)性能问题的核心在于避免扫描和丢弃大量无用记录。首选方案是延迟关联:通过子查询利用覆盖索引快速定位目标页的主键 ID(SELECT id ... LIMIT offset, size),再通过主键精确关联回表获取完整数据行(SELECT * FROM ... JOIN ...),大幅减少 I/O。对于连续分页(如无限滚动),基于书签(上一页末记录值)查询WHERE order_col > bookmark LIMIT size)效率最高,但无法跳页。业务上可限制最大页码或优化交互。在数据量极大时,分库分表是终极方案。

拓展:

I.视频讲解:limit 10000000深分页为什么慢,怎么优化?_哔哩哔哩_bilibili

II.具体解决

  1. 延迟关联 / 子查询优化 (最常用且推荐):

    • 思路: 先利用覆盖索引快速定位到目标分页的主键 ID 集合,再根据这些 ID 回表查询完整数据行。

    • SQL 示例:

      SELECT * FROM your_table AS t1
      JOIN (
          SELECT id FROM your_table
          WHERE [some_condition] -- 可选,利用索引过滤
          ORDER BY [order_column] -- 必须与主查询一致
          LIMIT 1000000, 20      -- 大偏移量
      ) AS t2 ON t1.id = t2.id
      ORDER BY t1.[order_column]; -- 通常需要再次排序,但数据量很小
    • 为什么有效:

      • 子查询 SELECT id ... 通常只需要扫描索引(尤其是覆盖索引),避免了回表操作,扫描 offset + size 行的代价显著降低(索引文件比数据文件小得多)。

      • 外层查询 SELECT * ... JOIN ... 只需要根据 20 个 ID 进行精确查找(主键查找非常快)。

    • 关键: 确保 ORDER BY 和 WHERE 子句能高效利用索引。通常需要在 (order_column, id) 或 (filter_column, order_column, id) 上建立索引。

  2. 基于“书签”记录位置 (适用于连续分页):

    • 思路: 记录上一页最后一条记录的排序字段值(和唯一标识,如 ID),下一页直接查询排序字段值大于(或小于)该“书签”的记录。

    • SQL 示例 (下一页):

      SELECT * FROM your_table
      WHERE [some_condition]
        AND [order_column] > [last_value_of_prev_page] -- "书签"
        -- AND id > [last_id_of_prev_page] -- 如果 order_column 可能重复,加上唯一ID条件
      ORDER BY [order_column] ASC
      LIMIT 20;
    • SQL 示例 (上一页类似):

      SELECT * FROM your_table
      WHERE [some_condition]
        AND [order_column] < [first_value_of_current_page]
        -- AND id < [first_id_of_current_page]
      ORDER BY [order_column] DESC
      LIMIT 20
      ORDER BY [order_column] ASC; -- 可能需要反转结果
    • 为什么有效: 完全避免了 offset,只需基于索引查找指定范围的下 size 条记录,效率极高(type=range)。

    • 缺点:

      • 无法直接跳转到任意页码(如第 100 页),只能“上一页”、“下一页”导航。

      • 需要客户端维护“书签”状态(上一页/下一页的边界值)。

      • 如果排序字段值不唯一,需要额外处理(通常结合唯一 ID)。

  3. 业务妥协方案:

    • 限制最大可访问页码/偏移量: 避免用户请求过深的分页(如只允许查看前 100 页)。

    • 近似分页 / 预加载:

      • 使用 COUNT(*) 估算总行数(可能不精确)。

      • 只加载前 N 页(如 10 页)到缓存/前端,用户翻页时快速响应。超出范围提示或使用其他方式。

    • 优化数据展示: 提供更好的搜索/筛选功能,减少用户到达深度分页的需求。

  4. 终极方案:分库分表:

    • 思路: 当单表数据量极其庞大(亿级以上),深度分页成为系统性瓶颈时,考虑水平分片(Sharding)。将数据分散到多个物理库/表中。

    • 影响:

      • 分页逻辑变复杂(可能需要在每个分片查询后合并结果)。

      • 显著增加架构复杂度和维护成本。

    • 适用场景: 超大规模数据,其他优化手段已无法满足性能要求。

8.什么是 MySQL 的主从同步机制?它是如何实现的?(中)

MySQL 主从同步(Replication)是将主库(Master)的数据变更同步到一个或多个从库(Slave)的过程。

MySQL 主从同步基于异步复制机制实现,核心是利用主库的二进制日志 (Binlog) 记录数据变更。从库通过 I/O 线程 拉取主库 Binlog 并写入本地中继日志 (Relay Log),再由 SQL 线程 重放 Relay Log 中的事件使数据最终一致。其核心价值在于读写分离提升性能、提供数据冗余保障高可用。默认异步复制存在延迟,可通过半同步复制增强数据安全,或组复制实现强一致。

拓展:

I.核心组件

1)二进制日志(Binlog)

  • 作用:主库记录所有数据变更的日志(DDL/DML)

  • 格式

    • STATEMENT:记录SQL语句(可能因函数导致主从不一致)

    • ROW(推荐):记录数据行变化(默认格式,安全)

    • MIXED:混合模式

2)主库线程

  • Binlog Dump Thread

    • 每个从库对应一个线程

    • 将Binlog推送给从库的I/O线程

3)从库线程

线程类型职责关键参数
I/O Thread接收主库Binlog → 写入Relay Logrelay_log
SQL Thread读取Relay Log → 重放SQLrelay_log_recovery

4)中继日志(Relay Log)

  • 从库的临时存储:结构与Binlog相同

  • 作用:解耦网络传输与SQL重放

II.主从同步全流程

  • 主库数据变更

    UPDATE users SET balance=100 WHERE id=1; -- 生成Binlog(ROW格式)

    Binlog内容示例:

    ### UPDATE `test`.`users`
    ### WHERE
    ###   @1=1 /* INT meta=0 nullable=0 */
    ###   @2='Alice' /* STRING(20) meta=65044 nullable=0 */
    ###   @3=50 /* INT meta=0 nullable=1 */
    ### SET
    ###   @3=100 /* INT meta=0 nullable=1 */
  • 从库拉取日志

    • I/O Thread 通过 COM_BINLOG_DUMP 命令请求Binlog

    • 主库通过 Binlog Dump Thread 推送日志

  • 日志中继存储

    • I/O Thread 将收到的Binlog写入 relay-log.000002

    • 更新 master.info 文件记录读取位置

  • 从库重放日志
    SQL Thread 顺序执行Relay Log中的事件:

    UPDATE users SET balance=100 WHERE id=1; -- 在从库执行
  • 状态同步

    • 从库更新 relay-log.info 记录重放位置

    • 主库通过 SHOW SLAVE STATUS 监控延迟

III.主从同步模式对比

同步模式原理数据安全性性能应用场景
异步复制主库提交后立即响应客户端可能丢数据最高非金融业务
半同步复制至少一个从库确认后才响应较高中等通用生产环境
组复制(MGR)基于Paxos协议的多主一致性最高较低金融级高可用

9.如何处理 MySQL 的主从同步延迟?(中)    

主从延迟是不可避免的,只能尽量降低延迟。

1)二次查询:当从库查找不到数据时,回到主库查找,但是这相当于把压力转移到了主库上,当有大量的查询找不到数据时,会对主库造成冲击。

2)将写入后需要马上读的操作转移到主库:这相当于把操作写死了,不推荐。

3)关键事务在主库查询:只有比较关键的数据在主库查询,非关键数据读写分离。

4)将数据存储到缓存:可以先通过缓存来读取数据,但是容易引起不一致的问题。

拓展:

MySQL 主从延迟的常见原因及优化方案

原因分类具体表现优化方案
从库单线程复制SQL Thread 单线程重放Binlog导致积压- 启用多线程复制(MTS):
sql<br> slave_parallel_workers = 8; # 并行线程数<br> slave_parallel_type = LOGICAL_CLOCK;<br>
- MySQL 8.0+ 使用 WRITESET 并行复制
网络延迟跨机房/跨地域部署时网络抖动- 主从同机房部署(延迟≤0.1ms)
- 使用专线或VPN隧道
- 启用Binlog压缩:
ini<br> slave_compressed_protocol = ON;<br>
从库性能不足从库硬件配置低于主库,I/O或CPU瓶颈- 升级为与主库同规格硬件(SSD/NVMe)
- 调整内存参数:
ini<br> innodb_buffer_pool_size = 12G; # 物理内存的70%<br>
- 隔离从库读流量
长事务主库大事务阻塞Binlog传输,从库重放耗时- 拆分事务(如每1000行提交一次)
- 避免 ALTER TABLE 直接操作大表
- 设置事务超时:
sql<br> SET SESSION max_execution_time = 30000; # 30秒超时<br>
从库数量过多主库需为每个从库维护Binlog Dump线程,消耗资源- 控制从库数量(建议≤5个)
- 使用级联复制架构:
<br> 主库 → 中继从库 → 二级从库<br>
- 非关键业务从库设置 delay=1h
从库读负载过高从库处理大量查询,挤占SQL Thread资源- 增加从库实例横向扩展
- 使用ProxySQL路由读请求
- 缓存热点数据(Redis)
- 识别并优化慢查询:
sql<br> SELECT * FROM slow_log WHERE exec_time > 2s;<br>

10.什么是分库分表?分库分表有哪些类型(或策略)?(易)

分库分表是将一个数据库/表的数据分散存储到多个数据库/表中的技术方案,主要解决单点性能瓶颈(如数据量过大、高并发、磁盘 I/O 限制),提升系统扩展性和查询效率

可以分为垂直拆分和水平拆分两种类型。垂直拆分(按业务维度)是按字段维度拆分,不同业务字段分离到不同表/库,水平拆分(按数据维度)是按数据行拆分,相同表结构分布在多个库/表。

拓展:

I.拆分原则

  • 先垂直后水平

  • 单表不超过500万行

  • 分片键选择高基数字段

II.垂直拆分

1)垂直分表

-- 原始用户表
CREATE TABLE users (
    id BIGINT,
    name VARCHAR(50),
    age INT,
    credit_card_no VARCHAR(20),  -- 敏感信息
    login_history JSON,          -- 大字段
    PRIMARY KEY(id)
);

-- 拆分后
CREATE TABLE users_basic (
    id BIGINT,
    name VARCHAR(50),
    age INT,
    PRIMARY KEY(id)
);

CREATE TABLE users_private (
    user_id BIGINT,              -- 关联字段
    credit_card_no VARCHAR(20),
    login_history JSON
);

适用场景

  • 表字段超过50个

  • 存在不常用大字段(如TEXT/BLOB)

  • 需要冷热数据分离

2)垂直分库

优势

  • 业务解耦(不同微服务对应不同库)

  • 降低单库连接数压力

III.水平拆分

1)水平分表

-- 原始订单表
CREATE TABLE orders (
    id BIGINT,
    user_id INT,
    amount DECIMAL(10,2),
    create_time DATETIME
);

-- 按月分表
CREATE TABLE orders_202301 (
    id BIGINT,
    user_id INT,
    amount DECIMAL(10,2),
    create_time DATETIME
) PARTITION BY RANGE (MONTH(create_time)) (
    PARTITION p1 VALUES LESS THAN (2),
    PARTITION p2 VALUES LESS THAN (3)
);

分片策略

策略实现方式优缺点
范围分片按ID范围/时间区间易扩容,但可能热点不均匀
哈希取模user_id % 1024数据均匀,但扩容需要迁移
一致性哈希虚拟节点环扩容影响小,实现复杂
目录分片路由表维护映射关系灵活但需维护元数据

2)水平分库

典型分片键选择

  • 用户ID(保证同一用户数据在同一库)

  • 订单ID(离散分布)

  • 地理区域(如华北、华东库)

本文到此结束,如果对你有帮助,可以点个赞~

后续会在合集里持续更新 MySQL 相关的面试题,欢迎关注~

祝各位都能拿到满意的offer~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值