MySQL索引优化的方法

这篇文章主要介绍“MySQL索引优化的方法”,在日常操作中,相信很多人在MySQL索引优化的方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”MySQL索引优化的方法”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

1.数据准备

#1.建立员工表,并创建name,age,position索引,id为自增主键CREATE TABLE `employees` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',  `age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',  `position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',  `hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',  PRIMARY KEY (`id`),  KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=100010 DEFAULT CHARSET=utf8 COMMENT='员工记录表'# 2.前面插入三条数据,并建立employees_min_copy表插入这三条数据INSERT INTO employees (name,age,`position`,hire_time) VALUES ('LiLei',22,'manager','2021-08-17 21:00:55'),('HanMeimei',23,'dev','2021-08-17 21:00:55'),('Lucy',23,'dev','2021-08-17 21:00:55');#3.再通过执行计划向表中插入十万条数据#3.1建立存储过程,往employees表中插入数据(MySQL8.0版本)DELIMITER $$USE `zhebase`$$DROP PROCEDURE IF EXISTS `batch_insert_employees`$$CREATE PROCEDURE `batch_insert_employees`(IN `start_number` BIGINT,IN `counts` BIGINT)BEGIN     DECLARE start_number BIGINT DEFAULT start_number;    DECLARE stop_number BIGINT DEFAULT start_number;    SET stop_number=start_number + counts;    WHILE start_number < stop_number DO        INSERT INTO employees(name,age,position,hire_time) VALUES(CONCAT('zhang',start_number),start_number,'dev',now());        SET start_number=start_number+1;    END WHILE ;    COMMIT;END$$DELIMITER ;#3.2执行存储过程插入十万条数据CALL batch_insert_employees(1,100000);

2.实例一

1.联合索引第一个字段用范围不会走索引  

EXPLAIN SELECT * FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';

MySQL索引优化的方法

 原因:MySQL 内部可能觉得第一个字段就用范围,结果集应该很大,还需要回表,回表效率不高,不如直接采用全表扫描 但是我们可以强制走索引

EXPLAIN SELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei' AND age = 22 AND position ='manager';

MySQL索引优化的方法

-- 关闭查询缓存set global query_cache_size=0;set global query_cache_type=0;-- 执行时间0.321sSELECT * FROM employees WHERE name > 'LiLei';-- 执行时间0.458sSELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei';

MySQL索引优化的方法

 使用了强制走索引让联合索引第一个字段范围查找也走索引,扫描的行rows看上去也少了点,但是最终查找效率不一定比全表扫描高,因为回表效率不高

对于这种情况,如果可以使用覆盖索引,就使用覆盖索引进行优化 

EXPLAIN SELECT name,age,position FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';

MySQL索引优化的方法

2.in 和 or 在表数据量比较大的情况会走索引,在表记录不多的情况下会选择全表扫描

EXPLAIN SELECT * FROM employeesWHERE name in ('LiLei','HanMeimei','Lucy')AND age = 22AND position ='manager';#表数据量大走索引,数据量小全表扫描EXPLAIN SELECT * FROM employeesWHERE (name = 'LiLei' or name = 'HanMeimei')AND age = 22 AND position ='manager';

MySQL索引优化的方法

 将十万行数据的employees表复制一份插入几行数据,再进行查询 

MySQL索引优化的方法

发现进行了全表扫描 

MySQL索引优化的方法

3.like xx% 无论数据量多少一般情况都会走索引

EXPLAIN SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager';

MySQL索引优化的方法

 MySQL 底层使用索引下推(Index Condition Pushdown,ICP) 来对 like xx%进行优化。

索引下推: 对于辅助的联合索引(idx_name_age_position),通常按照最左前缀原则,SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager' 因为在 name 是范围查询,过滤完后,age 和 position 是无序的,后续索引无法使用,只会走name字段索引。

MySQL5.6 以前: 先在索引树中匹配 name 是 ‘LiLei’ 开头的索引,然后根据索引下的主键进行回表操作,在主键索引上在匹配 age 和 position

MySQL 5.6以后: 引入索引下推,先在索引树种匹配 name 是 ‘LiLei’ 开头的索引,同时将该所与树通有的所有条件字段进行判断,过滤掉不符合条件的记录再回表匹配其他条件及查询整行数据。

优点: 过滤掉不符合条件的记录之后再回表,可以有效的减少回表次数,提高查询效率

MySQL 范围查找为什么没有使用索引下推优化?  可能因为范围查找结果集一般较大,like xx%在大多数情况下,过滤后结果集较小。而结果集大的时候,每次检索出来都要匹配后面的字段,不一定比立即回表要快。但是也不是绝对的,有些时候 Like xx%也不会走索引下推。

3.MySQL如何选择合适的索引?

先来看两条 SQL 语句:

# MySQL直接使用全表扫描EXPLAIN select * from employees where name > 'a';# MySQL走索引EXPLAIN select * from employees where name > 'zzz';

MySQL索引优化的方法

 我们发现第一条 SQL 进行了全表扫描,第二条 SQL 走了索引。对应第一条SQL,MySQL 通过计算执行成本发现走索引成本比全部扫描更高(走索引需要遍历 name 字段,再进行回表操作查出最终数据,比直接查聚簇索引树更慢)。对于这种情况可以使用覆盖索引进行优化。至于 MySQL 如何选择最终索引,可以用 Trace 工具进行查看。但开启trace工具会影响 MySQL 性能,用完之后需立即关闭。

#开启traceset session optimizer_trace="enabled=on",end_markers_in_json=on; #关闭traceset session optimizer_trace="enabled=off";#使用traceselect * from employees where name > 'a' order by position;select * from information_schema.OPTIMIZER_TRACE;

MySQL索引优化的方法

下面是执行后的Trace中的内容:

{  "steps": [    {      #第一阶段:SQL准备阶段,格式化sql      "join_preparation": {        "select#": 1,        "steps": [          {            "expanded_query": "/* select#1 */ select `employees`.`id` AS `id`,`employees`.`name` AS `name`,`employees`.`age` AS `age`,`employees`.`position` AS `position`,`employees`.`hire_time` AS `hire_time` from `employees` where (`employees`.`name` > 'a') order by `employees`.`position` limit 0,200"          }        ] /* steps */      } /* join_preparation */    },    {      #第二阶段:SQL优化阶段      "join_optimization": {        "select#": 1,        "steps": [          {            #条件处理            "condition_processing": {              "condition": "WHERE",              "original_condition": "(`employees`.`name` > 'a')",              "steps": [                {                  "transformation": "equality_propagation",                  "resulting_condition": "(`employees`.`name` > 'a')"                },                {                  "transformation": "constant_propagation",                  "resulting_condition": "(`employees`.`name` > 'a')"                },                {                  "transformation": "trivial_condition_removal",                  "resulting_condition": "(`employees`.`name` > 'a')"                }              ] /* steps */            } /* condition_processing */          },          {            "substitute_generated_columns": {            } /* substitute_generated_columns */          },          {            #表依赖详情            "table_dependencies": [              {                "table": "`employees`",                "row_may_be_null": false,                "map_bit": 0,                "depends_on_map_bits": [                ] /* depends_on_map_bits */              }            ] /* table_dependencies */          },          {            "ref_optimizer_key_uses": [            ] /* ref_optimizer_key_uses */          },          {            #预估表的访问成本            "rows_estimation": [              {                "table": "`employees`",                "range_analysis": {                  "table_scan": { --全表扫描情况                    "rows": 93205, --扫描行数                    "cost": 9394.9 --查询成本                  } /* table_scan */,                  #查询可能使用的索引                  "potential_range_indexes": [                    {                      "index": "PRIMARY",  --主键索引                      "usable": false, -- 是否使用                      "cause": "not_applicable"                    },                    {                      #辅助索引                      "index": "idx_name_age_position",                      "usable": true,                      "key_parts": [                        "name",                        "age",                        "position",                        "id"                      ] /* key_parts */                    }                  ] /* potential_range_indexes */,                  "setup_range_conditions": [                  ] /* setup_range_conditions */,                  "group_index_range": {                    "chosen": false,                    "cause": "not_group_by_or_distinct"                  } /* group_index_range */,                  "skip_scan_range": {                    "potential_skip_scan_indexes": [                      {                        "index": "idx_name_age_position",                        "usable": false,                        "cause": "query_references_nonkey_column"                      }                    ] /* potential_skip_scan_indexes */                  } /* skip_scan_range */,                  #分析各个索引使用成本                  "analyzing_range_alternatives": {                    "range_scan_alternatives": [                      {                        "index": "idx_name_age_position",                        "ranges": [                          "a  'a')",              "attached_conditions_computation": [                {                  "table": "`employees`",                  "rechecking_index_usage": {                    "recheck_reason": "low_limit",                    "limit": 200,                    "row_estimate": 46602                  } /* rechecking_index_usage */                }              ] /* attached_conditions_computation */,              "attached_conditions_summary": [                {                  "table": "`employees`",                  "attached": "(`employees`.`name` > 'a')"                }              ] /* attached_conditions_summary */            } /* attaching_conditions_to_tables */          },          {            "optimizing_distinct_group_by_order_by": {              "simplifying_order_by": {                "original_clause": "`employees`.`position`",                "items": [                  {                    "item": "`employees`.`position`"                  }                ] /* items */,                "resulting_clause_is_simple": true,                "resulting_clause": "`employees`.`position`"              } /* simplifying_order_by */            } /* optimizing_distinct_group_by_order_by */          },          {            "reconsidering_access_paths_for_index_ordering": {              "clause": "ORDER BY",              "steps": [              ] /* steps */,              "index_order_summary": {                "table": "`employees`",                "index_provides_order": false,                "order_direction": "undefined",                "index": "unknown",                "plan_changed": false              } /* index_order_summary */            } /* reconsidering_access_paths_for_index_ordering */          },          {            "finalizing_table_conditions": [              {                "table": "`employees`",                "original_table_condition": "(`employees`.`name` > 'a')",                "final_table_condition   ": "(`employees`.`name` > 'a')"              }            ] /* finalizing_table_conditions */          },          {            "refine_plan": [              {                "table": "`employees`"              }            ] /* refine_plan */          },          {            "considering_tmp_tables": [              {                "adding_sort_to_table_in_plan_at_position": 0              } /* filesort */            ] /* considering_tmp_tables */          }        ] /* steps */      } /* join_optimization */    },    {      #第三阶段:SQL执行阶段      "join_execution": {        "select#": 1,        "steps": [          {            "sorting_table_in_plan_at_position": 0,            "filesort_information": [              {                "direction": "asc",                "table": "`employees`",                "field": "position"              }            ] /* filesort_information */,            "filesort_priority_queue_optimization": {              "limit": 200,              "chosen": true            } /* filesort_priority_queue_optimization */,            "filesort_execution": [            ] /* filesort_execution */,            "filesort_summary": {              "memory_available": 262144,              "key_size": 40,              "row_size": 186,              "max_rows_per_buffer": 201,              "num_rows_estimate": 285696,              "num_rows_found": 100003,              "num_initial_chunks_spilled_to_disk": 0,              "peak_memory_used": 38994,              "sort_algorithm": "std::stable_sort",              "unpacked_addon_fields": "using_priority_queue",              "sort_mode": ""            } /* filesort_summary */          }        ] /* steps */      } /* join_execution */    }  ] /* steps */}

由 Trace字段可知,全表扫描的 cost_for_plan = 9394.9 小于使用索引 cost_for_plan = 16311,故最终选择全表扫描。

4.常见 SQL 深入优化

4.1.Order by与Group by优化

# 案例1explain select * from employees where name = 'Lucy' and position = 'dev' order by age;

分析:  案例1 由最左前缀法则分析出索引中间不能出现断层,只使用了 name 索引前缀,也可以从key_len = 3n + 2 看出。age 索引列用在排序过程中,因为Extra字段里没有 Using filesort 而是 Using index condition 。 

MySQL索引优化的方法

#案例2explain select * from employees where name = 'Lucy'  order by position;

分析:  案例2 索引查询使用了 name 索引前缀,但排序由于跳过了 age 所以Extra字段出现了 Using filesort 。

#案例3explain select * from employees where name = 'Lucy'  order by age, position;

MySQL索引优化的方法

分析:  案例3 查询时使用了 name 索引,age 和 postion 用于排序,不会出现 Using filesort

#案例4explain select * from employees where name = 'Lucy'  order by position,age;

MySQL索引优化的方法

分析:  案例4 查询时使用了 name 索引,age 和 postion 顺序与创建索引树不一致,出现了 Using filesort

MySQL索引优化的方法

#案例5explainselect * from employeeswhere name = 'Lucy'and age = 22order by position,age;

MySQL索引优化的方法

分析:  案例5 查询时使用了 name 索引,age 和 postion 顺序与创建索引树不一致,但 name、age 为常量,MySQL 会自动优化,不会出现 Using filesort

#案例6explain select * from employees where name = 'Lucy' order byage,position desc;

MySQL索引优化的方法

分析:  案例6 排序顺序一样,但 order by 默认升序,导致与索引的排序方式不同,出现了 Using filesort 。 MySQL8.0 以上版本有降序索引可以支持这种查询。

#案例7explain select * from employees where name = 'Lucy' or name = 'LiLei' order by age;

MySQL索引优化的方法

 分析:  案例7 对于排序来说,多个相等条件也是范围查询,出现了 Using filesort 。

#案例8#SQL-1explain select * from employees where name > 'zzz' order by name;#SQL-2explain select * from employees where name > 'a' order by name;

MySQL索引优化的方法

  分析:  案例8 原因同前面的例子,可以使用覆盖索引优化。

MySQL排序总结:

1、MySQL支持两种方式的排序 filesort 和 indexUsing index是指MySQL扫描索引本身完成排序。Using filesort 是指MySQL扫描聚簇索引(整张表)进行排序。index效率高,filesort效率低。

2、order by 满足两种情况会使用 Using index(不绝对)

a.order by 语句使用索引最左前列。

b.使用 where 子句与 order by 子句条件列组合满足索引最左前列。

3、尽量在索引列上完成排序,遵循最左前缀法则。

4、如果 order by 的条件不在索引列上,就会产生Using filesort。

5、能用覆盖索引尽量用覆盖索引

6、group by 与 order by 很类似,其实质是先排序后分组(group by 底层:先执行一次 order by 再进行分组),遵照索引创建顺序的最左前缀法则。对于group by的优化如果不需要排序的可以加上order by null 禁止排序。注意,where高于having能写在where中的限定条件就不要去having限定了

Using filesort 文件排序原理 filesort文件排序方式有:

单路排序是一次性取出满足条件行的所有字段,然后在 sort buffer 中进行排序。用trace工具得到sort_mode信息显示或者

双路排序(又叫回表排序模式) :先根据相应的条件取出相应的排序字段和可以直接定位行数据的行 ID,然后在 sort buffer 中进行排序,排序完后需要再次取回其它需要的字段。用trace工具得到sort_mode信息显示

MySQL 通过比较系统变量 max_length_for_sort_data(默认1024字节) 的大小和需要查询的字段总大小来判断使用哪种排序模式。

字段的总长度 < max_length_for_sort_data ,使用单路排序

字段的总长度 >max_length_for_sort_data ,使用双路排序

 select * from employees where name = 'Lucy' order by position;
"join_execution": {    --Sql执行阶段        "select#": 1,        "steps": [          {            "filesort_information": [              {                "direction": "asc",                "table": "`employees`",                "field": "position"              }            ] /* filesort_information */,            "filesort_priority_queue_optimization": {              "usable": false,              "cause": "not applicable (no LIMIT)"            } /* filesort_priority_queue_optimization */,            "filesort_execution": [            ] /* filesort_execution */,            "filesort_summary": {                      --文件排序信息              "rows": 10000,                           --预计扫描行数              "examined_rows": 10000,                  --参与排序的行              "number_of_tmp_files": 3,                --使用临时文件的个数,如果为0代表全部使用的sort_buffer内存排序,否则使用的磁盘文件排序              "sort_buffer_size": 262056,              --排序缓存的大小,单位Byte              "sort_mode": ""       --排序方式,此处是路排序            } /* filesort_summary */          }        ] /* steps */      } /* join_execution */

单路排序会把所有需要查询的字段都放到 sort buffer 中排序,而双路排序只会把主键和需要排序的字段放到 sort buffer 中进行排序,然后再通过主键回到原表查询需要的字段。

单路排序过程:

a.从索引 name 找到第一个满足 name = ‘Lucy’ 条件的主键 id

b.回表根据主键 id 取出整行,取出所有字段的值,存入 sort_buffer 中

c.从索引name找到下一个满足 name = ‘Lucy’ 条件的主键 id

d.重复步骤 2、3 直到不满足 name = ‘Lucy’

e.对 sort_buffer 中的数据按照字段 position 进行排序

f.返回结果

双路排序过程:

a.从索引 name 找到第一个满足 name =’Lucy’ 的主键 id

b.根据主键 id 取出整行,把排序字段 position 和主键 id 这两个字段放到 sort buffer 中

c.从索引 name 取下一个满足 name = ‘Lucy’ 记录的主键 id

d.重复 3、4 直到不满足 name = ‘Lucy’

e.对 sort_buffer 中的字段 position 和主键 id 按照字段 position 进行排序

f.遍历排序好的 id 和字段 position,按照 id 的值回到原表中取出所有字段的值返回

4.2.分页查询优化

 select * from employees limit 10000,10

这条 SQL 语句实际查询了 10010 条记录,然后丢弃了前面的 10000 条记录,所以,在 数据量很大时,执行效率是非常非常低的。一般需要对分页查询进行优化。 优化方法: 1.根据自增且连续的主键排序的分页查询

 select * from employees where id > 90000 limit 5;

当一个表的主键连续且自增时,可以使用该方法进行优化,但如果自增不连续会造成数据丢失

2.根据非主键字段排序的分页查询

#优化前select * from employees ORDER BY name limit 90000,5;#优化后select * from employees e inner join (select id from employees order by name limit 90000,5) ed on e.id = ed.id;

先通过排序和分页操作先查出主键,然后根据主键查出对应的记录。 

MySQL索引优化的方法

4.3.join关联查询优化

4.3.1.数据准备
#示例表# 创建t1,t2表,主键id,单值索引aCREATE TABLE `t1` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `a` int(11) DEFAULT NULL,  `b` int(11) DEFAULT NULL,  PRIMARY KEY (`id`),  KEY `idx_a` (`a`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;create table t2 like t1;#存储过程往t1,t2表插入数据DELIMITER $$USE `zhebase`$$DROP PROCEDURE IF EXISTS `batch_insert_t1`$$CREATE PROCEDURE `batch_insert_t1`(IN `start_number` BIGINT,IN `counts` BIGINT)BEGIN     DECLARE start_number BIGINT DEFAULT start_number;    DECLARE stop_number BIGINT DEFAULT start_number;    SET stop_number=start_number + counts;    WHILE start_number < stop_number DO        INSERT INTO t1(a,b) VALUES(start_number,start_number);         SET start_number=start_number+1;     END WHILE ;     COMMIT; END$$DELIMITER ;DELIMITER $$USE `zhebase`$$DROP PROCEDURE IF EXISTS `batch_insert_t2`$$CREATE PROCEDURE `batch_insert_t2`(IN `start_number` BIGINT,IN `counts` BIGINT)BEGIN     DECLARE start_number BIGINT DEFAULT start_number;    DECLARE stop_number BIGINT DEFAULT start_number;    SET stop_number=start_number + counts;    WHILE start_number < stop_number DO        INSERT INTO t2(a,b) VALUES(start_number,start_number);         SET start_number=start_number+1;     END WHILE ;     COMMIT; END$$DELIMITER ;#执行存储过程往t1表插入10000条记录,t2表插入100条记录CALL batch_insert_t1(1,10000);CALL batch_insert_t2(1,100);
4.3.2.MySQL 表关联常见的两种算法

嵌套循环连接 Nested-Loop Join(NLJ) 算法

基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法

通常,当MySQL查询被驱动表的关联字段没有索引时,会采用BNL算法进行关联查询。当存在索引时,通常会选择NLJ算法,因为 NLJ 算法在有索引的情况下比 BNL 算法效率更高

1.嵌套循环连接 Nested-Loop Join(NLJ) 算法 原理:一次一行循环地从第一张表(驱动表)中读取行,在这行数据中取到关联字段,根据关联字段在另一张表(被驱动表)里取出满足条件的行,然后取出两张表的结果合集。

explain select * from t1 inner join t2 on t1.a= t2.a;

MySQL索引优化的方法

 从执行计划可以了解的信息:

a.驱动表是 t2,被驱动表是 t1( inner join 时 SQL优化器会小表驱动大表,外连接则根据连接类型区分)

b.使用了 NLJ 算法。如果执行计划 Extra 中未出现 Using join buffer 则表示使用的 join 算法是 NLJ

整个过程会读取 t2 表的所有数据(扫描100行),然后遍历这每行数据中字段 a 的值,根据 t2 表中 a 的值索引扫描 t1 表中的对应行(扫描100次 t1 表的索引,1次扫描可以认为最终只扫描 t1 表一行完整数据,也就是总共 t1 表也扫描了100行)。因此整个过程扫描了 200 行 。

2. 基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法 原理:把驱动表的数据读入到 join_buffer 中,然后扫描被驱动表,把被驱动表每一行取出来跟 join_buffer 中的数据做对比

explain select * from t1 inner join t2 on t1.b= t2.b;

MySQL索引优化的方法

 整个过程对表 t1 和 t2 都做了一次全表扫描,因此扫描的总行数为10000(表 t1 的数据总量) + 100(表 t2 的数据总量) = 10100。并且 join_buffer 里的数据是无序的,因此对表 t1 中的每一行,都要做 100 次判断,所以内存中的判断次数是 100 * 10000= 100 万次(非扫描次数) 。 注意: join_buffer 的大小是由参数 join_buffer_size 控制,默认256k。如果 t2 放不下就会使用分段策略(先从 t2 表取出部分数据,比对完就清空 join_buffer,再重新拿出来余下的部分进行比对)。

被驱动表的关联字段无索引为什么要选择使用 BNL 算法而不使用 NLJ 算法?          如第二条 SQL,如果使用 NLJ 算法扫描行数为 100 * 10000 = 100万,这个是磁盘扫描。使用 BNL 算法仅需扫描 100100 行。

对于表关联 SQL 的优化

尽量少关联(在阿里规范中,关联表不能超过三种,可以后端代码单独查询,循环关联)

小表驱动大表,写多表连接 SQL 时如果明确知道哪张表是小表可以用straight_join写法固定连接驱动方式,节约 MySQL 优化器判断时间.select * from t2 straight_join t1 on t2.a = t1.a; 代表指定mysql选着 t2 表作为驱动表

关联字段加索引,大表关联字段一定要加索引,尽量使得 MySQL 在进行 join 操作时选择NLJ算法

多表连接是非常难以优化的,最好95%的场景都使用单表来完成,复杂场景交个JAVA代码,大规模计算交给大数据工具,无需效率才考虑连接

4.4.in和exsits优化

原则:小表驱动大表

# in 先执行括号里面的select * from A where id in (select id from B)  #exists 先执行括号外面的#select * 可以用 select 1 替换,没有区别#exists 子查询内部会进行优化,并非逐条对比#exists 子查询往往也可以用 jion 来代替,何种最优需要具体问题具体分析select * from A where exists (select 1 from B where B.id = A.id)

4.5.count(*)查询优化

注意:根据某个字段 count 不会统计字段为 null 的行

#扫描二级索引,按行累加explain select count(1) from employees;#扫描辅助索引按行累加(辅助索引比聚簇索引小)explain select count(id) from employees;#把 name 拿到内存,不为 null 就累加explain select count(name) from employees;#不取值,按行累加explain select count(*) from employees;

四条语句的效率几乎可以忽略,效率对比如下: 字段有索引: count(* )≈count(1)>count(字段)>count(主键 id) 段)>count(主键 id)  字段无索引: count(*)≈count(1)>count(主键 id)>count(字段)

常见优化方法:

1.对于 MyISAM 存储引擎的表做不带 where 条件的 count 查询性能是很高的,数据总行数直接写在磁盘上,查询不需要计算。innodb 存储引擎的表则不会记录(因为有MVCC机制)

2.对与不用知道确切行的可以直接使用show table status,它是一个估值,使用该查询效率很高

3.将总数维护到 Redis 里面,插入或删除表数据行的时候同时维护 Redis 里的表总行数 key 的计数值(用 incr 或 decr 命令),但是这种方式可能不准,很难保证表操作和Redis 操作的事务一致性。

4.增加数据库计数表,插入或删除表数据行的时候同时维护计数表,且它们在同一个事务里操作

5.索引设计原则

1、代码先行,索引后上,先开发完主体业务代码,再把涉及到该表相关sql都要拿出来分析之后再建立索引。

2、联合索引尽量覆盖条件,可以设计一个或者两三个联合索引(单值索引要少建),让每一个联合索引都尽量去包含SQL语句里的 where、order by、group by 的字段,且这些联合索引字段顺序尽量满足 SQL查询的最左前缀原则。

3、不要在小基数字段上建立索引,无法进行快速的二分查找,不能能发挥出B+树快速二分查找的优势来,没有意义

4、尽量对字段类型较小的列设计索引,尽量对字段类型较小的列设计索引,比如 Tinyint 之类,字段类型较小的话,占用磁盘空间小,搜索的时性能更好。

5、长字符串可以采用前缀索引,比如针对某个字段的前20个字符建立索引,即:每个值的前20个字符放入索引树中,搜索时会先匹配前而是个字符,再回表到聚簇索引取出来完整的 name 字段值进行比较。但排序(order by 和 group by)时无法使用该索引。

6、where 与 order by 冲突时优先 where,大多数情况下根据索引进行 where 筛选一般筛选出来的数据比较少,然后做排序成本会更低。

7、基于慢SQL查询做优化,可以根据监控后台的一些慢SQL,针对这些慢 SQL 查询做特定的索引优化(MySQL有提供,只需设置具体参数)。

以上就是MySQL索引优化的方法的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/158007.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月5日 02:02:38
下一篇 2025年12月5日 03:14:10

相关推荐

  • 网络进化!

    Web 应用程序从静态网站到动态网页的演变是由对更具交互性、用户友好性和功能丰富的 Web 体验的需求推动的。以下是这种范式转变的概述: 1. 静态网站(1990 年代) 定义:静态网站由用 HTML 编写的固定内容组成。每个页面都是预先构建并存储在服务器上,并且向每个用户传递相同的内容。技术:HT…

    2025年12月24日
    000
  • 为什么多年的经验让我选择全栈而不是平均栈

    在全栈和平均栈开发方面工作了 6 年多,我可以告诉您,虽然这两种方法都是流行且有效的方法,但它们满足不同的需求,并且有自己的优点和缺点。这两个堆栈都可以帮助您创建 Web 应用程序,但它们的实现方式却截然不同。如果您在两者之间难以选择,我希望我在两者之间的经验能给您一些有用的见解。 在这篇文章中,我…

    2025年12月24日
    000
  • CSS如何实现任意角度的扇形(代码示例)

    本篇文章给大家带来的内容是关于CSS如何实现任意角度的扇形(代码示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。 扇形制作原理,底部一个纯色原形,里面2个相同颜色的半圆,可以是白色,内部半圆按一定角度变化,就可以产生出扇形效果 扇形绘制 .shanxing{ position:…

    2025年12月24日
    000
  • html中怎么运行sql语句_html中运行sql语句方法【教程】

    必须通过后端服务执行SQL操作。一、PHP与MySQL交互:使用PHP脚本在服务器端连接数据库,执行查询并嵌入HTML输出,避免硬编码凭证。二、Ajax调用API:前端通过JavaScript向后端API发送请求,服务端执行SQL并返回JSON数据,前端动态渲染结果。三、SQLite与JavaScr…

    2025年12月23日
    000
  • html手机怎么运行_手机运行html方法【教程】

    1、使用手机浏览器可直接打开本地HTML文件,只需通过文件管理器点击文件并选择浏览器打开即可预览;2、借助Spck Editor等专用编辑器应用能实现实时编辑与预览,适合开发调试;3、对于含JavaScript或需服务器支持的动态内容,应安装KSWEB类应用搭建本地服务器,再通过http://loc…

    2025年12月23日
    000
  • html如何连接_连接HTML与数据库或API接口【接口】

    HTML无法直接连接数据库或调用API,需借助JavaScript fetch、PHP中转、Node.js后端或Python Flask等服务端技术实现动态数据交互。 如果您希望在网页中动态获取数据,HTML本身无法直接连接数据库或调用API接口,必须借助服务器端语言或JavaScript等客户端技…

    2025年12月23日
    000
  • HTML如何添加批注功能_评论系统实现方案【教程】

    可实现HTML文本批注功能的四种方案:一、基于HTML5自定义属性与JS的静态批注;二、遵循W3C标准的语义化批注;三、嵌入Utterances或Giscus等第三方评论系统;四、自建AJAX评论后端+前端组件。 如果您希望在HTML页面中为特定文本添加可交互的批注功能,或构建一个轻量级的评论系统,…

    2025年12月23日
    000
  • html怎么在本地服务器运行_本地服务器运html方法【指南】

    使用本地服务器运行HTML文件需通过HTTP协议,可选Python命令启动服务、Node.js的http-server、VS Code的Live Server插件或XAMPP等工具,确保AJAX等功能正常。 要在本地服务器运行HTML文件,不能直接双击打开,因为部分功能(如AJAX、API调用)需要…

    2025年12月23日
    200
  • phpstudy怎么运行本地html_phpstudy运行本地html方法【教程】

    确保Apache或Nginx服务已启动;2. 将HTML文件放入WWW目录;3. 浏览器访问localhost即可运行页面。 在使用 PHPStudy 时,运行本地 HTML 文件非常简单。PHPStudy 是一个集成了 Apache/Nginx、PHP 和 MySQL 的集成环境工具,主要用于本地…

    2025年12月23日
    000
  • HTML页面如何生成短链接_URL压缩转换方法【攻略】

    可借助第三方服务、API调用、Nginx反向代理、PHP脚本或GitHub Pages五种方式将HTML页面URL转为短链接:1.用bit.ly等平台手动缩短;2.调用Bitly API批量生成;3.配置Nginx rewrite规则重定向;4.部署PHP+MySQL实现动态跳转;5.利用GitHu…

    2025年12月23日
    000
  • Java JDBC中SQL INSERT语句的常见语法错误及修复指南

    本文旨在解决java jdbc应用中常见的sql `insert`语句语法错误,特别是因缺少括号而导致的错误。我们将深入分析错误信息,指出问题根源,并提供正确的sql语句范例及java jdbc `preparedstatement`的使用方法。文章还将涵盖jdbc数据库操作的最佳实践、错误处理和调…

    2025年12月23日
    000
  • wampserver怎么运行html程序_wampserver运行html程序方法【教程】

    使用WampServer运行HTML程序需将文件放入www目录,启动Apache服务后通过http://localhost/项目路径访问,确保在本地服务器环境下正确解析运行。 如果您在本地开发网页,但无法正确查看HTML文件的运行效果,可能是由于未通过本地服务器环境进行访问。WampServer 提…

    2025年12月23日
    000
  • 平板怎么运行html代码_平板运行html代码步骤【指南】

    可在平板上通过四种方式查看HTML效果:一、用浏览器直接打开本地.html文件;二、使用JSFiddle等在线编辑器实时预览;三、安装Acode等编程应用离线编写并预览;四、通过KSWEB搭建本地服务器运行含动态内容的页面。 如果您希望在平板设备上查看或测试HTML代码的效果,但不确定如何操作,则可…

    2025年12月23日
    000
  • html上怎么运行php代码吗_html中运行php代码方法【教程】

    要使PHP代码在HTML中执行,必须通过支持PHP的服务器环境。首先将文件保存为.php格式并部署到配置好PHP模块的服务器(如Apache)根目录,通过http://localhost访问;或修改服务器配置(如.htaccess)令.html文件解析PHP;推荐使用.php文件混合HTML与PHP…

    2025年12月23日
    000
  • html怎么用sublime运行php_sublime运行html中php方法【教程】

    可在Sublime Text中通过配置PHP环境变量并创建Build System运行PHP代码,或使用PHP内置服务器、XAMPP等集成环境结合浏览器预览实现解析与调试。 如果您在使用Sublime Text编辑HTML或PHP文件时,希望直接运行PHP代码并查看输出结果,但发现无法像在浏览器中那…

    2025年12月23日
    000
  • PHP表单提交后防止页面刷新并保留数据与错误提示的教程

    本教程旨在解决php表单提交时页面刷新、用户输入数据丢失以及错误提示显示不佳的问题。核心方法是利用服务器端php的`$_post`变量,在表单提交并进行服务器端验证失败后,不进行页面重定向,而是直接在当前页面重新渲染表单,同时回填用户之前输入的数据并显示验证错误信息,从而显著提升用户体验。 引言:优…

    2025年12月23日
    000
  • 如何通过JavaScript/jQuery获取HTML元素内容并与PHP后端交互

    本教程详细阐述了如何利用JavaScript和jQuery从HTML页面中动态获取特定` `标签的文本内容,并进一步探讨了如何将这些前端捕获的数据通过AJAX技术安全地传递给PHP后端进行处理,例如执行SQL查询。文章涵盖了从前端事件触发、数据捕获到后端数据接收、处理及安全防护的全流程,旨在提供一个…

    2025年12月23日
    000
  • php怎么在html5中运行_php在html5中运行方法【教程】

    PHP在服务器端运行,通过嵌入HTML5文件生成动态内容。1. PHP与HTML5协同工作:PHP代码嵌入.html或.php文件,由服务器解析后输出纯HTML至浏览器。2. 创建index.php文件,使用标准HTML5结构,在其中插入等PHP代码,实现动态内容展示。3. 搭建本地环境可选用XAM…

    2025年12月23日 好文分享
    000
  • epp4怎么运行html文件_EPP4运行html文件步骤【指南】

    首先确认EPP4已安装并启动Apache服务,将HTML文件放入www目录后,通过http://localhost/路径访问即可预览页面,确保文件位置与路径正确。 打开EPP4后运行HTML文件并不复杂,只需正确操作即可在浏览器中预览页面效果。EPP4(Easy PHP Pack 4)是一个集成开发…

    2025年12月23日
    000
  • html怎么用浏览器运行php_浏览器运html中php文件方法【教程】

    正确答案是搭建本地开发环境。需安装XAMPP等集成工具,将.php文件放入htdocs目录,通过http://localhost访问,确保服务器解析PHP并返回HTML给浏览器显示。 PHP 是服务器端语言,不能直接通过浏览器像 HTML 那样双击打开运行。你看到的“在浏览器中运行 PHP”其实是指…

    2025年12月23日
    000

发表回复

登录后才能评论
关注微信