MySQL ISAM 搜索优化

Posted

技术标签:

【中文标题】MySQL ISAM 搜索优化【英文标题】:MySQL ISAM search optimization 【发布时间】:2018-01-19 08:57:35 【问题描述】:

我有一个包含 1,019,502 条记录的表和一个运行需要 1.6 秒的特定查询。如果可能的话,我想减少运行时间。

该表是 mysql 5.7 上的 INNODB(在 Ubuntu 上):

mysql> describe summary_data;
+--------------+------------------+------+-----+---------+-------+
| Field        | Type             | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+-------+
| propId       | int(10) unsigned | NO   | PRI | NULL    |       |
| elemType     | varchar(50)      | NO   | PRI | NULL    |       |
| sku          | varchar(100)     | NO   | PRI | NULL    |       |
| family       | varchar(100)     | NO   | PRI | NULL    |       |
| subcategory  | varchar(100)     | NO   | PRI | NULL    |       |
| category     | varchar(100)     | NO   | PRI | NULL    |       |
| details      | varchar(255)     | YES  |     | NULL    |       |
| merchSales   | float(12,2)      | YES  |     | NULL    |       |
| orders       | int(10) unsigned | YES  |     | NULL    |       |
| quantity     | int(10) unsigned | YES  |     | NULL    |       |
| margin       | float(12,2)      | YES  |     | NULL    |       |
| grossSales   | float(12,2)      | YES  |     | NULL    |       |
| discount     | float(12,2)      | YES  |     | NULL    |       |
| shipping     | float(12,2)      | YES  |     | NULL    |       |
| tax          | float(12,2)      | YES  |     | NULL    |       |
| createDate   | datetime         | YES  |     | NULL    |       |
| date         | date             | NO   | PRI | NULL    |       |
| dateType     | varchar(10)      | NO   | PRI | NULL    |       |
+--------------+------------------+------+-----+---------+-------+

查询如下:

SET @propId = 1,
@from = '2016-01-01',
@to = '2016-12-31',
@elemType = 'sku',
@sku = NULL,
@family = NULL,
@subcategory = NULL,
@category = NULL;

SELECT SUM(ifnull(merchSales,0)+ifnull(discount,0)) as totalSales
,SUM(ifnull(merchSales,0)) as merchSales
,SUM(ifnull(orders,0)) as orders
,SUM(ifnull(quantity,0)) as quantity
,sum(ifnull(grossSales,0)) as grossSales
,sum(ifnull(discount,0))*(-1) as discount
,sum(ifnull(shipping,0)) as shipping
,elemType
,sku
,family
,category
,subcategory
,details
,SUM(ifnull(margin,0)) as margin
,sum(ifnull(margin,0)) / sum(ifnull(merchSales,0))*100 as marginPerc
,SUM(ifnull(grossSales,0))/SUM(ifnull(orders,0)) as avgOrderVal
,sum(ifnull(merchSales,0)+ifnull(discount,0))/sum(ifnull(margin,0))*100 as marginPercTotal
FROM summary_data
WHERE propId = @propId
AND dateType = 'day'
AND elemType = @elemType
AND (@sku IS NULL OR sku = @sku)
AND (@family IS NULL OR family = @family)
AND (@subcategory IS NULL OR subcategory = @subcategory)
AND (@category IS NULL OR category = @category)
GROUP BY category,subcategory,family,sku
ORDER BY merchSales DESC;

查询使用的索引:

mysql> show indexes from summary_data;
+--------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table        | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| summary_data |          0 | PRIMARY  |            1 | propId      | A         |         218 |     NULL | NULL   |      | BTREE      |         |               |
| summary_data |          0 | PRIMARY  |            2 | elemType    | A         |        1529 |     NULL | NULL   |      | BTREE      |         |               |
| summary_data |          0 | PRIMARY  |            3 | category    | A         |        5528 |     NULL | NULL   |      | BTREE      |         |               |
| summary_data |          0 | PRIMARY  |            4 | subcategory | A         |       11198 |     NULL | NULL   |      | BTREE      |         |               |
| summary_data |          0 | PRIMARY  |            5 | family      | A         |       15678 |     NULL | NULL   |      | BTREE      |         |               |
| summary_data |          0 | PRIMARY  |            6 | sku         | A         |       17470 |     NULL | NULL   |      | BTREE      |         |               |
| summary_data |          0 | PRIMARY  |            7 | dateType    | A         |       17470 |     NULL | NULL   |      | BTREE      |         |               |
| summary_data |          0 | PRIMARY  |            8 | date        | A         |      985490 |     NULL | NULL   |      | BTREE      |         |               |

查询使用了 1,019,502 条记录中的大约 115,000 条。结果返回 2106 个聚合行。

任何建议将不胜感激!

***** 编辑 *****

添加解释:

+----+-------------+--------------+------------+------+----------------------------------+---------+---------+-------------+--------+----------+----------------------------------------------+
| id | select_type | table        | partitions | type | possible_keys                    | key     | key_len | ref         | rows   | filtered | Extra                                        |
+----+-------------+--------------+------------+------+----------------------------------+---------+---------+-------------+--------+----------+----------------------------------------------+
|  1 | SIMPLE      | summary_data | NULL       | ref  | PRIMARY,propId_4,propId_5,propId | PRIMARY | 156     | const,const | 492745 |    10.00 | Using where; Using temporary; Using filesort |
+----+-------------+--------------+------------+------+----------------------------------+---------+---------+-------------+--------+----------+----------------------------------------------+

【问题讨论】:

我可能是错的,因为我没有做任何研究,但我相信 null 值的总和默认为 0。 解释输出说明了什么?请将它(作为文本)添加到您的问题中。 (建议您始终这样做以进行优化) 并遵循 @jhpratt SUM() 忽略 NULL,因此您可以通过反转函数序列来避免在每一行上运行 IFNULL(),例如IFNULL(SUM(列),0) @Used_By_Already 我添加了说明语句。 @jhpratt 我删除了 ifnull() 语句,这将处理时间缩短了 0.02 秒。 TY 的建议。 【参考方案1】:

where 子句中唯一不变的部分包括:

WHERE propId = @propId
AND dateType = 'day'
AND elemType = @elemType

所以 MIGHT 在声明涉及这 3 个字段 propid、elemtype、datetype 的非唯一复合索引时会有一些优势(nb:我不确定我可以指定这样索引中的那些列,可能需要一些实验)我会在定义这样的索引后尝试解释,但在试验时确保这些变量保持 NULL:

@sku = NULL,
@family = NULL,
@subcategory = NULL,
@category = NULL

如果该复合索引有任何改进,现在尝试将这 4 个变量中的任何一个设为非空。对您的解释计划有何影响?然后,您可能会发现您需要在每个列上单独使用非唯一索引,以帮助支持 where 子句的可变性。

即当您更改变量时,解释计划也会有所不同。

但是:在超过 100 万行的大约 1.6 秒时,您将进入收益递减领域。

【讨论】:

TY @Used_By_Already。我今天要玩弄你的建议。此外,我想在 where 子句中再添加一个常量:日期范围。 where 子句中总会有一个 data > 和 date 我尝试了几次复合索引变体的迭代,包括您的具体建议,都增加了搜索时间。我确实发现调整 PRIMARY KEY 以在 cat/subcat/family/sku 之前对 datetype/date 列进行优先级排序并包括选定的列会产生重大影响。这个主键将搜索时间减少到0.8s:添加主键(propId,elemType,dateType,date,category,subcategory,family,sku,merchSales,grossSales,quantity,orders,shipping,tax,details)。仍在寻求改进 - 感谢您的帮助! 这是个好消息,而且在我看来你已经掌握了使用和利用解释输出的技能。干得好。

以上是关于MySQL ISAM 搜索优化的主要内容,如果未能解决你的问题,请参考以下文章

mysql 数据存储引擎区别

优化 MySQL 全文搜索查询?

Mysql查询优化——范围搜索

优化位置搜索mysql脚本

优化 MySQL 查询以进行整数范围搜索

优化 MySQL Query - 多列搜索条件