优化 MySQL InnoDB 查询最大值,计数

Posted

技术标签:

【中文标题】优化 MySQL InnoDB 查询最大值,计数【英文标题】:Optimize MySQL InnoDB query for max, count 【发布时间】:2019-03-06 11:17:35 【问题描述】:

我有一个 570 万行和 1.9GB 大小的 mysql InnoDB 表:

+-------------------+---------+------+-----+---------+----------------+
|       Field       |  Type   | Null | Key | Default |     Extra      |
+-------------------+---------+------+-----+---------+----------------+
| id                | int(20) | NO   | PRI | NULL    | auto_increment |
| listing_id        | int(20) | YES  |     | NULL    |                |
| listing_link      | text    | YES  |     | NULL    |                |
| transaction_title | text    | YES  |     | NULL    |                |
| image_thumb       | text    | YES  |     | NULL    |                |
| seller_link       | text    | YES  |     | NULL    |                |
| seller_name       | text    | YES  |     | NULL    |                |
| sale_date         | date    | YES  |     | NULL    |                |
+-------------------+---------+------+-----+---------+----------------+

这是我的 3GB RAM 服务器的 my.ini 设置:

key_buffer = 16M
max_allowed_packet = 16M
sort_buffer_size = 8M
net_buffer_length = 8K
read_buffer_size = 2M
read_rnd_buffer_size = 16M
myisam_sort_buffer_size = 8M
log_error = "mysql_error.log"
innodb_autoinc_lock_mode=0
join_buffer_size = 8M
thread_cache_size = 8
thread_concurrency = 8
query_cache_size = 64M
query_cache_limit = 2M
ft_min_word_len = 4
thread_stack = 192K
tmp_table_size = 64M

innodb_buffer_pool_size = 2G
innodb_additional_mem_pool_size = 16M
innodb_log_file_size = 512M
innodb_log_buffer_size = 8M
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 120
innodb_write_io_threads = 8
innodb_read_io_threads = 8
innodb_thread_concurrency = 16
innodb_log_files_in_group = 3
innodb_max_dirty_pages_pct = 90

当我运行下一个查询时,需要 20 多分钟才能返回结果:

SELECT transaction_title, 
       listing_id, 
       seller_name, 
       Max(sale_date) AS sale_date, 
       Count(*)       AS count 
FROM   sales_meta 
WHERE `sale_date` BETWEEN '2017-06-06' AND '2017-06-06' 
GROUP  BY listing_id 
HAVING Count(*) > 1 
ORDER  BY count DESC, 
          seller_name;

我做了一些研究,看来我需要添加一些索引来加快速度,但我很困惑如何去做。有一些单列索引和一些多列索引,我应该做哪一个?

为了让事情变得更复杂,我需要定期在此表上执行一些其他查询:

SELECT * 
FROM   sales_meta 
WHERE ` sale_date `= '2017-06-06'; 

SELECT DISTINCT `seller_name` 
FROM   `sales_meta`; 

这两个可能不那么费力,但我仍然需要尽可能优化它们,尽管三个查询中的第一个是目前的重中之重。

【问题讨论】:

。 .您的查询格式不正确。您的select 包含几个不在group by 中的未聚合列。在优化之前修复查询。 @GordonLinoff 你能否展示一下这个查询的正确查询结构是什么样的,也许作为下面的答案? 。 .我不知道你想做什么,所以,不,我不能。我可以说select 中的未聚合列都应该在group by 中。 也就是说,您将获得一个随机的标题和名称,因为您也没有按它们进行分组。 @Acidon 请发布 SHOW INDEX FROM sales_meta > SIFsales-meta.txt 的文本结果;并告诉我们 3 个查询中的任何一个是否仍然“缓慢”。如果第一个表现良好,你应该能够使用你的 BETWEEN 而不仅仅是 EQUAL 并且有出色的表现。你有多少内存?您是否使用任何 SSD/NVME 进行数据存储? SELECT @@version 的结果是什么;谢谢 【参考方案1】:

如果您只想要一天的值并且数据类型是日期,那么您可以避免使用 between 子句并使用 =

    SELECT transaction_title, 
           listing_id, 
           seller_name, 
           Max(sale_date) AS max_sale_date, 
           Count(*)       AS count 
    FROM   sales_meta 
    WHERE sale_date =  str_to_date('2017-06-06', '%Y-%m-%d')  
    GROUP  BY listing_id 
    HAVING Count(*) > 1 
    ORDER  BY count DESC, seller_name;

并确保您在 sale_date 上有一个索引

【讨论】:

您在此处使用str_to_date 有什么好处吗? @scaisEdge 我确实将此查询用于单日和日期范围的情况,如果摆脱单日查找的 BETWEEN 子句对性能有好处,我一定会实施它。 我不在您的数据内容中,因此您应该尝试并根据结果评估我的建议是否有用.. .. 对于所有性能问题,没有单一的解决方案..有时一个解决方案可以解决一个问题,但会创建其他问题,所以..您应该逐步评估 @WillemRenzema 不应该......因为对于默认格式,转换应该由 mysql 完成......但通常使用显式转换来避免非法日期问题...... @scaisEdge 好的,谢谢。有一阵子我担心我不使用str_to_date 会无意中减慢自己的查询速度。【参考方案2】: 看起来sale_date 上的索引绝对是您应该在问题中添加几个查询使用sale_date 另一个建议是按照 MySQL 的 documentation 索引 GROUP BY 中使用的列

我不会采用一次性添加所有索引的方法,而是选择增量方法并在添加每个索引后测量性能。

【讨论】:

很遗憾,GROUP BY 中使用的索引列并没有带来任何性能提升。【参考方案3】:
INDEX(sale_date) -- very important for the first query

str_to_date('2017-06-06', '%Y-%m-%d') -- no better than '2017-06-06'

innodb_buffer_pool_size = 2G  -- too big for your tiny RAM; change to 1G (swapping kills perf)

GROUP  BY listing_id  -- meaningless, since `listing_id` is unique; hence count is always 1

Prefer using an explicit list instead of `SELECT *`

SELECT DISTINCT `seller_name` 
    FROM   `sales_meta`;       -- needs INDEX(seller_name)

but `seller_name` needs to be a VARCHAR, not TEXT

进一步证明str_to_date 无用:

mysql> SELECT STR_TO_DATE('2019-02-27', '%Y-%m-%d');
+---------------------------------------+
| STR_TO_DATE('2019-02-27', '%Y-%m-%d') |
+---------------------------------------+
| 2019-02-27                            |
+---------------------------------------+

【讨论】:

listing_id 不是唯一的 - 每天可能会有许多具有给定listing_id 的产品销售。查询的重点是显示在给定时间段内每种产品销售了多少次。不过我会考虑其他建议,谢谢!

以上是关于优化 MySQL InnoDB 查询最大值,计数的主要内容,如果未能解决你的问题,请参考以下文章

优化 Mysql 查询

mysql innodb select count 优化解决方案

...尝试优化 mysql innodb 表以实现快速计数仍然没有得到结果

MYSQL中的InnoDB存储引擎简介

MYSQL中的InnoDB存储引擎简介

mysql优化