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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
日志服务 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

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
打赏
0
0
0
0
9
分享
相关文章
MySQL底层概述—8.JOIN排序索引优化
本文主要介绍了MySQL中几种关键的优化技术和概念,包括Join算法原理、IN和EXISTS函数的使用场景、索引排序与额外排序(Using filesort)的区别及优化方法、以及单表和多表查询的索引优化策略。
MySQL底层概述—8.JOIN排序索引优化
MySQL底层概述—7.优化原则及慢查询
本文主要介绍了:Explain概述、Explain详解、索引优化数据准备、索引优化原则详解、慢查询设置与测试、慢查询SQL优化思路
MySQL底层概述—7.优化原则及慢查询
MySQL和SQLSugar百万条数据查询分页优化
在面对百万条数据的查询时,优化MySQL和SQLSugar的分页性能是非常重要的。通过合理使用索引、调整查询语句、使用缓存以及采用高效的分页策略,可以显著提高查询效率。本文介绍的技巧和方法,可以为开发人员在数据处理和查询优化中提供有效的指导,提升系统的性能和用户体验。掌握这些技巧后,您可以在处理海量数据时更加游刃有余。
41 9
基于SQL Server / MySQL进行百万条数据过滤优化方案
对百万级别数据进行高效过滤查询,需要综合使用索引、查询优化、表分区、统计信息和视图等技术手段。通过合理的数据库设计和查询优化,可以显著提升查询性能,确保系统的高效稳定运行。
17 2
图解MySQL【日志】——磁盘 I/O 次数过高时优化的办法
当 MySQL 磁盘 I/O 次数过高时,可通过调整参数优化。控制刷盘时机以降低频率:组提交参数 `binlog_group_commit_sync_delay` 和 `binlog_group_commit_sync_no_delay_count` 调整等待时间和事务数量;`sync_binlog=N` 设置 write 和 fsync 频率,`innodb_flush_log_at_trx_commit=2` 使提交时只写入 Redo Log 文件,由 OS 择机持久化,但两者在 OS 崩溃时有丢失数据风险。
23 3
MySQL进阶突击系列(09)数据磁盘存储模型 | 一行数据怎么存?
文中详细介绍了MySQL数据库中一行数据在磁盘上的存储机制,包括表空间、段、区、页和行的具体结构,以及如何设计和优化行数据存储以提高性能。
【YashanDB 知识库】MySQL 迁移至崖山 char 类型数据自动补空格问题
问题分类】功能使用 【关键字】char,char(1) 【问题描述】MySQL 迁移至崖山环境,字段类型源端和目标端都为 char(2),但应用存储的数据为'0'、'1',此时崖山查询该表字段时会自动补充空格 【问题原因分析】mysql 有 sql_mode 控制,检查是否启用了 PAD_CHAR_TO_FULL_LENGTH SQL 模式。如果启用了这个模式,MySQL 才会保留 CHAR 类型字段的尾随空格,默认没有启动。 #查看sql_mode mysql> SHOW VARIABLES LIKE 'sql_mode'; 【解决/规避方法】与应用确认存储的数据,正确定义数据
E-Mapreduce如何处理RDS的数据
目前网站的一些业务数据存在了数据库中,这些数据往往需要做进一步的分析,如:需要跟一些日志数据关联分析,或者需要进行一些如机器学习的分析。在阿里云上,目前E-Mapreduce可以满足这类进一步分析的需求。
4990 0
Docker Compose V2 安装常用数据库MySQL+Mongo
以上内容涵盖了使用 Docker Compose 安装和管理 MySQL 和 MongoDB 的详细步骤,希望对您有所帮助。
135 42
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等