Mysql数据查询优化——索引优化

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
日志服务 SLS,月写入数据量 50GB 1个月
简介: Mysql数据查询优化——索引优化

正文


如何查看一个表的索引


如何查看一个表中有哪些索引呢?可以通过如下sql查看


SHOW INDEX FROM tb_user; #tb_user是表名


111.png


各个字段表示含义如下


table列表示表名;

Non_unique-表示是否是唯一索引,0代表是唯一索引,1代表不是唯一索引;

key_name表示索引名称;

seq_in_index序号;

column_name 表示在哪列上创建索引;

collation表示列以什么方式存储在索引中,可以是A或者null,B+Tree的索引总是A,即排序的,如果使用hash索引的话,就是null;

Cardinality表示索引中不重复数量的预估值,也可以理解数据等值的数量比较少,一般这个数据值越大越比较适合创建索引,如果特别小那就不适合建索引,如性别(gender)字段。

sub_part是否是列的部分索引,如上面在address前15个字符创建了索引。

packed 关键字如何被压缩,如果没有被压缩则为null;

null 表示是否索引的列含有空值(null);

Index_type 表示索引类型,因为表是Innodb存储引擎,所以是Btree;

Comment表示注释;


Cardinality


对这个字段说明一下,Cardinality表示索引中不重复记录数量的预估值。Cardinality是一个预估值而不是一个准确的值。mysql优化器会根据这个值来判断是否使用索引,但是在某些情况下Cardinality可能为null,在这种情况下可能导致明明创建了索引,而索引却没生效,可以尝试执行如下sql尝试。


ANALYZE TABLE tb_user;



Cardinality在更新通常发生在insert和update。但是不能每次执行insert和update都更新这个值,这样很明显不合理。在Innodb引擎中更新Cardinality值的策略为


表中1/16的数据已经发生过变化。

stat_modified_counter>20亿。考虑到如果对同一条数据进行修改,那么在Innodb存储内部会有一个计数器stat_modified_counter,如果该数值超过了20亿就会更新Cardinality。

Innodb存储引擎通过采样的方法来统计这个数值。


默认情况下Innodb通过采样的方式来统计该数值,采取8个叶子结点的数值。采样过程如下


采取B+Tree中叶子节点的数量 记为A。

随机获取8个叶子结点。统计每个页不同的记录数记为P1,P2.......

计算出预估值Cardinality(P1+P2+....P8)*A/8。

由于Cardinality是通过采样计算出的数值,所以每次执行SHOW INDEX FROM tb_user;查询出的结果值可能不同。


索引优化


本文主要对联合索引讨论。


慢查询日志


如何查看慢查询日志呢?可以通过如下sql


SHOW VARIABLES LIKE '%query%' ;


默认情况下慢查询是关闭状态(图上是我已经开启过的),默认超过10s(long_query_time)定义为慢查询,slow_query_log_file慢查询日志的存放目录。可以通过如下sql开启慢查询


set global slow_query_log =1; #开启慢查询或者set global slow_query_log ='on';
set global slow_query_log =0; #关闭慢查询或者set global slow_query_log ='off';
set global long_query_time = 1 ;#修改慢查询时间1s
show variables like '%dir%'; #查看日志存放目录


注意设置完之后,需要关闭navicat连接重新连接才会生效,这只是临时开启慢查询,如果想要永久开启需要修改配置文件


编辑mysql配置文件/etc/my.cnf
[mysqld]
slow_query_log = 1   #是否开启慢查询日志,1表示开启,0表示关闭,也可以使用off和on
long_query_time = 1     #慢查询时间
log-slow-queries=/var/log/slowquery.log     #mysql5.6以下版本
slow-query-log-file=/var/log/slowquery.log  #mysql5.6及以上版本

慢查询会记录查询缓慢的sql记录,以及索引使用情况。


111.png


优化规则


全值匹配


全值匹配是指在查询条件中尽量条件都是索引中的字段,而且索引都使用。另外尽量创建联合索引,因为每一个索引都是一颗B+Tree,在增删改的时候,需要维护这颗B+Tree;


EXPLAIN SELECT * FROM tb_user WHERE `name`='行言孙';
EXPLAIN SELECT * FROM tb_user WHERE `name`='行言孙' AND age=22  ;
EXPLAIN SELECT * FROM tb_user WHERE `name`='行言孙' AND age=22 AND position='武汉信息经理' ;


最左前缀匹配


顾名思义就是最左优先,查找会按照索引的顺序依次查找,索引的最左前列开始并且不跳过索引中的列。


EXPLAIN SELECT * FROM tb_user WHERE `name`='行言孙' AND age=22 AND position='武汉信息经理' ;
EXPLAIN SELECT * FROM tb_user WHERE age=22 AND position='武汉信息经理' ;


上面sql的执行计划如下图


222.png

333.png


第一个查询使用了索引,第二个索引中没有使用索引而使用了全表扫描,可以判断出,如果索引中第一个列没使用上,那么索引就不能使用,而会全表扫描。


EXPLAIN SELECT * FROM tb_user WHERE `name`='行言孙' AND age>22 AND position='武汉信息经理' ;

111.png


实际上这三个索引的key_len应该是221,而实际上是68,证明position这个索引没有用上。可以判断出,如果索引在遇到范围(> 、<、Like 不包括>=、<= )查询时后面的索引就会失效。


如果等值查询中索引顺序变了,索引会生效吗?


EXPLAIN SELECT * FROM tb_user WHERE `name`='行言孙' AND position='武汉信息经理' AND age=22;
EXPLAIN SELECT * FROM tb_user WHERE age=22  AND position='武汉信息经理' AND name='行言孙';
EXPLAIN SELECT * FROM tb_user WHERE position='武汉信息经理' AND age=22  AND name='行言孙';


这三个sql的执行计划是一模一样的,可知在等值查询中如果索引顺序跟索引的顺序不一致依然会使用索引,原因可能是Mysql优化器会将我们的sql进行优化而使用索引。


111.png


like

EXPLAIN SELECT * FROM tb_user WHERE `name`LIKE '%行言孙' AND age=22 AND position='武汉信息经理' ;
EXPLAIN SELECT * FROM tb_user WHERE `name`LIKE '行言孙%' AND age=22 AND position='武汉信息经理' ;


111.png

222.png


通过执行计划可知%或者_(%匹配一个或者多个字符;_匹配一个字符)放在左边的时候是不会使用索引的,如果在右边则可以使用到索引。如果非要把%放在左边则可以使用索引覆盖,这样同样会使用索引。


EXPLAIN SELECT name,age,position FROM tb_user WHERE `name`LIKE '%行言孙' AND age=22 AND position='武汉信息经理' ;


111.png


可以看到同样使用到了索引


不在索引列做计算、函数、类型转换


EXPLAIN SELECT * FROM tb_user WHERE LTRIM(name)= '行言孙' AND age=21 AND position='武汉信息经理' ;


111.png


可知如果在索引列上执行函数户或者计算不会使用到索引。


索引覆盖


从辅助索引中就可以得到查询记录,而不需要查询聚集索引中的记录(回表),辅助索引不包含整行数据,其大小远小于聚集索引,可以减少大量IO操作。尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少select *语句。


EXPLAIN SELECT name,age,position FROM tb_user WHERE name= '行言孙' AND age=21 AND position='武汉信息经理' ;


<>、!=不等号


EXPLAIN SELECT * FROM tb_user WHERE name!='行言孙' AND age=21 AND position='武汉信息经理' ;
EXPLAIN SELECT * FROM tb_user WHERE name='行言孙' AND age<>21 AND position='武汉信息经理' ;


111.png

222.png

当不等号在索引的开头使用时,不会使用索引,而改为全表扫描。不在开头时索引是会使用到索引的,但是<>之后的字段的索引不会使用(同范围查询一样)。


null&is not null


EXPLAIN SELECT * FROM tb_user WHERE name IS NOT NULL AND age<>21 AND position='武汉信息经理' ;
EXPLAIN SELECT * FROM tb_user WHERE name IS NULL AND age<>21 AND position='武汉信息经理' ;


111.png

222.png



可以看出在使用is not null时,并没有使用索引,而在is null时使用了索引。那么为什么呢?通过执行计划可知is not  null 的数据大概有1282633条,Mysql优化器认为如果使用索引然后再回表查询 不如直接全表扫描快,而is null 时才有大概6614条数据。由可以得出结论MySQL中决定使不使用某个索引执行查询的依据很简单:就是成本够不够小。而不是是否在WHERE子句中用了 is null 或者 is not null 。


你可能不信 那么我证明给你看。


可以使用optimizer_trace 分析sql是否选择使用索引。


执行以下sql可以开启trace,默认是关闭的,不建议开启,会消耗mysql的性能,使用完之后记得关闭。


#开启
SET  optimizer_trace='enabled=on',end_markers_in_json=on;
#关闭
SET session optimizer_trace="enabled=off";


注意两条语句要全选中同时执行。


SELECT * FROM tb_user WHERE name IS NOT NULL AND age<>21 AND position='武汉信息经理' ;
SELECT * FROM information_schema.OPTIMIZER_TRACE;


{
  "steps": [
    {
      "join_preparation": { /*sql准备阶段*/
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `tb_user`.`id` AS `id`,`tb_user`.`name` AS `name`,`tb_user`.`age` AS `age`,`tb_user`.`position` AS `position`,`tb_user`.`address` AS `address`,`tb_user`.`create_time` AS `create_time`,`tb_user`.`update_time` AS `update_time`,`tb_user`.`delete_flag` AS `delete_flag` from `tb_user` where ((`tb_user`.`name` is not null) and (`tb_user`.`age` <> 21) and (`tb_user`.`position` = '武汉信息经理'))"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {/*sql优化*/
        "select#": 1,
        "steps": [
          {
            "condition_processing": { /*sql条件*/
              "condition": "WHERE",
              "original_condition": "((`tb_user`.`name` is not null) and (`tb_user`.`age` <> 21) and (`tb_user`.`position` = '武汉信息经理'))",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "((`tb_user`.`name` is not null) and (`tb_user`.`age` <> 21) and (`tb_user`.`position` = '武汉信息经理'))"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "((`tb_user`.`name` is not null) and (`tb_user`.`age` <> 21) and (`tb_user`.`position` = '武汉信息经理'))"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "((`tb_user`.`name` is not null) and (`tb_user`.`age` <> 21) and (`tb_user`.`position` = '武汉信息经理'))"
                }
              ] /* steps */
            } /* condition_processing */
          },
          {
            "substitute_generated_columns": {
            } /* substitute_generated_columns */
          },
          {
            "table_dependencies": [ /*表依赖*/
              {
                "table": "`tb_user`",
                "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": "`tb_user`",
                "range_analysis": { /*全表扫描*/
                  "table_scan": {
                    "rows": 1282633,/*扫描行数*/
                    "cost": 142326/*花费时间*/
                  } /* 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 */
                    },
                    {
                      "index": "address", /*address索引*/
                      "usable": false,
                      "cause": "not_applicable"
                    }
                  ] /* 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": [
                          "NULL < name"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": true,
                        "index_only": false,
                        "in_memory": 0,
                        "rows": 641316,/*索引扫描的行数*/
                        "cost": 476980, /*使用idx_name_age_position索引的成本*/
                        "chosen": false,/*没有使用该索引*/
                        "cause": "cost"
                      }
                    ] /* range_scan_alternatives */,
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    } /* analyzing_roworder_intersect */
                  } /* analyzing_range_alternatives */
                } /* range_analysis */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [ /*考虑的执行计划*/
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`tb_user`", /*全表扫描*/
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 1282633,
                      "access_type": "scan",
                      "resulting_rows": 1.28263e+06,
                      "cost": 142324, /*执行成本*/
                      "chosen": true/*选择这个*/
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 1.28263e+06,
                "cost_for_plan": 142324,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "((`tb_user`.`name` is not null) and (`tb_user`.`age` <> 21) and (`tb_user`.`position` = '武汉信息经理'))",
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`tb_user`",
                  "attached": "((`tb_user`.`name` is not null) and (`tb_user`.`age` <> 21) and (`tb_user`.`position` = '武汉信息经理'))"
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "finalizing_table_conditions": [
              {
                "table": "`tb_user`",
                "original_table_condition": "((`tb_user`.`name` is not null) and (`tb_user`.`age` <> 21) and (`tb_user`.`position` = '武汉信息经理'))",
                "final_table_condition   ": "((`tb_user`.`name` is not null) and (`tb_user`.`age` <> 21) and (`tb_user`.`position` = '武汉信息经理'))"
              }
            ] /* finalizing_table_conditions */
          },
          {
            "refine_plan": [
              {
                "table": "`tb_user`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": { /*sql执行*/
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}


111.png


可以看出使用索引消耗的成本要远比全表扫描要高,所以选择了全表扫描。如果感觉索引没有执行的话,都可以使用这个工具来具体分析一下。


in&exsits&or


少用in、exsits、or用它查询时,mysql不一定使用索引,mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引。


字符串不加引号


字符串上未加引号时是不会使用索引的,但是在mysql8.0.27如果字符串不加引号是会报错的。


111.png


索引下推


Index Condition PushDown(ICP)从Mysql5.6开始。使用ICP时,当进行索引查询时,Mysql在取出索引的同时会进行where条件的过滤,当然where可以过滤的条件是该索引可以覆盖到的范围。这样过滤完之后可以减少大量的回表操作,提高查询效率。默认是开启ICP优化的。


EXPLAIN SELECT * FROM tb_user WHERE name='行言孙' OR  name='德进药' OR name='比张国' AND age=21 AND position='武汉信息经理' ;


111.png


Using index condition表示使用了ICP优化。


MRR


从Mysql5.6开始支持Multi_Range_Read(多范围读),MRR的是为了磁盘的随机访问次数。我们通过辅助索引能够使用到B+树的有序性,但是查出来的主键id未必是有序的。此时通过无序的id主键进行回表扫描的话,此时的IO是随机IO。磁盘IO主要分为两大类:一个随机IO,一个顺序IO。顺序IO的效率大概是随机IO的两个数量级。因此MySQL提出了一个名为Disk-Sweep Multi-Range Read (MRR,多范围 读取)的优化措施,即先读取一部分二级索引记录,将它们的主键值排好序之后再统一执行回表操作。因为磁盘读取数据通过扇区为单位进行读取的。一颗B+树它是有序的。比如说id 1到50 它是第一个扇区的。51到100是第二个扇区的以此类推。假设回表的id是这样的96、23、105、12、88等这些。那么磁盘读取的时候先读取第二个扇区,拿到96的数据,发现没有23的数据,在读取第一个扇区,拿到23的数据,发现没有105的数据,在读取第三个扇区。这样的IO就是随机IO。如果说通过MRR机制排好序了:12、23、88、96、105。读取第一个扇区的时候就能拿到12、23的数据,读取第二个扇区的时候就能拿到88、96的数据,减少了回表的次数。


总结


MRR可适用于range、ref、eq_ref类型的查询,使用MRR的好处


MRR使数据访问变得较为有顺序,在查找辅助索引时,首先根据得到的结果按照主键排序,并按照主键的顺序进行回表查找。

批量处理对键值的操作。MRR还可以将某些范围查询,拆分为键值对,以此来进行批量的数据查询。这样做的好处是可以在拆分过程中,直接过滤一些不符合查询条件的数据。

减少缓冲池中页被替换的次数。数据读取之后是首先存放在一个缓冲池中的,如果缓冲池不够大,此时频繁的离散读取导致缓冲池中的数据被替换出缓冲池,若是按照主键排序进行访问则可以将这种重复行为降到最低。

那么如何开启MRR模式呢?可以通过参数optimizer_switch中的flag来控制。当MRR为on时,表示启用MRR优化。mrr_cost_based表示是否通过costbased的方式来选择是否启用mrr。若设置mrr=on,mrr_cost_based=off,则总是启用MRR优化。


SET @@OPTIMIZER_switch='mrr=on,mrr_cost_based=off';


EXPLAIN SELECT * FROM tb_user WHERE name='行言孙' OR  name='德进药' OR name='比张国' AND age=21 AND position='武汉信息经理' ;


222.png


可以看到已经使用了MRR优化。


111.png


缓存区的大小默认是256K(262144/1024)当超过该值时则执行器对已经缓存的数据进行排序,排序之后获取数据。


长字段索引


如果字段过长,整个字段建立索引就会很浪费空间了,所以可以考虑对字段的前面几个字段建立索引。


ALTER TABLE tb_user ADD INDEX address (address(15));

111.png


分页优化


如果要查询LMIT 1000000,10,虽然只取了10条数据,但是mysql并不能跳跃取值,而是要取出1000010条数据,然后从这里面取出10条。


优化方式一使用主键id


SELECT * FROM tb_user LIMIT 1000000,10;
SELECT * FROM tb_user WHERE id >1000000 LIMIT 10;


优化方式二非主键索引的方式分页优化


EXPLAIN select * from tb_user u inner join (select id from tb_user limit 1000000,10) uid on u.id = uid.id;

770259d1a417490293a94d9fc2494375.png


 优化方式三 分库分表


如果一个标的数据量特别大,可以考虑分表分库,阿里开发手册中推荐,如果单表行数超过500万行或者单表容量超过2G,推荐使用分表分库。


排序优化


1、


EXPLAIN SELECT * FROM tb_user WHERE name='下里国' AND position='天津信息经理' ORDER BY age;


111.png


可以看到用到了索引,age索引列用在排序过程中,因为Extra字段里没有using filesort。根据最左前缀原则,position索引没有用上。


2、


EXPLAIN SELECT  * FROM  tb_user WHERE NAME = '下里国' ORDER BY position;


111.png


由于索引跳过了age所以排序索引没有使用上而显示Using filesort。可知在排序中同样需要满足最左前缀原则,如果索引中断,那么将不会使用后面的索引。


3、


EXPLAIN SELECT * FROM tb_user WHERE name='下里国'  ORDER BY age,position;
EXPLAIN SELECT * FROM tb_user WHERE name='下里国'  ORDER BY position,age;
EXPLAIN SELECT * FROM tb_user WHERE name='下里国'  ORDER BY age DESC ,position DESC;
EXPLAIN SELECT * FROM tb_user WHERE name='下里国'  ORDER BY age ASC,position DESC;


111.png

222.png

333.png

444.png


第一个sql很显然用到了name列上的索引,而extra是null,null意味着用到了索引,但是部分字段未被索引覆盖,必须通过“回表”来实现,不是纯粹地用到了索引,也不是完全没用到索引。


第二个sql由于不符合最左前缀原则(sql优化器没有像等值查询时优化),所以没有使用到索引。


第三个sql Backward index scan是mysql8.0之后才出现的,也是一种mysql的优化方式,叫降序索引。


第四个sql 由于age使用正序,position使用逆序,与创建索引时的顺序不同,所以也没有使用到索引排序。


EXPLAIN SELECT name,age,position FROM tb_user WHERE name='下里国'  ORDER BY age,position;


222.png


当使用覆盖索引时,排序字段用到了索引。


Using filesort排序原理


Using filesort分为单路排序和双路排序


单路排序:是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序。

双路排序(又叫回表排序模式):是首先根据相应的条件取出相应的排序字段和可以直接定位行 数据的行 ID,然后在 sort buffer 中进行排序,排序完后根据主键id获取其他字段的数据-- 相当于要查询两次。

通过trace工具可以看到sort_mode


111.png


可以通过改变 max_length_for_sort_data变量的值来影响mysql选择的算法。因为单路排序为将要排序的每一行创建了固定的缓冲区。如果查询字段总的长度大小比设定的max_length_for_sort_data 要小,则使用单路排序方式;如果查询字段总的长度大小比设定的max_length_for_sort_data 要大,则使用多路排序方式 。注意从 MySQL 8.0.20 开始不推荐使用此变量(max_length_for_sort_data),因为优化器更改使其过时且无效。在MySQL 8.0.20之后该值默认为4096而不是1024。通过以下sql可以查看。


SHOW VARIABLES LIKE '%max_length_for_sort_data%';
SET max_length_for_sort_data = 1024;
#max_length_for_sort_data默认值是4096,最小值是4,最大值是8388608


小结


1、order by语句使用索引最左前缀原则。

2、使用where子句与order by子句条件列组合满足索引最左前缀原则。


3、尽量在索引列上完成排序,遵循索引建立(索引创建的顺序)时的最左前缀原则。


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


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


6、如果和where 条件查询冲突,那优先where条件上的索引。


count优化


-- 临时关闭缓存
 set global query_cache_size=0; 
 set global query_cache_type=0;
 EXPLAIN select count(1) from tb_user;
 EXPLAIN select count(id) from tb_user; 
 EXPLAIN select count(name) from tb_user; 
 EXPLAIN select count(*) from tb_user;

上面的执行计划1、2、4,没有使用主键的聚簇索引,而使用了address列上的索引。原因是Innodb会优先考虑使用索引树较小的索引来查询,因为address比主键索引和idx_name_age_address都小,所以选择了address索引,如果不存在辅助索引,则使用聚簇索引(一般是主键索引)来查询。


333.png


sql3使用了辅助索引


222.png


其实上面的执行效率基本是相同的,阿里开发手册中规定不要使用count(列名)或者count(1)来替代count(*)。count(*)会统计null的行,count(列名)不会统计此列为null的值。count(distinct col )计算该列除Null之外不重复的数量,count(distinct col1,col2)只要有一列全为null,则结果为0。


优化方案


1、为了更快地计数,创建一个计数器表并让您的应用程序根据它所做的插入和删除来更新。


2、SHOW TABLE STATUS


111.png


3、查询mysql自己维护的总行数 对于myisam存储引擎的表做不带where条件的count查询性能是很高的,因为myisam存储引擎的表的总行数会被 mysql存储在磁盘上,查询不需要计算。


参考:

https://blog.csdn.net/weixin_34462016/article/details/113654725


https://blog.csdn.net/admin522043032/article/details/121919494


https://blog.csdn.net/admin522043032/article/details/121281402

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
11天前
|
缓存 关系型数据库 MySQL
MySQL执行计划选择策略:揭秘查询优化的艺术
【10月更文挑战第15天】 在数据库性能优化中,选择最优的执行计划是提升查询效率的关键。MySQL作为一个强大的关系型数据库管理系统,提供了复杂的查询优化器来生成执行计划。本文将深入探讨如何选择合适的执行计划,以及为什么某些计划更优。
30 2
|
2天前
|
SQL 前端开发 关系型数据库
SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
29 9
|
4天前
|
缓存 监控 关系型数据库
如何优化MySQL查询速度?
如何优化MySQL查询速度?【10月更文挑战第31天】
15 3
|
5天前
|
搜索推荐 关系型数据库 MySQL
mysql like查询优化
通过合理的索引设计、使用全文索引、优化查询结构以及考虑分片和分区表,可以显著提高MySQL中 `LIKE`查询的性能。针对不同的应用场景选择合适的优化策略,能够有效地提升数据库查询效率,减少查询时间。希望这些方法和技巧能帮助您优化MySQL数据库中的模糊查询。
26 4
|
6天前
|
缓存 关系型数据库 MySQL
如何优化 MySQL 数据库的性能?
【10月更文挑战第28天】
24 1
|
13天前
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:优化百万数据查询的实战经验
【10月更文挑战第13天】 在处理大规模数据集时,传统的关系型数据库如MySQL可能会遇到性能瓶颈。为了提升数据处理的效率,我们可以结合使用MySQL和Redis,利用两者的优势来优化数据查询。本文将分享一次实战经验,探讨如何通过MySQL与Redis的协同工作来优化百万级数据统计。
38 5
|
10天前
|
SQL 关系型数据库 MySQL
定时任务频繁插入数据导致锁表问题 -> 查询mysql进程
定时任务频繁插入数据导致锁表问题 -> 查询mysql进程
25 1
|
11天前
|
SQL 关系型数据库 MySQL
mysql数据误删后的数据回滚
【11月更文挑战第1天】本文介绍了四种恢复误删数据的方法:1. 使用事务回滚,通过 `pymysql` 库在 Python 中实现;2. 使用备份恢复,通过 `mysqldump` 命令备份和恢复数据;3. 使用二进制日志恢复,通过 `mysqlbinlog` 工具恢复特定位置的事件;4. 使用延迟复制从副本恢复,通过停止和重启从库复制来恢复数据。每种方法都有详细的步骤和示例代码。
|
8天前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
40 0
|
8天前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第26天】数据库作为现代应用系统的核心组件,其性能优化至关重要。本文主要探讨MySQL的索引策略与查询性能调优。通过合理创建索引(如B-Tree、复合索引)和优化查询语句(如使用EXPLAIN、优化分页查询),可以显著提升数据库的响应速度和稳定性。实践中还需定期审查慢查询日志,持续优化性能。
35 0