重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
修改mysql配置文件,优化缓存大小和连接数连接方式,优化sql语句 ,记得mysql好像是有工具可以查看最占用资源的sql语句,找到他,优化他。
创新互联建站是一家集网站建设,松山企业网站建设,松山品牌网站建设,网站定制,松山网站建设报价,网络营销,网络优化,松山网站推广为一体的创新建站企业,帮助传统企业提升企业形象加强企业竞争力。可充分满足这一群体相比中小企业更为丰富、高端、多元的互联网需求。同时我们时刻保持专业、时尚、前沿,时刻以成就客户成长自我,坚持不断学习、思考、沉淀、净化自己,让我们为更多的企业打造出实用型网站。
安装好mysql后,配制文件应该在/usr/local/mysql/share/mysql目录中,配制文件有几个,有my-huge.cnf my-medium.cnf my-large.cnf my-small.cnf,不同的流量的网站和不同配制的服务器环境,当然需要有不同的配制文件了。
一般的情况下,my-medium.cnf这个配制文件就能满足我们的大多需要;一般我们会把配置文件拷贝到/etc/my.cnf 只需要修改这个配置文件就可以了,使用mysqladmin variables extended-status _u root _p 可以看到目前的参数,有3个配置参数是最重要的,即key_buffer_size,query_cache_size,table_cache。
key_buffer_size只对MyISAM表起作用,
key_buffer_size指定索引缓冲区的大小,它决定索引处理的速度,尤其是索引读的速度。一般我们设为16M,实际上稍微大一点的站点 这个数字是远远不够的,通过检查状态值Key_read_requests和Key_reads,可以知道key_buffer_size设置是否合理。比例 key_reads / key_read_requests应该尽可能的低,至少是1:100,1:1000更好(上述状态值可以使用SHOW STATUS LIKE ‘key_read%’获得)。 或者如果你装了phpmyadmin 可以通过服务器运行状态看到,笔者推荐用phpmyadmin管理mysql,以下的状态值都是本人通过phpmyadmin获得的实例分析:
这个服务器已经运行了20天
key_buffer_size _ 128M
key_read_requests _ 650759289
key_reads - 79112
比例接近1:8000 健康状况非常好
目前公司的订单表有100多万条,使用订单号查询数据时,所需时间大多要10-30秒不等,查看了慢查询日志,发现有的订单查询竟然耗时65秒
我查看了原有的查询语句,发现where后面跟了or查询,虽然3个or都索引,使用explain分析查询结果,发现要扫描近70万行,几乎是全盘扫描一遍,只为获取最多3条数据,效率实在是低下
这3个字段均设置了索引,但or在这个语句中,使索引失效了(主要看最后几行)
使用union all代替or查询,也就是说把3个字段的查询分别做查询,将结果使用union all连接在一起,这样单次查询可以用到索引,效率大大提高
先看一下分析结果
简要的sql语句,查询结果不超80ms
在开始演示之前,我们先介绍下两个概念。
概念一,数据的可选择性基数,也就是常说的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。
范围访问方法使用一个索引来检索包含一个或多个索引间隔中的表行的子集。它可以使用索引中的一列或者多列,以下各节描述了优化器使用范围访问的条件
对于一个单列索引,索引值间隔可以方便地由 WHERE 条件中的相应条件表示,表示为范围条件而不是 intervals 。
上述的 常量 指以下情况之一:
以下是在 WHERE 子句中具有范围条件的查询示例
一些非常量可能会在优化器传播阶段转换为常量
MySQL对于每个可能使用的索引,尝试从 WHERE 子句中提取范围条件。在提取过程中,不能用于构建条件范围的条件被删除,产生重复范围的条件被合并,产生空范围的条件被删除。
假设有以下语句, key1 是一个被索引的列,而 nonkey 没有索引
提取 key1 索引的过程如下:
通常,范围扫描使用的条件比 WHERE 子句中的限制要少()。MySQL执行额外的检查来过滤满足范围条件但是不完全满足 WHERE 子句的行。
范围条件提取算法可以处理任意深度嵌套的 AND/OR 构造,并且它的输出不取决于条件在 WHERE 子句中出现的顺序
MySQL不支持为空间索引的 range 访问合并多个范围。要解决此限制,可以在相同的 SELECT 语句中使用 UNION 语句,将每个空间谓词放在不同的 SELECT 中。
多列索引的范围条件是单列索引的扩展,多列索引的范围条件将索引行限制在一个或多个索引元组的间隔中。索引元组间隔是一个按照索引顺序的,索引元组的集合。
假设有一个多列索引 key1(key_part1,key_part2,key_part3) ,按照索引顺序,具有以下键值元组列表
key_part1 = 1 定义了一个间隔: (1,-inf,-inf) = (key_part1,key_part2,key_part3) (1,+inf,+inf) ,这个间隔包括上面的第4、5、6个元组并且可以被用来进行范围访问。
但是, key_part3 = 'abc' 没有定义间隔并且不能被范围访问方法使用。
就是索引的最左前缀原则,B树索引是有序的,多列索引是首先按照第一列进行排序,然后在第一列排序的基础上,再对第二列数据进行排序,所以后面的列的顺序独立来看不是有序的,就不能单独用后面的列来进行排序或者范围访问的操作。
对于 HASH 索引,只能使用包含相同值的每个间隔。这意味着只能针对以下形式的条件生成间隔:
这里, const1,const2... 是常量, cmp 是比较表达式: =,=,IS NULL ,并且条件覆盖所有的索引部分(就是说,如果有 N 个条件,那么每个条件都需要是一个 N列 索引的一部分)。例如:以下是一个三列 HASH 索引的一个范围条件
对于 BTREE 索引,一个间隔可以是使用 AND 组成的多个范围条件的集合,每个条件都将索引的一部分和一个常量使用 =,=,IS NULL,,,=,=,!=,,BETWEENT,LIKE 'pattern'(pattern不以通配符开始) 进行比较。只要可以确定与条件匹配的一个索引元组,就可以使用一个间隔( !=, 使用两个间隔)
当比较运算符是 =,=,IS NULL 时,优化器尝试使用索引的其他部分来确定间隔。如果比较运算符是 , , =, =, !=, , BETWEEN, LIKE ,优化器使用索引,但不考虑索引中的其他列。
对于以下表达式,优化器使用第一个 = ,也会使用第二个 = ,但是忽略其他索引部分,并且不将第三部分用作间隔构造。
key_part1 = 'foo' AND key_part2 = 10 AND key_part3 10
单个间隔为:
创建的间隔中可能包括比原始条件更多的行,比如,前面这个间隔可能会包括 ('foo',11,0) 这个值, 010 ,这个值不满足原始条件
如果覆盖间隔中的行集合的条件使用 OR 进行组合,则他们会形成间隔的并集。
如果条件使用 AND 进行组合,他们形成一个包括间隔交集的行集合。
示例:
这个在两列索引上的条件:
(key_part1 = 1 AND key_part2 2) OR (key_part1 5)
间隔是:
可以查看 EXPLAIN 输出中的 key_len 部分查看使用的索引前缀的最大长度。
在某些情况下, key_len 包括已使用的索引列,但是这个列可能不是你期望的,假设 key_part1 和 key_part2 可以为 NULL ,然后, key_len 显示以下条件的两个索引部分长度:
key_part1 = 1 AND key_part2 2
但是实际上,这个条件被转换为:
key_part1 = 1 AND key_part2 IS NOT NULL
假设以下表达式, col_name 是一个索引的列
只要 col_name 等同于这些值中的任意一个,这个表达式结果就是 true 。这种比较是等值范围比较(其中的“范围”是一个单独的值)。
优化器按照以下方法,估算读取相等的值来进行等值范围比较的成本:
当使用 index dive 时,优化器在每个范围的末端进行 dive 并且使用该范围中的行数作为估算值。例如: col_name IN (10, 20, 30) 具有三个等值范围,优化器对每个范围进行两次 dive 以生成估算值。每次 dive 都会得出具有给定值的行数的估算值。
使用 index dive 提供了准确的行数估算值,但是随着表达式中要比较的值的数量增加,优化器需要使用更长的时间来生成行数的估算值。而使用索引统计信息的准确性不如直接使用索引,但是可以对大表进行更快的估算。
eq_range_index_dive_limit 选项可以控制优化器选择评估策略的值。要对 N 个等值范围使用 index dive ,将 eq_range_index_dive_limit 设置为 N+1 ,要禁用统计信息,总是使用 index dive ,将 eq_range_index_dive_limit 设置为0。
在MySQL8.0以前,除了使用 eq_range_index_dive_limit ,没有其他方法可以跳过 index dive 。在MySQL8.0中,当满足以下条件时,跳过 index dive :
对于 EXPLAIN FOR CONNECTION ,如果跳过了 index dive ,输出结果有所变更:
不包括 FOR CONNECTION 的 EXPLAIN 输出没有变化
在执行跳过 index dive 的查询后, INFORMATION_SCHEMA.OPTIMIZER_TRACE 表包含一个值为 skipped_due_to_force_index 的 index_dives_for_range_access 行
优化器可以对这种形式的查询进行范围扫描:
SELECT ... FROM t1 WHERE ( col_1, col_2 ) IN (( 'a', 'b' ), ( 'c', 'd' ));
要使用范围扫描,查询必须满足以下条件:
要控制有多少内存可以用来进行范围优化,使用 range_optimizer_max_mem_size 变量
使用以下原则估算范围扫描使用的内存:
IN() 中的每个值被当做使用 OR 结合的一个谓词。如果有两个 IN() 列表,每个列表中都是列表中的值的数量个谓词通过 OR 结合。在这种情况下,视作 M × N 个 谓词通过OR 结合。