范围查询 range级别 继续优化思路

问题:

这几天工作遇到了一个问题。千万级别的表,每秒钟产生很多数据,select count(id) from table where flag = 1 and create_time < 2023.11.07;分区表,range级别,已经是走create_time列上的索引,flag的值只有0,1。厂商业务卡死在这条sql语句。有什么办法还能再通过什么手段优化吗。大家不妨想一想。

初看已经走索引,range级别好像已经够优秀了,但是执行计划产生了回表,看查找的列不是select *。很好我们可以通过创建联合索引避免回表。

create index inx_caf on table(create_time,flag);

实验1(name的索引选择性还不错),验证具体有没有优化:

-- 创建结构相似的表
create table lian(id int primary key,name varchar(10),flag varchar(10),unique key inx_name(name),key inx_flag(flag)
)
-- 创建存储过程,插入flag分别为0,1的数据
delimiter $$  # 定义结束符
drop procedure if exists addTestData; # 存储过程名叫:addTestData
create procedure addTestData()
begin
declare number int;
set number = 20001;
while number <= 30000 #插入N条数据
do
insert into lian
values(null,number+'1',0);  # 为了区分姓名,我们加上后缀
set number = number + 1;
end
while;
end $$;
select count(*) from lian;
select count(*) from lian where flag = 0;
select count(*) from lian where flag = 1;

已知:现在表中name列和flag列分别有单列索引,我们按找卡死的sql语句运行。

explain
select count(id) from lian   where flag = 1 and name < '1000';

explain format=json
select count(id) from lian   where flag = 1 and name < '1000';
{"query_block": {"select_id": 1,"cost_info": {"query_cost": "5.21"},"table": {"table_name": "lian","access_type": "range","possible_keys": ["inx_name","inx_flag"],"key": "inx_name","used_key_parts": ["name"],"key_length": "13","rows_examined_per_scan": 3,"rows_produced_per_join": 0,"filtered": "10.00","index_condition": "(`test2`.`lian`.`name` < '1000')","cost_info": {"read_cost": "5.15","eval_cost": "0.06","prefix_cost": "5.21","data_read_per_join": "9"},"used_columns": ["id","name","flag"],"attached_condition": "(`test2`.`lian`.`flag` = 1)"}}
}

可以看到这就如厂商卡到的sql语句一样,并且范围查询如果 范围过大的话,执行计划就会变成全表扫描。执行代价5.21

创建联合索引:

create index inx_naf on lian(name,flag);

可以看到联合索引消除了回表操作。让我们看看执行代价:

{"query_block": {"select_id": 1,"cost_info": {"query_cost": "2.22"},"table": {"table_name": "lian","access_type": "range","possible_keys": ["inx_name","inx_flag","inx_naf"],"key": "inx_naf","used_key_parts": ["name"],"key_length": "13","rows_examined_per_scan": 3,"rows_produced_per_join": 0,"filtered": "10.00","using_index": true,"cost_info": {"read_cost": "2.16","eval_cost": "0.06","prefix_cost": "2.22","data_read_per_join": "9"},"used_columns": ["id","name","flag"],"attached_condition": "((`test2`.`lian`.`flag` = 1) and (`test2`.`lian`.`name` < '1000'))"}}
}

2.22比之走单列索引的5.21小了很多(我后续做了实验,小这么多的原因是因为name列是唯一约束,或者说name列的唯一值特别多。后续也做了唯一值特别少的代价实验,请往后看)

联合索引的考量

众所周知联合索引设在前谁在后是有考量的,规则就是谁的选择性好(相对来说唯一值多)谁就放在前面 ,所以我先入为主就把create_time放在了前面。让我们测试下flag在前面的情况。

-- 还没删除naf
create index inx_fan on lian(flag,name);
explain 
select count(id) from lian   where flag = 1 and name < '1000';
explain format=json
select count(id) from lian   where flag = 1 and name < '1000';

 可以看到在两个索引上还是选择了naf。

删除naf:

 优化器没有选择fan,而是选择了最初的单列索引,产生回表。

让我们强制走fan,看看执行代价:

explain format=json
select count(id) from lian force index(inx_fan) where flag = 1 and name < '1000';explain
select count(id) from lian force index(inx_fan) where flag = 1 and name < '1000';

 变成了索引树扫描而非range。

{"query_block": {"select_id": 1,"cost_info": {"query_cost": "35755.00"},"table": {"table_name": "lian","access_type": "index","possible_keys": ["inx_fan"],"key": "inx_fan","used_key_parts": ["flag","name"],"key_length": "26","rows_examined_per_scan": 29795,"rows_produced_per_join": 993,"filtered": "3.33","using_index": true,"cost_info": {"read_cost": "35556.39","eval_cost": "198.61","prefix_cost": "35755.00","data_read_per_join": "31K"},"used_columns": ["id","name","flag"],"attached_condition": "((`test2`.`lian`.`flag` = 1) and (`test2`.`lian`.`name` < '1000'))"}}
}

 执行代价35755特别大,所以联合索引不可以把flag放在最前面,索引选择性特别低。

实验2(name列的索引选择度也特别低)

create table lian3(id int primary key,name varchar(10),flag varchar(10),key inx_flag(flag)
)delimiter $$  # 定义结束符
drop procedure if exists addTestData; # 存储过程名叫:addTestData
create procedure addTestData()
begin
declare number int;
set number = 40001;
while number <= 45000 #插入N条数据
do
insert into lian3
values(null,'h',0);  # 为了区分姓名,我们加上后缀
set number = number + 1;
end
while;
end $$;call addTestData();

分别看name和flag的索引选择度


select count(distinct(name))/count(*),count(distinct(name)),count(*) from lian3;
select count(distinct(flag))/count(*),count(distinct(flag)),count(*) from lian3;

 

 

explain
select count(id) from lian3  where flag = 1 and name < 'e';
explain format=json
select count(id) from lian3  where flag = 1 and name < 'e';

 全表扫描执行代价:

{"query_block": {"select_id": 1,"cost_info": {"query_cost": "6035.00"},"table": {"table_name": "lian3","access_type": "ALL","possible_keys": ["inx_flag"],"rows_examined_per_scan": 29690,"rows_produced_per_join": 989,"filtered": "3.33","cost_info": {"read_cost": "5837.09","eval_cost": "197.91","prefix_cost": "6035.00","data_read_per_join": "30K"},"used_columns": ["id","name","flag"],"attached_condition": "((`test2`.`lian3`.`flag` = 1) and (`test2`.`lian3`.`name` < 'e'))"}}
}

创建name单列索引,执行计划:

create index inx_name on lian3(name);

 因为name的选择度特别低,所以必须强制走索引

explain
select count(id) from lian3 force index(inx_name)  where flag = 1 and name < 'e';explain format=json
select count(id) from lian3 force index(inx_name)  where flag = 1 and name < 'e';

{"query_block": {"select_id": 1,"cost_info": {"query_cost": "20784.01"},"table": {"table_name": "lian3","access_type": "range","possible_keys": ["inx_name"],"key": "inx_name","used_key_parts": ["name"],"key_length": "13","rows_examined_per_scan": 14845,"rows_produced_per_join": 1484,"filtered": "10.00","index_condition": "(`test2`.`lian3`.`name` < 'e')","cost_info": {"read_cost": "20487.11","eval_cost": "296.90","prefix_cost": "20784.01","data_read_per_join": "46K"},"used_columns": ["id","name","flag"],"attached_condition": "(`test2`.`lian3`.`flag` = 1)"}}
}

创建联合索引naf,执行代价:

create index inx_naf on lian3(name,flag);

{"query_block": {"select_id": 1,"cost_info": {"query_cost": "5993.18"},"table": {"table_name": "lian3","access_type": "range","possible_keys": ["inx_flag","inx_name","inx_naf"],"key": "inx_naf","used_key_parts": ["name"],"key_length": "13","rows_examined_per_scan": 14845,"rows_produced_per_join": 1484,"filtered": "10.00","using_index": true,"cost_info": {"read_cost": "5696.29","eval_cost": "296.90","prefix_cost": "5993.19","data_read_per_join": "46K"},"used_columns": ["id","name","flag"],"attached_condition": "((`test2`.`lian3`.`flag` = 1) and (`test2`.`lian3`.`name` < 'e'))"}}
}

创建联合索引fan,并且删除naf,执行代价:

create index inx_fan on lian3(flag,name);
drop index inx_naf on lian3;

{"query_block": {"select_id": 1,"cost_info": {"query_cost": "6035.00"},"table": {"table_name": "lian3","access_type": "index","possible_keys": ["inx_flag","inx_name","inx_fan"],"key": "inx_fan","used_key_parts": ["flag","name"],"key_length": "26","rows_examined_per_scan": 29690,"rows_produced_per_join": 1484,"filtered": "5.00","using_index": true,"cost_info": {"read_cost": "5738.10","eval_cost": "296.90","prefix_cost": "6035.00","data_read_per_join": "46K"},"used_columns": ["id","name","flag"],"attached_condition": "((`test2`.`lian3`.`flag` = 1) and (`test2`.`lian3`.`name` < 'e'))"}}
}

实验2总结:

全表扫描:6035

单列name索引:20784

naf:5993.18

fan:6035

可以看到naf和fan相差不大,这和name列的选择度低有关,但是就算选择度低,naf也是最优解,所以选择naf是最好的。

实验3(flag列选择性好):

这就根本不用做实验了,flag等值查询,选择性还好肯定放在联合索引的左侧。

综上所述,不管是什么做实验时硬道理,实践出真理。也要直到range级别了也可以继续优化,优化的一个思路就是创建联合索引避免回表。

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

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

相关文章

基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(七)

分页查询、删除和修改菜品 1. 菜品分页查询1.1 需求分析和设计1.1.1 产品原型1.1.2 接口设计 1.2 代码开发1.2.1 设计DTO类1.2.2 设计VO类1.2.3 Controller层1.2.4 Service层接口1.2.5 Service层实现类1.2.6 Mapper层 1.3 功能测试1.3.2 前后端联调测试 2. 删除菜品2.1 需求分析…

vue3项目中使用富文本编辑器

前言 适配 Vue3 的富文本插件不多&#xff0c;我看了很多插件官网&#xff0c;也有很多写的非常棒的&#xff0c;有UI非常优雅让人耳目一新的&#xff0c;也有功能非常全面的。 如&#xff1a; Quill&#xff0c;简单易用&#xff0c;功能全面。editorjs&#xff0c;UI极其优…

风电场叶片运输车模型-FBX格式-带动画-数字孪生场景搭建

FBX格式的风电场中叶片运输车辆模型&#xff0c;按照真实尺寸建模&#xff0c;车辆多个部位带动画效果&#xff0c;适用于风电场三维数字化场景和风电场数字孪生使用&#xff0c;也可以用来作为各种三维平台的测试模型。 模型效果图 下载地址 叶片运输车模型下载地址

Java-接口

接口 接口 接口就是公共的行为规范,只要实现时符合标准就可以通用. 接口可以看成是: 多个类的公共规范,是一种引用数据类型. 使用关键字interface实现接口. 接口是不能被实例化的. 接口中的成员变量默认是 public static final 接口中只能有抽象方法,当中的方法不写,也是pu…

visionOS空间计算实战开发教程Day 4 初识ImmersiveSpace

细心的读者会发现在在​​Day1​​​和​​Day2​​​的示例中我们使用的都是​​WindowGroup​​。 main struct visionOSDemoApp: App {var body: some Scene {WindowGroup {ContentView()}} } 本节我们来认识在visionOS开发中会经常用到的另一个概念​​ImmersiveSpace​​…

react中模块化样式中:global的作用

在react中如果是通过import styles from ./index.less这种方式模块化引入样式的话&#xff0c;那么编译后的less文件里的样式名都会自动添加后缀。而:global的作用就是不让类名添加后缀

2023年03月 Scratch(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 小猫的程序如图所示,积木块的颜色与球的颜色一致。点击绿旗执行程序后,下列说法正确的是?( ) A:小猫一直在左右移动,嘴里一直说着“抓到了”。 B:小猫会碰到球,然后停止。…

《QT从基础到进阶·三十八》QWidget实现炫酷log日志打印界面

QWidget实现了log日志的打印功能&#xff0c;不仅可以在界面显示&#xff0c;还可以生成打印日志。先来看下效果&#xff0c;源码放在文章末尾&#xff1a; LogPlugin插件类管理log所有功能&#xff0c;它可以获取Log界面并能打印正常信息&#xff0c;警告信息和错误信息&…

加入破局 180 天,成功立起 IP

大家好&#xff0c;我是破局合伙人木川 在成为破局合伙人之前&#xff0c;我就是那个两三年不怎么发朋友圈的人&#xff0c;成为破局合伙人之后&#xff0c;开始日更朋友圈 在成为破局合伙人之前&#xff0c;我就是那个喜欢单打独斗的人&#xff0c;成为破局合伙人之后&#xf…

Unity UI设计 软件构造实验报告

实验1: 仿真系统的UI主界面设计 1.实验目的 &#xff08;1&#xff09;熟悉Unity中UI界面的设计与编写&#xff1b; &#xff08;2&#xff09;熟悉UI界面中场景转换,UI与场景内容相互关联的方式。 &#xff08;3&#xff09;熟悉Unity中MySQL数据库的操作 2.实验内容 新建…

深入了解批处理文件:从基础到实例

1. 什么是批处理文件&#xff1f; 批处理文件是一种包含一系列命令的文本文件&#xff0c;通常用于自动化执行一系列任务。在不同操作系统中&#xff0c;批处理也有不同的名称&#xff0c;如在Windows中被称为批处理文件&#xff08;.bat&#xff09;&#xff0c;而在Linux中则…

PTA-输出三角形面积和周长

本题要求编写程序&#xff0c;根据输入的三角形的三条边a、b、c&#xff0c;计算并输出面积和周长。注意&#xff1a;在一个三角形中&#xff0c; 任意两边之和大于第三边。三角形面积计算公式&#xff1a;areas(s−a)(s−b)(s−c)​&#xff0c;其中s(abc)/2。 输入格式&…