MySQL索引. ref_MySQL索引类型(type)分析

本文详细解析了MySQL索引类型,包括system、const、eq_ref、ref、range、index和all,以及它们在查询优化中的作用。通过实例演示了不同索引类型在查询中的表现,帮助理解如何针对不同场景选择和优化索引。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

type索引类型:

ststem > const > eq_ref > ref > range > index > all

优化级别从左往右递减,没有索引的一般为’all’,需要对type进行优化前提是有索引。

其中’system’和’const’只是理想型,实际只能达到’ref’和’range’。

注意:这里主要针对MySQL5.6进行讲解,与其他版本有区别,但是原理过程一致

创建两张表teacher和teacherCard

# 教师表

create table teacher(

tid int(5),

tname varchar(20),

tcid int(5)

);

# 教师卡

create table teacherCard(

tcid int(5),

tcdesc varchar(20)

);

# 插入教师信息

insert into teacher values(1, 'tz', 1);

insert into teacher values(2, 'tw', 2);

insert into teacher values(3, 'tl', 3);

# 插入教师卡信息

insert into teacherCard values(1, 'tzdesc');

insert into teacherCard values(2, 'twdesc');

insert into teacherCard values(3, 'tldesc');

# 添加主键索引

alter table teacheradd constraint tid_pk primary key(tid);

1.system

衍生表只有一条数据的主查询;衍生表通过主键查询只有一条数据,再通过这条数据进行主查询。

# 子查询为主键查询

explain select * from (select * from test01 where tid = 1) t;

子查询通过主键查询得出一条数据(该条数据构成衍生表),通过衍生表的数据进行主查询,其衍生表的索引类型为system。

b2998771304c1bc5c628f86aaa3f4017.png

2.const

仅仅能查到一条数据的SQL,用于primary key 或 unique的索引(其他索引类型不属于)。

# 主键查询只有一条数据的情况,类型为const

explain select * from test01 where tid = 1;

910a0268956d088f9a0b558f243647a9.png

3.eq_ref

唯一性索引,表索引与另外表的主键进行关联,两张表之间每条数据要一一对应(每个都要一一对应,不能一个对应多个,不能没有对应),查询的数据在表中是唯一性,不能有重复。

# 给teacherCard添加主键

alter table teacherCard add constraint pk_tcid primary key(tcid);

# 对teacher表进行索引唯一查询

explain select t.tcid from teacher t, teacherCard tc where t.tcid = tc.tcid;

主表(没有外键的表)为eq_ref:

54f5effdb0adafe6b0ab6f2a5d8412c5.png

4.ref

非唯一线性索引,对于每个索引键的查询返回的数据为0或多条。

# 给teacher表的tname的字段添加索引

alter table teacher add index tname_index (tname);

# 根据tname = tz查询出两条数据

explain select * from teacher where tname = 'tz';

根据tname索引直接查询出来的值为ref类型。

57d9a9734ac5a2a1c9138b75440f7aef.png

5.range

检查指定范围行,where后面是一个范围查询(between、in、>、

# 查看range类型的索引

explain select * from teacher t where t.tid in (1, 2);

explain select * from teacher where tid between 1 and 2;

explain select * from teacher where tid < 3;

范围查询指定的索引,其索引类型为range:

fd7fe306e58a0180fd6c3d4a8a8f2b03.png

6.index

查询索引中的所有数据。

# 查询索引的所有数据,其中tname就是索引

explain select tname from teacher;

13ad003f83f53a65bbf54e3697849af4.png

7.all

查询表中的所有数据,或者根据不是索引的字段查询。

# 直接查询

explain select * from teacher;

type类型总结:

system/const:结果只有一条数据。

eq_ref:结果多条,但是每条数据都是唯一的。

ref:结果多条,但是查询到的数据可以是多条,且数据可以重复。

range:根究索引字段进行范围查询。

SELECT * FROM ( SELECT t.user_id, t.user_name, t.org_id, t.org_name, t.parent_org_name, t.full_org_name, t.position_name, concat_ws('_', t.org1_name, t.org2_name, t.org3_name) belong_org_name, IFNULL(t1.total, 0) AS gdjsTotal, IFNULL(t1.zcTotal, 0) AS gdjsZcTotal, IFNULL(t1.yjTotal, 0) AS gdjsYjTotal, IFNULL(t1.csTotal, 0) AS gdjsCsTotal, IFNULL(t2.total, 0) AS dctjTotal, IFNULL(t2.zcTotal, 0) AS dctjZcTotal, IFNULL(t2.yjTotal, 0) AS dctjYjTotal, IFNULL(t2.csTotal, 0) AS dctjCsTotal, IFNULL(t3.total, 0) AS dcfkTotal, IFNULL(t3.zcTotal, 0) AS dcfkZcTotal, IFNULL(t3.yjTotal, 0) AS dcfkYjTotal, IFNULL(t3.csTotal, 0) AS dcfkCsTotal, IFNULL(t4.total, 0) AS fassTotal, IFNULL(t4.zcTotal, 0) AS fassZcTotal, IFNULL(t4.yjTotal, 0) AS fassYjTotal, IFNULL(t4.csTotal, 0) AS fassCsTotal, IFNULL(t5.total, 0) AS khhfTotal, IFNULL(t5.zcTotal, 0) AS khhfZcTotal, IFNULL(t5.yjTotal, 0) AS khhfYjTotal, IFNULL(t5.csTotal, 0) AS khhfCsTotal, IFNULL(t6.gdpfTotal, 0) AS gdpfTotal, ( SELECT COUNT(*) FROM cchp_close_statistics_his WHERE ( gdpf_user_id = t.user_id OR gdjs_user_id = t.user_id OR dctj_user_id = t.user_id OR dcfk_user_id = t.user_id OR fass_user_id = t.user_id OR khhf_user_id = t.user_id) AND DATE_FORMAT(feedback_time, '%Y-%m') = '2025-01') AS total, ( SELECT COUNT(*) FROM cchp_close_statistics_his WHERE ( (gdjs_user_id = t.user_id AND gdjs_type = 1) OR (dctj_user_id = t.user_id AND dctj_type = 1) OR (dcfk_user_id = t.user_id AND dcfk_type = 1) OR (fass_user_id = t.user_id AND fass_type = 1) OR (khhf_user_id = t.user_id AND khhf_type = 1)) AND DATE_FORMAT(feedback_time, '%Y-%m') = '2025-01') AS zcTotal, ( SELECT COUNT(*) FROM cchp_close_statistics_his WHERE ( (gdjs_user_id = t.user_id AND gdjs_type = 2) OR (dctj_user_id = t.user_id AND dctj_type = 2) OR (dcfk_user_id = t.user_id AND dcfk_type = 2) OR (fass_user_id = t.user_id AND fass_type = 2) OR (khhf_user_id = t.user_id AND khhf_type = 2)) AND DATE_FORMAT(feedback_time, '%Y-%m') = '2025-01') AS yjTotal, ( SELECT COUNT(*) FROM cchp_close_statistics_his WHERE ( (gdjs_user_id = t.user_id AND gdjs_type = 3) OR (dctj_user_id = t.user_id AND dctj_type = 3) OR (dcfk_user_id = t.user_id AND dcfk_type = 3) OR (fass_user_id = t.user_id AND fass_type = 3) OR (khhf_user_id = t.user_id AND khhf_type = 3)) AND DATE_FORMAT(feedback_time, '%Y-%m') = '2025-01') AS csTotal FROM cchp_current_his_processor t LEFT JOIN ( SELECT gdjs_user_id, COUNT(*) AS total, COUNT(gdjs_type = 1 OR NULL) AS zcTotal, COUNT(gdjs_type = 2 OR NULL) AS yjTotal, COUNT(gdjs_type = 3 OR NULL) AS csTotal FROM cchp_close_statistics_his WHERE 这个sql数据一多就查询很慢,帮我优化下
03-08
<think>好的,我现在需要帮用户优化这个SQL查询。首先,我得仔细看一下用户提供的SQL语句,了解它的结构和各个部分的作用。用户提到当数据量大的时候,查询变得很慢,所以我要找出可能存在的性能瓶颈,并提出优化建议。 首先,整体来看,这个SQL主要是在统计各个用户在不同环节(gdjs、dctj、dcfk、fass、khhf)的处理数量,以及相关的分类统计(zcTotal、yjTotal、csTotal)。主查询是从cchp_current_his_processor表(别名为t)中获取用户信息,然后通过多个LEFT JOIN和子查询关联统计信息。 接下来,我需要分析每个子查询和JOIN操作的效率。注意到用户使用了多个子查询来统计不同条件下的总数,比如gdjsTotal、dctjTotal等,每个子查询都关联到不同的用户ID和类型。此外,还有四个子查询(total, zcTotal, yjTotal, csTotal)在主查询的SELECT列表中,这些子查询可能每次都会执行,导致性能问题。 首先,我想到的是这些子查询可能会重复扫描cchp_close_statistics_his表,尤其是在数据量大的情况下,多次全表扫描会导致性能急剧下降。例如,主查询中的四个子查询都针对同一个表,并且每个子查询都有不同的WHERE条件,这可能会导致大量的重复计算。 另外,LEFT JOIN的子查询t1到t6,每个都是对cchp_close_statistics_his表的不同聚合操作,同样可能多次扫描同一张表。这些子查询是否可以合并,以减少扫描次数? 接下来,我需要考虑索引的情况。如果cchp_close_statistics_his表中的相关字段(如gdjs_user_id、dctj_user_id等)没有索引,那么每次查询都需要全表扫描,这肯定会影响性能。例如,在t1子查询中,gdjs_user_id和gdjs_type可能需要建立联合索引,以加速COUNT操作。 另外,WHERE条件中的DATE_FORMAT(feedback_time, '%Y-%m') = '2025-01'可能会造成索引失效,因为使用了函数转换。如果feedback_time有索引,改成范围查询可能会更高效,比如feedback_time >= '2025-01-01' AND feedback_time < '2025-02-01',这样可以利用索引。 然后,主查询中的四个子查询(total, zcTotal, yjTotal, csTotal)都涉及到多个OR条件,检查不同的用户ID字段是否等于t.user_id,并且类型不同。这些子查询是否可以合并成一个,使用条件聚合,减少对同一表的访问次数? 可能的优化步骤: 1. 合并多个子查询:将t1到t6的子查询合并为一个,使用条件聚合,减少对cchp_close_statistics_his表的访问次数。例如,使用CASE语句或者SUM(IF(...))来统计不同的类型和环节。 2. 优化日期条件:将DATE_FORMAT(feedback_time, '%Y-%m') = '2025-01'改为feedback_time的范围查询,以便利用索引。 3. 添加合适的索引:在cchp_close_statistics_his表的相关字段上创建索引,如(user_id字段,feedback_time),以及各个环节的user_id和type字段的组合索引。 4. 重写主查询中的子查询:将四个SELECT列表中的子查询合并到JOIN的子查询中,通过一次扫描完成多个统计,避免多次访问同一张表。 例如,可以创建一个公共的统计子查询,按用户ID汇总所有需要的统计信息,然后通过LEFT JOIN将其连接到主表。这样,每个用户只需要一次扫描就可以获取所有统计数据,而不是多次扫描。 另外,注意到主查询中的四个子查询(total, zcTotal等)都有相同的WHERE条件,除了不同的OR条件和类型检查。可以将这些条件合并到一个子查询中,使用条件聚合来生成不同的统计列。例如: SELECT user_id, COUNT(*) AS total, COUNT(CASE WHEN type_condition THEN 1 END) AS zcTotal, ... FROM ... GROUP BY user_id 这样,只需要一次扫描就可以得到所有需要的统计值,而不是多次查询。 此外,对于concat_ws('_', t.org1_name, t.org2_name, t.org3_name) belong_org_name,如果这些字段经常被查询,可以考虑在表中冗余存储该值,或者在应用层处理,减少SQL的计算量。 最后,执行计划分析(EXPLAIN)可以帮助确认哪些步骤消耗资源最多,比如是否有全表扫描、临时表或文件排序等。但用户可能没有提供执行计划的结果,所以需要基于经验推测可能的优化点。 总结可能的优化措施: 1. 合并所有统计子查询为一个,使用条件聚合,减少表扫描次数。 2. 为相关字段添加索引,特别是WHERE和JOIN条件中使用的字段。 3. 优化日期条件的写法,避免使用函数转换。 4. 减少SELECT列表中的子查询,改为通过JOIN获取统计结果。</think>这个SQL查询的主要性能瓶颈来自多个方面,我们可以通过以下优化方案逐步改进: --- ### **第一步:索引优化** 需要为以下字段创建组合索引(按优先级排序): ```sql ALTER TABLE cchp_close_statistics_his ADD INDEX idx_feedback_user (feedback_time, gdjs_user_id, gdjs_type), ADD INDEX idx_multi_roles (gdjs_user_id, dctj_user_id, dcfk_user_id, fass_user_id, khhf_user_id), ADD INDEX idx_feedback_date (feedback_time); ``` --- ### **第二步:日期条件优化** 将`DATE_FORMAT(feedback_time, '%Y-%m')`改为范围查询: ```sql AND feedback_time >= '2025-01-01' AND feedback_time < '2025-02-01' ``` --- ### **第三步:子查询合并** **原始问题**:存在6个LEFT JOIN子查询和4个SELECT列表中的子查询,导致多次全表扫描。 **优化方案**:使用`WITH`语句预聚合数据(MySQL 8.0+): ```sql WITH stats_agg AS ( SELECT COALESCE(gdjs_user_id, dctj_user_id, dcfk_user_id, fass_user_id, khhf_user_id) AS user_id, COUNT(*) AS total, COUNT(CASE WHEN (gdjs_type = 1 OR dctj_type = 1 OR dcfk_type = 1 OR fass_type = 1 OR khhf_type = 1) THEN 1 END) AS zcTotal, COUNT(CASE WHEN (gdjs_type = 2 OR dctj_type = 2 OR dcfk_type = 2 OR fass_type = 2 OR khhf_type = 2) THEN 1 END) AS yjTotal, COUNT(CASE WHEN (gdjs_type = 3 OR dctj_type = 3 OR dcfk_type = 3 OR fass_type = 3 OR khhf_type = 3) THEN 1 END) AS csTotal, COUNT(gdjs_user_id) AS gdjsTotal, COUNT(CASE WHEN gdjs_type = 1 THEN 1 END) AS gdjsZcTotal, COUNT(CASE WHEN gdjs_type = 2 THEN 1 END) AS gdjsYjTotal, COUNT(CASE WHEN gdjs_type = 3 THEN 1 END) AS gdjsCsTotal, -- 重复类似逻辑处理其他角色... FROM cchp_close_statistics_his WHERE feedback_time >= '2025-01-01' AND feedback_time < '2025-02-01' GROUP BY user_id ) ``` --- ### **第四步:最终优化SQL** ```sql SELECT t.user_id, t.user_name, t.org_id, t.org_name, t.parent_org_name, t.full_org_name, t.position_name, CONCAT_WS('_', t.org1_name, t.org2_name, t.org3_name) AS belong_org_name, COALESCE(s.gdjsTotal, 0) AS gdjsTotal, COALESCE(s.gdjsZcTotal, 0) AS gdjsZcTotal, COALESCE(s.gdjsYjTotal, 0) AS gdjsYjTotal, COALESCE(s.gdjsCsTotal, 0) AS gdjsCsTotal, -- 其他字段同理... COALESCE(s.total, 0) AS total, COALESCE(s.zcTotal, 0) AS zcTotal, COALESCE(s.yjTotal, 0) AS yjTotal, COALESCE(s.csTotal, 0) AS csTotal FROM cchp_current_his_processor t LEFT JOIN stats_agg s ON s.user_id = t.user_id ``` --- ### **第五步:执行计划验证** 使用`EXPLAIN`分析查询计划: ```sql EXPLAIN WITH stats_agg AS (...) SELECT ...; ``` 重点关注: 1. 是否使用了正确的索引(`type=ref`或`range`) 2. 是否出现`Using temporary`或`Using filesort` 3. 扫描行数(`rows`列) --- ### **性能提升点** 1. **索引覆盖**:通过组合索引直接命中查询条件,减少回表 2. **扫描次数**:将原始7次全表扫描合并为1次 3. **计算下推**:在聚合阶段提前完成条件过滤 4. **内存利用**:使用`WITH`公共表达式避免重复计算 --- ### **扩展建议** 1. 如果`cchp_current_his_processor`表数据量也很大,可以对其`user_id`添加索引 2. 考虑定期将统计结果物化到缓存表中 3. 对`CONCAT_WS('_', org1_name, org2_name, org3_name)`冗余存储
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值