mysql查询优化器应该怎么使用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql查询优化器应该怎么使用相关的知识,希望对你有一定的参考价值。

在开始演示之前,我们先介绍下两个概念。


概念一,数据的可选择性基数,也就是常说的cardinality值。


查询优化器在生成各种执行计划之前,得先从统计信息中取得相关数据,这样才能估算每步操作所涉及到的记录数,而这个相关数据就是cardinality。简单来说,就是每个值在每个字段中的唯一值分布状态。


比如表t1有100行记录,其中一列为f1。f1中唯一值的个数可以是100个,也可以是1个,当然也可以是1到100之间的任何一个数字。这里唯一值越的多少,就是这个列的可选择基数。


那看到这里我们就明白了,为什么要在基数高的字段上建立索引,而基数低的的字段建立索引反而没有全表扫描来的快。当然这个只是一方面,至于更深入的探讨就不在我这篇探讨的范围了。


概念二,关于HINT的使用。


这里我来说下HINT是什么,在什么时候用。


HINT简单来说就是在某些特定的场景下人工协助mysql优化器的工作,使她生成最优的执行计划。一般来说,优化器的执行计划都是最优化的,不过在某些特定场景下,执行计划可能不是最优化。


比如:表t1经过大量的频繁更新操作,(UPDATE,DELETE,INSERT),cardinality已经很不准确了,这时候刚好执行了一条SQL,那么有可能这条SQL的执行计划就不是最优的。为什么说有可能呢?


来看下具体演示


譬如,以下两条SQL,

    A:

    select * from t1 where f1 = 20;

    B:

    select * from t1 where f1 = 30;

    如果f1的值刚好频繁更新的值为30,并且没有达到MySQL自动更新cardinality值的临界值或者说用户设置了手动更新又或者用户减少了sample page等等,那么对这两条语句来说,可能不准确的就是B了。

    这里顺带说下,MySQL提供了自动更新和手动更新表cardinality值的方法,因篇幅有限,需要的可以查阅手册。

    那回到正题上,MySQL 8.0 带来了几个HINT,我今天就举个index_merge的例子。

    示例表结构:

    mysql> desc t1;+------------+--------------+------+-----+---------+----------------+| Field      | Type         | Null | Key | Default | Extra          |+------------+--------------+------+-----+---------+----------------+| id         | int(11)      | NO   | PRI | NULL    | auto_increment || rank1      | int(11)      | YES  | MUL | NULL    |                || rank2      | int(11)      | YES  | MUL | NULL    |                || log_time   | datetime     | YES  | MUL | NULL    |                || prefix_uid | varchar(100) | YES  |     | NULL    |                || desc1      | text         | YES  |     | NULL    |                || rank3      | int(11)      | YES  | MUL | NULL    |                |+------------+--------------+------+-----+---------+----------------+7 rows in set (0.00 sec)

    表记录数:

    mysql> select count(*) from t1;+----------+| count(*) |+----------+|    32768 |+----------+1 row in set (0.01 sec)

    这里我们两条经典的SQL:

    SQL C:

    select * from t1 where rank1 = 1 or rank2 = 2 or rank3 = 2;

    SQL D:

    select * from t1 where rank1 =100  and rank2 =100  and rank3 =100;

    表t1实际上在rank1,rank2,rank3三列上分别有一个二级索引。

    那我们来看SQL C的查询计划。

    显然,没有用到任何索引,扫描的行数为32034,cost为3243.65。

    mysql> explain  format=json select * from t1  where rank1 =1 or rank2 = 2 or rank3 = 2\\G*************************** 1. row ***************************EXPLAIN:  "query_block":    "select_id": 1,    "cost_info":      "query_cost": "3243.65"    ,    "table":      "table_name": "t1",      "access_type": "ALL",      "possible_keys": [        "idx_rank1",        "idx_rank2",        "idx_rank3"      ],      "rows_examined_per_scan": 32034,      "rows_produced_per_join": 115,      "filtered": "0.36",      "cost_info":        "read_cost": "3232.07",        "eval_cost": "11.58",        "prefix_cost": "3243.65",        "data_read_per_join": "49K"      ,      "used_columns": [        "id",        "rank1",        "rank2",        "log_time",        "prefix_uid",        "desc1",        "rank3"      ],      "attached_condition": "((`ytt`.`t1`.`rank1` = 1) or (`ytt`.`t1`.`rank2` = 2) or (`ytt`.`t1`.`rank3` = 2))"      1 row in set, 1 warning (0.00 sec)

    我们加上hint给相同的查询,再次看看查询计划。

    这个时候用到了index_merge,union了三个列。扫描的行数为1103,cost为441.09,明显比之前的快了好几倍。

    mysql> explain  format=json select /*+ index_merge(t1) */ * from t1  where rank1 =1 or rank2 = 2 or rank3 = 2\\G*************************** 1. row ***************************EXPLAIN:  "query_block":    "select_id": 1,    "cost_info":      "query_cost": "441.09"    ,    "table":      "table_name": "t1",      "access_type": "index_merge",      "possible_keys": [        "idx_rank1",        "idx_rank2",        "idx_rank3"      ],      "key": "union(idx_rank1,idx_rank2,idx_rank3)",      "key_length": "5,5,5",      "rows_examined_per_scan": 1103,      "rows_produced_per_join": 1103,      "filtered": "100.00",      "cost_info":        "read_cost": "330.79",        "eval_cost": "110.30",        "prefix_cost": "441.09",        "data_read_per_join": "473K"      ,      "used_columns": [        "id",        "rank1",        "rank2",        "log_time",        "prefix_uid",        "desc1",        "rank3"      ],      "attached_condition": "((`ytt`.`t1`.`rank1` = 1) or (`ytt`.`t1`.`rank2` = 2) or (`ytt`.`t1`.`rank3` = 2))"      1 row in set, 1 warning (0.00 sec)

    我们再看下SQL D的计划:

    不加HINT,

    mysql> explain format=json select * from t1 where rank1 =100 and rank2 =100 and rank3 =100\\G*************************** 1. row ***************************EXPLAIN:  "query_block":    "select_id": 1,    "cost_info":      "query_cost": "534.34"    ,    "table":      "table_name": "t1",      "access_type": "ref",      "possible_keys": [        "idx_rank1",        "idx_rank2",        "idx_rank3"      ],      "key": "idx_rank1",      "used_key_parts": [        "rank1"      ],      "key_length": "5",      "ref": [        "const"      ],      "rows_examined_per_scan": 555,      "rows_produced_per_join": 0,      "filtered": "0.07",      "cost_info":        "read_cost": "478.84",        "eval_cost": "0.04",        "prefix_cost": "534.34",        "data_read_per_join": "176"      ,      "used_columns": [        "id",        "rank1",        "rank2",        "log_time",        "prefix_uid",        "desc1",        "rank3"      ],      "attached_condition": "((`ytt`.`t1`.`rank3` = 100) and (`ytt`.`t1`.`rank2` = 100))"      1 row in set, 1 warning (0.00 sec)

    加了HINT,

    mysql> explain format=json select /*+ index_merge(t1)*/ * from t1 where rank1 =100 and rank2 =100 and rank3 =100\\G*************************** 1. row ***************************EXPLAIN:  "query_block":    "select_id": 1,    "cost_info":      "query_cost": "5.23"    ,    "table":      "table_name": "t1",      "access_type": "index_merge",      "possible_keys": [        "idx_rank1",        "idx_rank2",        "idx_rank3"      ],      "key": "intersect(idx_rank1,idx_rank2,idx_rank3)",      "key_length": "5,5,5",      "rows_examined_per_scan": 1,      "rows_produced_per_join": 1,      "filtered": "100.00",      "cost_info":        "read_cost": "5.13",        "eval_cost": "0.10",        "prefix_cost": "5.23",        "data_read_per_join": "440"      ,      "used_columns": [        "id",        "rank1",        "rank2",        "log_time",        "prefix_uid",        "desc1",        "rank3"      ],      "attached_condition": "((`ytt`.`t1`.`rank3` = 100) and (`ytt`.`t1`.`rank2` = 100) and (`ytt`.`t1`.`rank1` = 100))"      1 row in set, 1 warning (0.00 sec)

    对比下以上两个,加了HINT的比不加HINT的cost小了100倍。

    总结下,就是说表的cardinality值影响这张的查询计划,如果这个值没有正常更新的话,就需要手工加HINT了。相信MySQL未来的版本会带来更多的HINT。

参考技术A 、MySQL数据库几配置选项帮助我及捕获低效SQL语句 一slow_query_log 参数设置ON捕获执行间超定数值SQL语句 二long_query_time SQL语句执行间超数值记录志建议设置一或者更短 三slow_query_log_file 记录志文件名 四log_queries_not_using_indexes 参数设置ON捕获所未使用索引SQL语句尽管SQL语句能执行挺快 二、检测mysqlsql语句效率 一、通查询志 (一)、Windows启MySQL慢查询 MySQLWindows系统配置文件般my.ini找[mysqld]面加 代码 log-slow-queries = F:/MySQL/log/mysqlslowquerylog long_query_time = 二 (二)、Linux启用MySQL慢查询 MySQLWindows系统配置文件般my中国f找[mysqld]面加 代码 log-slow-queries=/data/mysqldata/slowquerylog long_query_time=二 说明 log-slow-queries = F:/MySQL/log/mysqlslowquery 慢查询志存放位置般目录要MySQL运行帐号写权限般都目录设置MySQL数据存放目录; long_query_time=二二表示查询超两秒才记录; 二.show processlist 命令 SHOW PROCESSLIST显示哪些线程运行您使用mysqladmin processlist语句信息 各列含义用途: ID列 标识要kill语句候用用命令杀掉查询 /*/mysqladmin kill 进程号 user列 显示单前用户root命令显示权限范围内sql语句 host列 显示语句哪ip哪端口发用于追踪问题语句用户 db列 显示进程目前连接哪数据库 command列 显示前连接执行命令般休眠(sleep)查询(query)连接(connect) time列 状态持续间单位秒 state列 显示使用前连接sql语句状态重要列续所状态描述请注意state语句执行某状态 sql语句查询例能需要经copying to tmp tableSorting resultSending data等状态才完 info列 显示sql语句度限所sql语句显示全判断问题语句重要依据 命令关键state列mysql列状态主要几种: Checking table  检查数据表(自) Closing tables  表修改数据刷新磁盘同关闭已经用完表快操作应该确认磁盘空间否已经满或者磁盘否处于重负 Connect Out  复制服务器连接主服务器 Copying to tmp table on disk  由于临结集于tmp_table_size临表内存存储转磁盘存储节省内存 Creating tmp table  创建临表存放部查询结 deleting from main table  服务器执行表删除第部刚删除第表 deleting from reference tables  服务器执行表删除第二部删除其表记录 Flushing tables  执行FLUSH TABLES等待其线程关闭数据表 Killed  发送kill请求给某线程线程检查kill标志位同放弃kill请求MySQL每主循环检查kill标志位些情况该线程能段才能死掉该线程程其线程锁住kill请求锁释放马效 Locked  其查询锁住 Sending data  处理SELECT查询记录同结发送给客户端 Sorting for group  GROUP BY做排序  Sorting for order  ORDER BY做排序 Opening tables  程应该快除非受其素干扰例执ALTER TABLE或LOCK TABLE语句行完前数据表其线程打尝试打表 Removing duplicates  执行SELECT DISTINCT式查询MySQL前阶段优化掉些重复记录MySQL需要再掉重复记录再结发送给客户端 Reopen table  获表锁必须表结构修改才能获锁已经释放锁关闭数据表尝试重新打数据表 Repair by sorting  修复指令排序创建索引 Repair with keycache  修复指令利用索引缓存创建新索引比Repair by sorting慢些 Searching rows for update  讲符合条件记录找备更新必须UPDATE要修改相关记录前完 Sleeping  等待客户端发送新请求. System lock  等待取外部系统锁前没运行mysqld服务器同请求同表通增加--skip-external-locking参数禁止外部系统锁 Upgrading lock  INSERT DELAYED尝试取锁表插入新记录 Updating  搜索匹配记录并且修改 User Lock  等待GET_LOCK() Waiting for tables  该线程通知数据表结构已经修改需要重新打数据表取新结构能重新打数据表必须等所其线程关闭表几种情况产通知:FLUSH TABLES tbl_name, ALTER TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE,或OPTIMIZE TABLE waiting for handler insert  INSERT DELAYED已经处理完所待处理插入操作等待新请求  部状态应快操作要线程保持同状态几秒钟能问题发需要检查  其状态没面列部查看服务器否存错误才用着 例图: 三、explain解SQL执行状态 explain显示mysql何使用索引处理select语句及连接表帮助选择更索引写更优化查询语句 使用select语句前加explain: 例: explain select surname,first_name form a,b where a.id=b.id 结图 EXPLAIN列解释 table 显示行数据关于哪张表 type 重要列显示连接使用何种类型差连接类型const、eq_reg、ref、range、indexheALL possible_keys 显示能应用张表索引空没能索引相关域WHERE语句选择合适语句 key 实际使用索引NULL则没使用索引少情况MYSQL选择优化足索引种情况SELECT语句 使用USE INDEX(indexname)强制使用索引或者用IGNORE INDEX(indexname)强制MYSQL忽略索引 key_len 使用索引度损失精确性情况度越短越 ref 显示索引哪列使用能数 rows MYSQL认必须检查用返请求数据行数 Extra 关于MYSQL何解析查询额外信息表四.三讨论看坏例Using temporaryUsing filesort意思MYSQL根本能使用索引结检索慢 extra列返描述意义 Distinct 旦MYSQL找与行相联合匹配行再搜索 Not exists MYSQL优化LEFT JOIN旦找匹配LEFT JOIN标准行再搜索 Range checked for each Record(index map:#) 没找理想索引于前面表每行组合MYSQL检查使用哪索引并用表返行使用索引慢连接 Using filesort 看候查询需要优化MYSQL需要进行额外步骤发现何返行排序根据连接类型及存储排序键值匹配条件全部行行指针排序全部行 Using index 列数据仅仅使用索引信息没读取实际行表返发表全部请求列都同索引部候 Using temporary 看候查询需要优化MYSQL需要创建临表存储结通发同列集进行ORDER BYGROUP BY Where used 使用WHERE句限制哪些行与张表匹配或者返给用户想返表全部行并且连接类型ALL或index发或者查询问题同连接类型解释(按照效率高低顺序排序) const 表记录值能够匹配查询(索引主键或惟索引)行值实际数MYSQL先读值做数待 eq_ref 连接MYSQL查询前面表每记录联合都表读取记录查询使用索引主键或惟键全部使用 ref 连接类型查询使用惟或主键键或者些类型部(比利用左边前缀)发于前表每行联合全部记录都表读类型严重依赖于根据索引匹配记录少—越少越 range 连接类型使用索引返范围行比使用>或<查找东西发情况 index 连接类型前面表每记录联合进行完全扫描(比ALL更索引般于表数据) ALL 连接类型于前面每记录联合进行完全扫描般比较糟糕应该尽量避本回答被提问者采纳

MySQL查询优化器工作原理解析

手册上查询优化器概述

查询优化器的任务是发现执行SQL查询的最佳方案。大多数查询优化器,包括MySQL的查询优化器,总或多或少地在所有可能的查询评估方案中搜索最佳方案。对于联接查询,MySQL优化器所调查的可能的方案数随查询中所引用的表的数目呈指数增长。对于小数量的表(典型小于7-10),这不是一个问题。然而,当提交的查询更大时,查询优化所花的时间会很容易地成为服务器性能的主要瓶颈。 
查询优化的一个更加灵活的方法是允许用户控制优化器详尽地搜索最佳查询评估方案。一般思想是优化器调查的方案越少,它编译一个查询所花费的时间越少。另一方面,因为优化器跳过了一些方案,它可能错过一个最佳方案。 
优化器关于方案数量评估的行为可以通过两个系统变量来控制:

  • optimizer_prune_level变量告诉优化器根据对每个表访问的行数的估计跳过某些方案。我们的试验显示该类“有根据的猜测”很少错过最佳方案,并且可以大大降低查询编辑次数。这就是为什么默认情况该选项为on(optimizer_prune_level=1)。然而,如果你认为优化器错过了一个更好的查询方案,则该选项可以关闭(optimizer_prune_level=0),风险是查询编辑花费的时间更长。请注意即使使用该启发,优化器仍然可以探测呈指数数目的方案。

  • ptimizer_search_depth变量告诉优化器对于每个未完成的“未来的”方案,应查看多深,以评估是否应对它进一步扩大。optimizer_search_depth值较小会使查询编辑次数大大减小。例如,如果optimizer_search_depth接近于查询中表的数量,对12、13或更多表的查询很可能需要几小时甚至几天的时间来编译。同时,如果用optimizer_search_depth等于3或4编辑,对于同一个查询,编译器编译时间可以少于1分钟。如果不能确定合理的optimizer_search_depth值,该变量可以设置为0,告诉优化器自动确定该值。 
    我们可以通过show variables 来查看这些参数 
    技术图片 
    备注(手册网址:http://doc.mysql.cn/mysql5/refman-5.1-zh.html-chapter

个人理解

从官方手册上看,可以理解为,MySQL采用了基于开销的优化器,以确定处理查询的最解方式,也就是说执行查询之前,都会先选择一条自以为最优的方案,然后执行这个方案来获取结果。在很多情况下,MySQL能够计算最佳的可能查询计划,但在某些情况下,MySQL没有关于数据的足够信息,或者是提供太多的相关数据信息,估测就不那么友好了。 
但是感觉手册上,并没有说MySQL怎么去寻找最优方案呢? 
通过查询相应的资料,个人理解如下 
MySQL优化器中,一个主要的目标是只要可能就是用索引,而且使用条件最严格的索引来尽可能多、尽可能快地排除那些不符合索引条件的数据行,说白了就是选择怎样使用索引,当然优化器还受其他的影响。为了更直观,下面将通过例子来说明。 
创建一个表:

CREATE TABLE t8(
id1 INT NOT NULL ,
id2 INT NOT NULL,
KEY id1_key(`id1`),
KEY id2_key(`id2`)
) ENGINE=MYISAM DEFAULT CHARSET=utf8;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

插入几行数据如下: 
技术图片 
当我执行如下查询语句时候,查询优化器会怎样进行优化呢?

select * from t8 where id1=1 and id2=0;

当然,MySQL不会傻到,从t8表中的一行开始,然后一行行的去比较,id1与id2。优化器会先分析数据表,得知有索引id1_key与id2_key,如果先判断id1_key的话,然后需要从4行数据中排除3行数据;如果先判断id2_key的话,然后需要从2行中排除1行。对人来说,这两种方式没有什么区别,但是对于程序而言,先判断id2_key需要较少的计算和磁盘输入输出。因此,查询优化器会规定程序,先去检验id2_key索引,然后在从中挑出id2为0的数据行。 
通过下图,我们可以看出,可以选择的索引有id1_key与id2_key,但是实际用到的索引只有id2_key 
技术图片 
如果将SQL语句改为 select * from t8 where id1=1 and id2=0;执行情况也是一样的,不区分前后。如下图: 
技术图片

当然,如果将程序,修改为如下

select * from t8 where id1=5 and id2=0;

也可以分析得出,会使用id1_key索引 
技术图片

当然,如果在创建一个复合索引

ALTER TABLE t8 ADD KEY id1_id2_key(`id1`,`id2`)

此时,在此执行select * from t8 where id1=1 and id2=0; 当然会考虑使用id1_id2_key索引。 
技术图片 
通过上面的例子,可以理解查询优化器在查询的时候,是选择哪一个索引作为最合适的索引。除此,也提示我们,要慎重选择创建索引。如,上面创建了三个索引(id1_key、id1_key、id1_id2_key),但是优化器优化程序时候,每次只能从中选择一个最合适的,如果创建过多,不仅仅是给数据的更新和插入带来了压力,同时也增加了优化器的压力。

分析优化器优化过程中的信息

其实,在上面已经查看过优化器优化过程中的信息,无非就是使用explain。在这里,在集中说说,里面的参数意义。如下图 
技术图片 
id: MySQL Query Optimizer 选定的执行计划中查询的序列号。表示查询中执行 select 子句或操作表的顺序,id值越大优先级越高,越先被执行。id 相同,执行顺序由上至下。 
select_type:查询类型,SIMPLE、PRIMARY、UNION、DEPENDENT UNION等。 
table:显示这一行的数据是关于哪张表的 
type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、indexhe和all 
possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从where语句中选择一个合适的语句 
key: 实际使用的索引。如果为null,则没有使用索引。很少的情况下,mysql会选择优化不足的索引。这种情况下,可以在select语句中使用use index(indexname)来强制使用一个索引或者用ignore index(indexname)来强制mysql忽略索引 
key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好 
ref:显示索引的哪一列被使用了,如果可能的话,是一个常数 
rows:mysql认为必须检查的用来返回请求数据的行数 
extra:关于mysql如何解析查询的额外信息。

调节MySQL优化器的优化

影响索引的选择

当我们在执行select * from t8 where id1=1 and id2=0; 语句的时候,优化器会id1_id2_key索引,但我们可以通过IGNORE INDEX、 IGNORE INDEX来影响索引的选择

强制索引

通过FORCE INDEX(索引1[,索引2])或者使用USE INDEX(索引1[,索引2]),来指定使用哪个索引,也可以指定多个索引,让优化器从中挑选。 
技术图片

技术图片

忽略索引

可以使用IGNORE INDEX(索引1[,索引2])来忽略一些索引,这样优化器,就不会考虑使用这些所有,减少优化器优化时间。 
技术图片

影响优化器使用数据表的顺序

一般情况下,MySQL优化器会自行决定按照哪种顺序扫描数据表才能最快地检索出数据,但是我们可以通过STRAGHT_JOIN强制优化器按特定的顺序使用数据表,毕竟优化器做的判断不一定都是最优的。使用原则是,让限制最强的选取操作最先执行。STRAIGHT_JOIN可以放在SELECT后面,也可以放在FROM子句中。 
如下图 
技术图片

技术图片 
可以看出,无论from t8,t6还是from t6,t8,都是先检索t6中的表。但是使用STRAIGHT_JOIN的话,就会按照SQL中顺序。 
技术图片 
为什么优化器要选择先判断t6中的数据呢?一个主要的原因,因为t6中数据更少。 
技术图片 
如果将t8中数据删除几行后,很明显MySQL优化器选择顺序数据表的顺序就会发生变化。 
技术图片

控制SQL语句的优先权

在高并发的网站中,因为MySQL默认的是写优先,有可能导致一些读操作有效时间内得不到执行机会,HIGH_PRIORITY可以使用在selectinsert操作中,让MYSQL知道,这个操作优先进行。 
技术图片 
LOW_PRIORITY可以使用在insertupdate操作中,让mysql知道,这个操作将优先权将降低。 
技术图片 
INSERT DELAYED告诉MySQL,这个操作将会延时插入。 
INSERT DELAYED INTO,是客户端提交数据给MySQL,MySQL返回OK状态给客户端。而这是并不是已经将数据插入表,而是存储在内存里面等待排队。当mysql有空余时,再插入。另一个重要的好处是,来自许多客户端的插入被集中在一起,并被编写入一个块。这比执行许多独立的插入要快很多,因为它较少了I/O操作。坏处是,不能返回自动递增的ID,以及系统崩溃时,MySQL还没有来得及插入数据的话,这些数据将会丢失。 
技术图片

控制查询缓冲

在实际开发中,一些数据对实时性要求特别高,或者并不经常使用(可能几天就执行一次或两次),这样就需要把缓冲关了,不管这条SQL语句是否被执行过,服务器都不会在缓冲区中查找该数据,每次都会从磁盘中读取。因为如果实时性要求特别高,缓存中数据可能和磁盘中的就不同步,如果数据不经常使用,被缓存起来,就会占用内存。 
在my.ini中的query_cache_type,使用来控制表缓存的。这个变量有三个取值:0,1,2,分别代表了off、on、demand。 
0:表示query cache 是关闭。 
1:表示查询总是先到查询缓存中查找,即使使用了sql_no_cache仍然查询缓存,因为sql_no_cache只是不缓存查询结果,而不是不使用查询结果。 
2:表示只有在使用了SQL_CACHE后,才先从缓冲中查询数据,仍然将查询结果缓存起来。 
我本地缓存是关闭的,,如下图。 
技术图片 
关于MySQL缓存可以参考这里 
http://blog.csdn.net/hsd2012/article/details/51526707

参考网址见:https://www.cnblogs.com/hellohell/p/5718238.html

MySQL索引优化分析,参考网址见:https://blog.csdn.net/qq_30604989/article/details/80904989

 

以上是关于mysql查询优化器应该怎么使用的主要内容,如果未能解决你的问题,请参考以下文章

mysql 一次插入几万条数据应该怎么做优化

我应该如何优化这个 mysql 查询?

怎么优化mysql查询语句

MySQL查询优化器工作原理解析

MySQL查询优化器工作原理解析

MySql性能优化查询优化