SQL优化简单思路

1. 背景

在实际生产中,因为SQL较慢、SQL关联不合理、不了解索引的性质、不熟悉mysql执行计划分析,可能会出现一些生产事故,本文会简单说明SQL通常的优化分析思路。
基本的优化原则:

  1. 先优化SQL
  2. 再优化mysql server
  3. 最后优化硬件

2. 优化sql从执行计划开始

执行计划是mysql语句优化中最长使用的方式。相比于开发人员依赖经验、感觉进行优化,explain将会提供精确的分析结论。
其使用非常简单:

explain select data from A where id = "asdaqsdqw"

explain后面加上你想要分析的语句,执行即可。
重点在于执行计划结果的分析。
来看一个典型的explain结果:
explain典型结果

2.1 table字段

2.1.1 分析

涉及到的表。一般而言,只涉及一张表就只有一行,但涉及多张表如n张表时,则至少有n行,因为多表join的过程可能出现中间临时表,这也是表,虽然不会出现在sql中,但执行时是会计算在执行计划内的。
对于inner join情况,先出现的表是驱动表,后出现的表是被驱动表。

2.1.1.1 驱动表

驱动表(Driving Table) 是作为外层循环的表,被驱动表(Driven Table)是作为内层循环的表。选择合适的驱动表能显著减少连接的总计算量,核心原则是:“小表驱动大表”(用数据量更小的表作为驱动表)

理由:
总操作次数 ≈ 驱动表行数 × 被驱动表中每行的匹配次数。
若驱动表是小表(如 100 行),被驱动表是大表(如 100 万行):总操作次数约为 100 × 平均匹配次数。
若反过来(大表驱动小表):总操作次数约为 100万 × 平均匹配次数,是前者的 1 万倍。
显然,小表作为驱动表能大幅减少外层循环次数,降低总计算开销。
伪代码辅助理解:

#伪代码:嵌套循环连接
for 驱动表中的每一行 a in A:for 被驱动表中匹配 a 的行 b in B:输出 (a, b)

所以,explain的结果,行数越少越好,用于驱动的表规模越小越好。
注意,这里的 “小” 不是指表的原始大小,而是 “经过 WHERE 过滤后参与连接的行数”,比如A表原本有100万数据,但谓词下推过滤后剩30条,那么驱动规模按30计算。

此外,被驱动表中,即使一个字段被加了索引,也很有可能在查询中不生效。
比如:
A表内连接B表,在where子句中使用B表的create_time作为时间范围限制,B.create_time已经加了索引,但为什么这个索引没生效?
因为MySQL 会先扫描 A表 的行,再逐行到 B表 中匹配连接条件(如 A.id = B.a_id)。此时,WHERE B.create_time 的过滤条件可能在 连接之后 执行(即先匹配所有满足连接条件的行,再过滤时间范围),导致索引失效,退化为普通全集扫描。
这个要点在后面线上事故分析时会看到实际案例。

2.1.2 使用

多数数据库(如 MySQL、PostgreSQL、SQL Server)的优化器会根据表的统计信息(行数、索引分布等)自动选择 “成本最低” 的驱动表,无需人工干预。
但在以下场景可能需要手动指定:

  • 统计信息过时(如刚批量插入数据未更新统计信息),导致优化器误判表大小(可以执行ANALYZE TABLE Table_name来解决);
  • 复杂多表连接(3 张以上),优化器可能因计算复杂度选择次优顺序。
    手动指定方式(以 MySQL 为例):
    用 STRAIGHT_JOIN 强制左表为驱动表:
-- 强制 A 作为驱动表,B 作为被驱动表
SELECT * FROM A STRAIGHT_JOIN B ON A.id = B.a_id;

2.2 type字段(重要)

type 字段 描述了 表的访问方式(即 MySQL 如何查找表中的行),是判断查询效率的核心指标之一。type 的值从 “性能最优” 到 “性能最差” 有明确的优先级,直接反映了查询对索引的利用效率
type 可能出现的值及含义(按性能从优到劣排序)

  1. system:表中只有一行数据(系统表),是 const 类型的特例,几乎不会在生产中出现
  2. const:通过主键或唯一索引的 等值查询 匹配到一行数据(最多一行),唯一主键且不join时会有
  3. eq_ref:在 多表 JOIN 中,被驱动表的连接列是 主键或唯一非空索引,且每个驱动表的行只能匹配到被驱动表的一行
  4. unique_subquery:子查询中使用了唯一索引,替代了 eq_ref 的 JOIN 方式(适用于 IN 子查询),一般认为和eq_ref效率基本一致
  5. ref:非唯一索引的等值查询,或 JOIN 中被驱动表的连接列是 非唯一索引,可能匹配到多行
  6. ref_or_null:类似 ref,但额外包含对 NULL 值的查询(即条件中包含 IS NULL)
  7. index_merge:MySQL 使用 索引合并优化,即同时使用多个单列索引,将结果合并(交集 / 并集)后返回
  8. range:使用索引进行 范围查询,只检索索引中某一范围内的行,范围越小(匹配行数越少)效率越高。若范围过大(接近全表),可能退化为全表扫描
  9. index:扫描 整个索引树(而非全表),但未利用索引筛选,仅通过索引覆盖数据(即 “索引全扫描”)
  10. ALL:全表扫描(Full Table Scan),MySQL 会遍历整个表的所有行来查找匹配的记录,最差的情况

目标是至少确保 type 达到 range 级别,最好能达到 ref 或更高。若出现 ALL 或 index,需检查是否缺少索引、索引失效或查询条件不合理,及时优化。

2.3 possible_keys字段

mysql执行计划预先判断某一次查询可能涉及的索引,如果你期望命中的索引没有命中,可以先在这里看你的索引是否被排除在预期之外,并据此优化。
有时mysql的执行计划如果只看keys字段,看起来好像有索引使用,但其实如果把possible_keys和keys连起来看,那么就会发现possible_keys为null时,即使keys有值也不一定走索引。

2.4 key字段(重要)

关键字段,代表实际命中的索引。如果为null则代表没有命中索引。要结合possible_keys字段一起分析。

2.5 ref字段

ref 字段用于描述:在使用索引查找匹配行时,哪些 “值”(可能是列名、常量或函数结果)被用来与索引列进行比较。它直观地展示了 MySQL 如何利用索引来定位符合条件的行,是理解索引匹配逻辑的重要依据

  1. 取值为 常量(如 const、具体值),当前表的索引列与常量进行等值匹配 时,ref 会显示为 const 或具体的常量值,说明用 “常量” 与索引列比对
  2. 取值为 其他表的列名:在 多表 JOIN 场景中,当被驱动表的索引列与驱动表的某列进行匹配时,ref 会显示驱动表的 “表名。列名”,说明用驱动表的列值与被驱动表的索引列比对。
  3. 取值为 函数或表达式结果:当查询条件中,索引列与函数 / 表达式的结果比对时,ref 会显示函数或表达式的相关信息(不同 MySQL 版本可能显示方式不同)
  4. 取值为 NULL:特殊情况下(如 type 为 index 或 ALL 时),ref 可能为 NULL,表示没有使用 “具体值” 与索引列比对(可能是全索引扫描或全表扫描,无需比对值)
    总而言之,这个字段是key字段的延展,用来告诉你你的索引和什么进行比对

2.6 rows字段

rows 表示 MySQL 优化器估计需要扫描的行数(即 “预计要检查多少行才能找到符合条件的记录”),rows 值越小,说明查询需要检查的行数越少,效率通常越高

2.7 filtered字段

filtered 表示经过表级条件过滤后,剩余行所占的百分比(取值范围 0~100),filtered = (满足条件的行数 / 扫描的行数) * 100

2.8 Extra字段(重要)

Extra 字段用于提供查询执行计划的额外细节信息,补充 type、ref 等字段未涵盖的执行逻辑(如索引使用细节、排序方式、临时表使用等)。它是判断查询是否需要优化的关键依据,许多 Extra 值直接反映了查询的性能瓶颈(如是否需要临时表、是否需要额外排序等)。

  1. Using index:使用了覆盖索引(Covering Index),即查询所需的所有列(SELECT 后的列 + WHERE/JOIN 条件的列)都包含在索引中,无需回表访问主键索引获取数据,性能极佳,是索引优化的理想状态,避免了回表的 IO 开销
  2. Using where:MySQL 使用了 WHERE 条件过滤数据,但未使用索引(或虽使用索引但索引未覆盖所有过滤条件,需在服务器层进一步过滤),性能:若 type 为 ALL(全表扫描),则性能较差,需添加索引;若 type 为 range 等,需结合具体场景优化
  3. Using index condition:启用了索引条件下推(Index Condition Pushdown, ICP) 优化,将部分 WHERE 条件下推到存储引擎层,在索引扫描时直接过滤不符合条件的行,减少回表次数
  4. Using temporary:MySQL 需要创建临时表来存储中间结果(如用于 GROUP BY、DISTINCT 或 JOIN 时的排序 / 去重),性能较差,临时表(尤其是磁盘临时表)会增加 IO 和内存开销,数据量大时可能导致严重性能问题。必须优化(如为 GROUP BY 列添加索引)
  5. Using filesort:MySQL 需要对结果进行额外排序(无法利用索引的自然顺序完成排序),排序操作可能在内存或磁盘中进行(数据量大时会写入临时文件),性能较差,排序是 CPU 密集型操作,数据量大时会严重影响性能。需优化(如为 ORDER BY 列添加索引,或调整排序顺序与索引一致)
  6. Using filesort with small result:类似 Using filesort,但排序的结果集很小,排序开销较低(MySQL 认为无需优化)
  7. Using join buffer (Block Nested Loop) 或 Using join buffer (Batched Key Access):JOIN 操作中,被驱动表无可用索引,MySQL 使用连接缓冲区(Join Buffer)存储驱动表的数据,再与被驱动表的行逐行比对(嵌套循环连接),性能极差,无索引的 JOIN 是性能杀手,数据量大时需为连接列添加索引
  8. Range checked for each record (index map: N):MySQL 无法确定哪个索引更高效,因此对驱动表的每一行,都会检查被驱动表的多个索引(index map: N 表示候选索引的位图),选择当前行最匹配的索引。可以尝试手动执行此语句解决:ANALYZE TABLE
  9. Impossible WHERE:WHERE 条件永远为 FALSE,MySQL 无需扫描表(如 WHERE 1=0),性能极高但毫无意义
  10. No tables used:不涉及表查询,比如select 1+1

总结而言:

  • Using temporary:避免临时表,通过索引优化 GROUP BY/DISTINCT;
  • Using filesort:添加排序索引,或调整 ORDER BY 顺序与索引一致;
  • Using join buffer:为 JOIN 的 ON 条件列添加索引;
  • Using where 且 type=ALL:为过滤列添加索引,避免全表扫描。

3. Mysql索引

特别地,任何索引在经过函数计算后都会失效。

3.1 主键索引

唯一标识表中的记录,不允许重复,且不允许为 NULL,每个表只能有一个主键索引。
InnoDB 中,主键索引是聚簇索引(叶子节点直接存储整行数据),性能最优。
执行计划:

{
"explain\r\nselect * from A where id = 1944811872387059713": [{"id" : 1,"select_type" : "SIMPLE","table" : "A","partitions" : null,"type" : "const","possible_keys" : "PRIMARY","key" : "PRIMARY","key_len" : "8","ref" : "const","rows" : 1,"filtered" : 100.0,"Extra" : null}
]}

3.2 唯一索引

确保索引列的值唯一(允许 NULL,但 NULL 只能出现一次)。Mysql非主键索引中最高效的键,它在建表时表达为UNIQUE,可以唯一定位一条数据。
对于唯一键,建议的使用方式只有一种,就是等于。
举例:
A表的唯一键是id,那么查询时应当:
select data from A where id = xxx
其执行计划表达为:

{
"explain\r\nselect * from service_entity where service_address=\"127.0.0.1\"": [{"id" : 1,"select_type" : "SIMPLE","table" : "service_entity","partitions" : null,"type" : "const","possible_keys" : "UKjety3jc210qsbnuij9yl61nf2","key" : "UKjety3jc210qsbnuij9yl61nf2","key_len" : "1022","ref" : "const","rows" : 1,"filtered" : 100.0,"Extra" : null}
]}

特别地,如果此唯一索引为int或bigint,且符合严格递增,那么我们遇到深分页问题时,可以尝试用唯一索引的范围来优化查询,这同样也适用在主键索引为数值的情况:

select id from A where id >= xxx and id < yyy and ...

其执行计划表达为:

{
"explain\r\nselect * from test_a where connections > 1 and connections <= 3\r\n\r\n\r\n": [{"id" : 1,"select_type" : "SIMPLE","table" : "test_a","partitions" : null,"type" : "range","possible_keys" : "idx_conn","key" : "idx_conn","key_len" : "5","ref" : null,"rows" : 2,"filtered" : 100.0,"Extra" : "Using where; Using index"}]
}

此处我们可以看到Extra中仍然存在using where,这是因为除了范围没有使用别的索引,如果查询还有其他索引,则还有优化空间

3.3 时间索引

时间索引一般而言使用模式比较固定,例如create_time,大多数场合都是create_time > xxx and create_time < yyy的使用模式。
不过也有一些细节可以注意。
看下面的SQL

select data from A where create_time >= '2025-09-01 00:00:00' and create_time < '2025-09-02 23:59:59'

这是我们在实际生产中见到的一类写法,这一类写法没有什么大问题,在必须精确到毫秒数的场合一定是这种写法。
其执行计划表现为:

{
"explain\r\n select * from A where create_time >= '2025-09-01 00:00:00' and create_time < '2025-09-02 23:59:59'": [{"id" : 1,"select_type" : "SIMPLE","table" : "A","partitions" : null,"type" : "range","possible_keys" : "timer_cre","key" : "timer_cre","key_len" : "4","ref" : null,"rows" : 45534,"filtered" : 100.0,"Extra" : "Using index condition; Using MRR"}
]}

特别地,Extra 列出现 “Using MRR” ,表示优化器启用了 MRR(Multi-Range Read,多范围读取)优化策略。这是 MySQL 针对 “二级索引查询 + 回表” 场景设计的性能优化手段,核心目标是将 “随机 IO” 转化为 “顺序 IO” ,减少磁盘 IO 开销,提升查询效率。总之,是一种比using where更优的表现。

如果是限定时间范围查询,比如某些场合已经预先确定一次查询必然查且只查一个自然日,那么更好的写法是:

select data from A where create_time = '2025-09-02'

其执行计划表现为:

{
"explain\r\nselect * from async_main_task_0 where create_time = \"2025-09-09\"\r\n\r\n\r\n": [{"id" : 1,"select_type" : "SIMPLE","table" : "async_main_task_0","partitions" : null,"type" : "ref","possible_keys" : "timer_cre","key" : "timer_cre","key_len" : "4","ref" : "const","rows" : 1,"filtered" : 100.0,"Extra" : null}
]}

3.4 字符串索引

这是实际生产中最常见到的一类索引,这一类索引不像数字索引那样可以简单的比较大小,其效率极大地取决于用户的使用方式。

3.4.1 最高效的方式

当然是直接等于:

select data from A where trace_id = "asdqwdzscqwfcqf"

其执行计划表现为:

{
"explain\r\n\r\nselect * from A where task_id = \"47f8fc36febb44f8afb5d08240bf4868\"": [{"id" : 1,"select_type" : "SIMPLE","table" : "A","partitions" : null,"type" : "ref","possible_keys" : "idx_task_id","key" : "idx_task_id","key_len" : "1022","ref" : "const","rows" : 1,"filtered" : 100.0,"Extra" : null}
]}

3.4.2 次优的方式

左like:

select data from A where trace_id like "asdwd%"

其执行计划表现为:

{
"explain\r\n\r\nselect * from A where task_id like \"47f8fc36febb44f8afb5d08240bf4%\"": [{"id" : 1,"select_type" : "SIMPLE","table" : "A","partitions" : null,"type" : "range","possible_keys" : "idx_task_id","key" : "idx_task_id","key_len" : "1022","ref" : null,"rows" : 1,"filtered" : 100.0,"Extra" : "Using index condition"}
]}

3.4.3 不生效的方式

  1. 两侧like: select data from A where trace_id like “%asdqwd%”
  2. 对列做函数计算:select data from A where len(trace_id) > 100
    以上两种,绝对不要在大数据量、大QPS场合出现必死。

3.5 联合索引

3.5.1 联合索引的建立

联合索引涉及到多个字段,由于mysql基础数据结构的限定,必须要在建立索引时就决定好从左到右的顺序。
以A表为例,如果其包含字段x,y,z,j,k,l,当我们想使用联合索引时,就必须预先分析下面两个问题:

  1. 要用哪些字段加入到联合索引
  2. 这些索引的排序
    这两个问题极其重要,会极大影响索引的过滤效果。
    下面给出一些分析标准来确定哪些字段应当加入联合索引:
  • 区分度极高(如各种ID),且固定出现在where条件里
  • 可以大规模过滤掉非目标数据(如时间段,如各种数值区间)
  • 在条件查询的where中高频出现
  • 绝不参与任何函数计算

假如根据上面的标准,我们筛选出x,z,j三个作为联合索引,那么排序应当按照如下原则从左到右排列:

  • 固定出现的查询条件
  • 高频出现的查询条件
  • 可以过滤更多数据的查询条件
  • 未必会出现的查询条件

比如,z条件必定出现在每次查询中,则z排第一位;x大概率出现,且为id字段,则排第2位;j不长出现,则排第三位,那么创建索引的语句为:

create index `udx_z_x_j` on A (z,x,j)

一定要注意,越靠左的索引越重要,使用联合索引查询时,左边的索引一旦缺失,整个查询效率会有巨幅下降。
创建该索引后,只要z,x出现,那么j不出现,也至少可以走两个索引。

3.5.2 联合索引的查询

查询过程务必遵守创建联合索引时的左右顺序,尽最大可能保证建立索引时左边的索引在where中出现:

select data from A where z="asdqdqw" and x like "niqdwnu%"

如果没有遵循这个原则,那么从左侧哪一个查询条件开始缺失,就会从哪一个索引开始失效。例如:

select data from A where x = 'aedqw'

看起来好像使用了联合索引中的x,但由于最左的z缺失,所以整个联合索引都失效了。
实际SQL:

-- test_jpa.test_union_index definitionCREATE TABLE `test_union_index` (`id` bigint(20) NOT NULL,`task_type` varchar(128) NOT NULL COMMENT '任务分类:智批改任务,划题任务,直接干预任务等',`task_status` varchar(32) NOT NULL COMMENT '任务状态,INIT:初始化,DOING:进行中,FAILED:失败,SUCCEED:成功',`task_stage` varchar(32) NOT NULL COMMENT '任务阶段,LAYOUT_ANALYSIS:版面分析,GET_TOPIC:取题,RECOGNIZATION:识别,CORRECTION:批改,INTERENTION:干预',PRIMARY KEY (`id`),KEY `udx_type_status_stage` (`task_type`,`task_status`,`task_stage`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;explain
select * from test_union_index
where task_status="STATUS_092" andtask_stage = "STAGE_092"

实际的执行计划为:

{
"explain\r\nselect\r\n\t*\r\nfrom\r\n\ttest_union_index\r\nwhere\r\n\t\r\n\ttask_status=\"STATUS_092\" and\r\n\ttask_stage = \"STAGE_092\"": [{"id" : 1,"select_type" : "SIMPLE","table" : "test_union_index","partitions" : null,"type" : "index","possible_keys" : null,"key" : "udx_type_status_stage","key_len" : "774","ref" : null,"rows" : 10119,"filtered" : 1.0,"Extra" : "Using where; Using index"}
]}

看似type为index好像走了索引,但实际上possible_keys为null,实际扫描的行数10119,就是全表的行数,根本未走索引。
而真正命中索引的效果为:

{
"explain\r\nselect\r\n\t*\r\nfrom\r\n\ttest_union_index\r\nwhere\r\n\t\r\n\ttask_type=\"TYPE_092\"": [{"id" : 1,"select_type" : "SIMPLE","table" : "test_union_index","partitions" : null,"type" : "ref","possible_keys" : "udx_type_status_stage","key" : "udx_type_status_stage","key_len" : "514","ref" : "const","rows" : 1,"filtered" : 100.0,"Extra" : "Using index"}
]}

3.5.3 合理使用联合索引避免回表

mysql中同一个索引涉及到的内容维护在同一棵索引树,如果要查询的字段包含在索引里且索引生效,那么就可以避免查询到索引后还需要回查真实数据。

3.6 多种索引冲突

有时SQL太过于复杂,join的表太多,索引之间会有冲突,未必真就会命中预期的索引。这种时候需要从未加索引的状态开始,一点点加索引做测试,千万不要偷懒。

4. 跨表JOIN问题

4.1 使用join的基本原则

这是整个Mysql生产中最容易出问题的部分。这里提出如下一些基本思想:

  1. 首先判断是否只能使用join解决问题,而不能使用任何别的手段。只要还有别的更优方案,尽量避开join
  2. 如果一定要使用join,那么inner join优先其他join(内连接取交集,数据量相对小)
  3. 尽最大可能避免join超过2张表(多表情况,mysql执行器会产生优化障碍和驱动表选择障碍)
  4. Join on的字段一定要对两张表都是索引,必须要用等于,比如task_id对于A和B都是索引,那么select data from A inner B where A.task_id=B.taskId
  5. join后的表绝对不要写select *,一是未必需要全字段,二是不同表也许有同名字段,一旦和实体字段映射错了那么查询直接崩溃报错,三是有时候精确查询是可以省略回表的,全查则没有这种可能性
  6. 一定要注意A和B表连接的字段是否是同样的数据类型,比如A中taskId为varchar,B中为BIGINT,那么就会发生隐式转换,这是一种函数计算,会导致索引失效
  7. 一定要使用explain分析执行计划,多方比较,直到explain的所有行都走索引、Extra字段中几乎没有using temporary、using where情况才算优化完成

4.2 谓词下推

在数据库查询优化中,谓词下推(Predicate Pushdown) 是一种通过将过滤条件(谓词)尽可能提前应用来减少数据处理量的优化技术。其核心思想是:让数据在进入后续处理阶段(如连接、排序、聚合等)之前,就被过滤掉不需要的部分,从而降低整体计算和 IO 开销。

4.2.1 谓词

指查询中用于筛选数据的条件,通常出现在 WHERE、ON 或 HAVING 子句中,例如 age > 30、status = ‘active’ 等

4.2.2 下推

指将这些过滤条件从查询的 “上层”(如外层查询、聚合阶段)“推” 到 “下层”(如子查询、表扫描阶段、数据源读取阶段)执行,让过滤操作在数据处理的早期阶段完成

4.2.3 为什么需要谓词下推

没有谓词下推时,查询可能会先读取大量无关数据,再在后续阶段过滤,导致资源浪费。例如:
假设查询是 A和B join并查询A.a大于10,B.b小于100 的行:

-- 无优化时的执行逻辑(低效)
select * from A Join B on A.id = B.id where A.a > 10 and B.b < 100;

此时子查询会先返回表中所有行,再在外部过滤。
应该改为:

select * from (select *  from A  where a>10)A1 Join (select *  from B  where b<100)B1 on A1.id = B1.id;

4.3 不使用join的一些解决思路

4.3.1 从复杂join更替为多个简单查询,在代码中筛选

有时候一条语句实在太过复杂,没有优化的思路,那么也许可以试试把复杂sql拆成多个简单查询。
比如A和B和C表关联查询,A表的join字段需要做函数计算导致索引失效。
也许可以尝试先查一下A表,如果规模并不大,比如结果集只有几十条上百条,那不妨先查A,而后在代码中对A的结果做你想要做的函数计算,再把计算结果作为过滤条件参与到B和C的关联查询中。

4.3.2 将join改为where中的exists

比如需要查询A表中符合B表某些条件的数据,如果用join则涉及到关联,但如果替换为exists,mysql会自动优化为单表查询,直接优化掉一整张表的扫描,这比任何索引优化都强。

4.3.3 合理冗余字段

如果存储数据时预先考虑到join可能出现的情况,那么设计表时可以考虑冗余一些字段,比如原本要AjoinB来查询B中的一两个字段,那么有没有可能直接在A中添加这两个字段来避免JOIN呢?
现在随着固态硬盘的发展和降价,少量空间上的冗余并不耗费多少成本,但慢SQL一旦出现线上问题,损失可能比存储贵得多。

5. 分治策略

对于特别复杂的查询情况,涉及多表多条件,不妨试试看用分治的思想来应对。
核心思路是把一个统一大查询拆分成几种不同的分支情况,在代码里决定不同情况走不同查询,而后针对不同的查询专项优化。
比如某种查询,涉及到AB表关联查询,B表查询条件视情况发生变化,未必出现,最终的结果要按照A表的时间字段进行排序分页。
那么就可以分出两种情况:

  1. 不存在B表的查询条件
  2. 存在
    针对1情况,结合上面的知识,就可以比较容易的得出优化方案,首先把A表做一次查询和分页,记为小表再inner joinB表,只要A表查询中索引设计合理,该查询的整体效率可以堆到近乎于单表索引查询的程度。
    针对2情况,再针对B不同查询条件出现的可能性,针对性调整驱动表、联合索引等部分。
    这样,通过部分的优化,逐渐逼近整体的最优效果。

6. 涉及分库分表

6.1 选择优良的分库分表键

优良的分库分表键的标准:

  1. 分布均匀:分片键需能将数据尽可能均匀地分散到所有分库分表中,避免某几个库 / 表数据量过大(“数据倾斜”)或过小(资源浪费)。例:若用 “用户性别” 作为分片键,仅能分为 2 组,必然导致数据倾斜;而用 “用户 ID 哈希” 则可分散到 N 个分片。
  2. 匹配高频查询场景:分片键需与业务中最频繁的查询条件强关联,确保绝大多数查询能通过分片键直接定位到目标库 / 表,避免 “跨库全表扫描”。例:订单查询 90% 场景是 “按用户 ID 查询我的订单”,则优先用 “用户 ID” 作为分片键,而非 “订单创建时间”。
  3. 贴合用户需要:统计业务中 80% 以上的查询 SQL,提取WHERE条件中最常出现的字段(如用户 ID、订单 ID、租户 ID)。典型案例:电商订单表中,“用户 ID” 是高频查询字段(用户查自己的订单),适合作为分片键
  4. 实体唯一标识优先:这类字段天然具备 “唯一性” 和 “稳定性”,且与业务逻辑强绑定,便于理解和维护,如用户表的user_id、商品表的product_id
  5. 如若必须用多字段组成分库分表键,则建议数据双写,双表查询。例如:通过 “数据冗余”(如订单表按用户 ID 分片,同时冗余一份按时间分片的表)实现。理由:组合分片键会增加查询复杂度,且难以保证所有查询都能匹配组合条件,容易引发跨库表查询

6.2 确保查询携带分库分表键

如果热点查询没有携带分库分表键,会触发跨库跨表全扫描,这个操作的性能开销是线上高并发业务不可承受的

7. mysql服务端的配置(DBA操作部分)

有时查询语句本身囿于业务强需求已经没有太多优化空间,那么考虑调优服务端配置也是一种思路。
需要注意,这种调整相对复杂,影响比较广,一定要充分和DBA协商清楚;如果数据库存在多业务复用,那么所有相关业务方都需要纳入讨论。

7.1 InnoDB专项优化

7.1.1 InnoDB 缓冲池(innodb_buffer_pool_size)

缓存 InnoDB 表的数据、索引、插入缓冲等,是 MySQL 中最重要的内存配置。命中缓冲池的查询可直接从内存获取数据,无需读磁盘。
对于专用 MySQL 服务器,设置为系统总内存的 50%~70%(预留部分内存给操作系统和其他进程)

7.1.2 日志相关配置

  • innodb_log_buffer_size:事务日志(redo log)的内存缓冲区,满了会刷盘。
  • innodb_flush_log_at_trx_commit:控制事务日志的刷盘策略(平衡性能与安全性)。
    • 1(默认):每次事务提交立即刷盘,最安全但性能最低(适合金融等核心业务)。
    • 2:事务提交后写入操作系统缓存,每秒刷盘一次,安全性与性能平衡(推荐)。
    • 0:每秒刷盘一次,性能最高但可能丢失 1 秒内的数据(适合非核心业务)。
  • innodb_log_file_size:单个 redo log 文件的大小。可酌情提高至 512M~2G(需同时调整innodb_log_files_in_group,通常 2~3 个文件),减少日志切换频率(频繁切换会导致 IO 波动)。

7.1.3 IO 并发配置

  • innodb_read_io_threads 和 innodb_write_io_threads:控制 InnoDB 的读写 IO 线程数,提升 IO 并发处理能力。
  • innodb_flush_method:控制 InnoDB 与磁盘 IO 的交互方式(减少双重缓存)。建议:Linux 系统:O_DIRECT(绕过操作系统缓存,避免 InnoDB 缓冲池与 OS 缓存的重复缓存)。Windows 系统:async_unbuffered。

7.2 连接与排序缓存

  • join_buffer_size:用于表连接(JOIN)的缓存,非索引连接(ALL或index类型)会使用此缓存。
  • sort_buffer_size:用于ORDER BY或GROUP BY的排序缓存,不足时会使用磁盘临时文件排序。
  • read_buffer_size:用于顺序扫描(filesort)的缓存,提升全表扫描或大范围查询的效率。

7.3 临时表缓存

tmp_table_size 和 max_heap_table_size:控制内存临时表的最大大小(两者取较小值),超过则转为磁盘临时表(存储在tmpdir目录)。
默认值 16M,可酌情提高至 64M~256M(根据业务中临时表的使用频率调整),减少磁盘临时表的 IO 开销。

8. 硬件上的优化

有时软件上的优化已经没有更多空间,这时可以考虑从硬件层面下手把查询效率带上来。
需要注意,并非所有云厂商都可以指定mysql服务器的硬件,这一点需要预先确认是否可用以及预算是否支持。

8.1 选择更合适的CPU

8.1.1 Intel 至强 6 性能核处理器(OLTP 首选)

代表型号:Platinum 6787P(56 核 112 线程,全核睿频 3.5GHz,L3 缓存 140MB)
核心优势:
单核性能突出:全核睿频 3.5GHz,配合 Intel AVX-512 指令集,单线程执行效率比上一代提升 36%,尤其适合高并发事务中的单行查询、锁竞争场景。
硬件加速技术:内置数据保护与压缩加速技术(QAT),可将 SSL 加密和数据压缩卸载至硬件,减少 CPU 占用率 20%-30%。例如,MySQL 备份速度可提升 2.56 倍。
内存带宽优化:支持 8 通道 DDR5-6400 内存,带宽高达 204.8GB/s,比上一代提升 2.3 倍,显著缓解内存瓶颈。
服务器整合能力:单台服务器可替代 17 台旧代服务器,TCO 降低 87%,适合企业级集中式数据库部署。
典型场景:电商秒杀、金融交易、实时订单系统等需毫秒级响应的 OLTP 场景。

8.1.2 AMD EPYC 9004 系列(OLAP 首选)

代表型号:Genoa-X 9384X(48 核 96 线程,主频 2.4GHz,L3 缓存 1.152TB)
核心优势:
3D V-Cache 技术:堆叠式 L3 缓存容量达 1.152TB,是传统 CPU 的 3 倍,可将复杂分析查询的内存访问延迟降低 50% 以上,尤其适合需频繁扫描大表的 OLAP 场景。
多核并行处理:64 核 / 128 线程设计,配合 Zen4 架构的高 IPC(每周期指令数),在 sysbench 测试中 OLAP 性能比 Intel 同核数 CPU 提升 27%。
高扩展性:支持 12 通道 DDR5-4800 内存和 128 条 PCIe 5.0 通道,可直连高速 NVMe 存储或 GPU 加速卡,构建分析型数据湖。
能效比领先:5nm 工艺使功耗比上一代降低 30%,在高密度数据中心中每瓦性能比 Intel 高 60%。
典型场景:供应链分析、用户行为画像、金融风控等需多表关联和复杂聚合的 OLAP 场景。

8.2 选择更合适的硬盘

基本原则:

  1. 固态硬盘 > 机械硬盘
  2. 新一代固态硬盘 > 前代
  3. OLTP 用 PCIE更新的版本
  4. OLAP -> U.2 及以上
  5. 需求均衡 -> SAS 4.0 及以上

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/96694.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/96694.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软考 系统架构设计师系列知识点之杂项集萃(144)

接前一篇文章:软考 系统架构设计师系列知识点之杂项集萃(143) 第268题 甲、乙、丙、丁4人加工A、B、C、D四种工件所需工时如下表所示。指派每人加工一种工件,四人加工四种工件其总工时最短的最优方案中,工件B应由()加工。 A B C D 甲

P1168 中位数

题目描述给定一个长度为 N 的非负整数序列 A&#xff0c;对于前奇数项求中位数。输入格式第一行一个正整数 N。第二行 N 个正整数 A1…N​。输出格式共 ⌊2N1​⌋ 行&#xff0c;第 i 行为 A1…2i−1​ 的中位数。输入输出样例输入 #1复制7 1 3 5 7 9 11 6输出 #11 3 5 6输入 #…

【CE】图形化CE游戏教程通关手册

【CE】图形化CE游戏教程通关手册 文章目录【CE】图形化CE游戏教程通关手册导读需求1️⃣ 第一关提示操作总结2️⃣ 第二关&#xff08;代码共享&#xff09;提示操作验证3️⃣ 第三关提示提示总结导读 需求 除了Tutorial-x86_64.exe教程外&#xff0c;CE还提供了图形化教程gtu…

leetcode 2785. 将字符串中的元音字母排序 中等

给你一个下标从 0 开始的字符串 s &#xff0c;将 s 中的元素重新 排列 得到新的字符串 t &#xff0c;它满足&#xff1a;所有辅音字母都在原来的位置上。更正式的&#xff0c;如果满足 0 < i < s.length 的下标 i 处的 s[i] 是个辅音字母&#xff0c;那么 t[i] s[i] 。…

支付子系统架构及常见问题

支付流程对于支付系统来说&#xff0c;它最重要的其实是安全&#xff0c;所以整个支付流程采用秘钥加签的方式进行操作&#xff0c;一共四对秘钥&#xff0c;以支付宝在线支付为例子&#xff0c;首先通过RSA2算法生成商户公钥以及商户私钥&#xff0c;同时支付宝平台会提供支付…

内存传输速率MT/s

1 0 0 0 0 0 0 0 0 010 9 8 7 6 5 4 3 2 1十 亿 千 百 十 万 千 百 十 个亿 万 万 万传输速率 …

.env文件的作用和使用方法

目录 什么是 .env 文件&#xff1f; 为什么要使用 .env 文件&#xff1f;&#xff08;好处&#xff09; 如何使用 .env 文件&#xff1f; 通用步骤&#xff1a; 具体技术栈中的实现&#xff1a; 最佳实践和注意事项 总结 什么是 .env 文件&#xff1f; .env 文件&#x…

深度拆解 Python 装饰器参数传递:从装饰器生效到参数转交的每一步

在 Python 装饰器的学习中&#xff0c;“被装饰函数的参数如何传递到装饰器内层函数”是一个高频疑问点。很多开发者能写出装饰器的基本结构&#xff0c;却对参数传递的底层逻辑一知半解。本文将以一段具体代码为例&#xff0c;把参数传递过程拆成“装饰器生效→调用触发→参数…

【Vue2 ✨】Vue2 入门之旅 · 进阶篇(七):Vue Router 原理解析

在前几篇文章中&#xff0c;我们介绍了 Vue 的性能优化机制、组件缓存等内容。本篇将深入解析 Vue Router 的原理&#xff0c;了解 Vue 如何管理路由并进行导航。 目录 Vue Router 的基本概念路由模式&#xff1a;hash 和 history路由匹配原理导航守卫Vue Router 的路由过渡动…

Linux磁盘级文件/文件系统理解

Linux磁盘级文件/文件系统理解 1. 磁盘的物理结构 磁盘的核心是一个利用磁性介质和机械运动进行数据读写的、非易失性的存储设备。 1.1 盘片 盘片是传统机械硬盘中最核心的部件&#xff0c;它是数据存储的物理载体。盘片是一个坚硬的、表面极度光滑的圆形碟片&#xff0c;被安装…

【星海出品】rabbitMQ - 叁 应用篇

rabbitMQ 的基础知识这里就不阐述了,可以参看我早年写的文章 -> rabbitMQ 入门 https://blog.csdn.net/weixin_41997073/article/details/118724779 Celery 官网:http://www.celeryproject.org/ Celery 官方文档英文版:http://docs.celeryproject.org/en/latest/index.h…

C# 每个chartArea显示最小值、平均值、最大值

private void AddStatisticsAnnotations(ChartArea chartArea, int channelIndex) {RemoveExistingAnnotations(channelIndex);// 获取ChartArea的相对坐标&#xff08;百分比&#xff09;float chartAreaX chartArea.Position.X; // X坐标&#xff08;百分比&#xff09;floa…

打破“不可能三角”:WALL-OSS开源,具身智能迎来“安卓时刻”?

目录 引言&#xff1a;当“大脑”学会思考&#xff0c;机器人才能走出实验室 一、具身智能的“不可能三角”&#xff1a;机器人“大脑”的核心困境 二、WALL-OSS的四把重锤&#xff1a;如何系统性地破解难题&#xff1f; 2.1 第一锤&#xff1a;更聪明的“大脑”架构 —— …

SigNoz分布式追踪新体验:cpolar实现远程微服务监控

前言 SigNoz是一款开源的应用性能监控工具&#xff0c;专为微服务架构设计&#xff0c;集成了指标、追踪和日志分析功能。它能够全面监控分布式系统的性能&#xff0c;帮助开发团队快速定位问题根源。SigNoz支持OpenTelemetry协议&#xff0c;可以无缝集成各种编程语言和框架&…

python编程原子化多智能体综合编程应用(下)

上述代码实现了基于Mesa框架的诊断智能体类,包含以下核心功能: 模块化设计:通过类属性分离数据与行为,支持不同专科智能体的扩展 状态管理:实现idle/processing/error等状态转换,支持任务调度 诊断推理:集成机器学习模型,支持症状提取与多分类诊断 错误处理:包含模型加…

QT M/V架构开发实战:QSqlQueryModel/ QSqlTableModel/ QSqlRelationalTableModel介绍

目录[TOC](目录)前言一、初步介绍二、QSqlQueryModel1.基础定位2.特点3.核心接口4.典型用法5.优缺点三、QSqlTableModel1.基础定位2.特点3.核心接口4.典型用法5.优缺点四、QSqlRelationalTableModel1.基础定位2.特点3.核心接口4.典型用法 (示例&#xff1a;employees表有 dept_…

Terraform 从入门到实战:历史、原理、功能与阿里云/Azure 上手指南

前言&#xff1a;在云时代&#xff0c;企业的IT基础设施早已从“几台服务器”演变为“横跨多云的复杂网络、计算、存储集群”。但随之而来的&#xff0c;是管理复杂度的爆炸式增长&#xff1a;开发环境和生产环境不一致、手动配置容易出错、多云平台操作方式各异、资源变更难以…

【计算机网络 | 第10篇】信道复用技术

文章目录信道复用技术&#xff1a;高效利用通信资源的智慧方案一、频分复用&#xff08;FDM&#xff09;&#xff1a;按频率划分的并行通道二、时分复用&#xff08;TDM&#xff09;&#xff1a;按时间分割的轮流占用三、统计时分复用&#xff08;STDM&#xff09;&#xff1a;…

安卓13_ROM修改定制化-----禁用 Android 导航按键的几种操作

Android 设备的导航按键通常包括后退键(Back)、主页键(Home)和最近键(Recents),这些按键位于屏幕底部或设备实体区域。禁用导航按键可以帮助在特定应用场景(如信息亭模式或儿童锁模式)中限制用户操作。安卓设备上禁用底部虚拟导航键(返回、主页、多任务键)有多种方法…

通过S参数测量评估电感阻抗:第2部分

S21双端口分流和双端口串联方法 T这是两篇文章中的第二篇&#xff0c;专门讨论使用网络分析仪测量 S 参数进行电感阻抗评估主题。上一篇文章 [1] 描述了阻抗测量和计算S11使用单端口分流器、双端口分流器和双端口串联方法的参数。本文专门介绍阻抗测量和计算S21使用双端口分流…