请帮助我优化一个单一的查询开始。故事是这样的:我有一个表称为“临时”,其中统计数据行插入非常频繁的一天(大约30 - 50万行插入每天在不规则的时间模式)。每天一次(午夜后),我的工作是选择30-50万行,分组和计算一些数据,并插入到“计算”表。
由于有这么多行,我决定最好运行一个查询来选择每小时的数据,所以我基本上运行了24个选择查询。问题是,选择每小时数据的查询非常慢。大约90秒左右。
首先,一些基本数据。我使用MariaDB,引擎是InnoDB。“temp”表的结构是这样的:
CREATE TABLE temp (
id char(36) NOT NULL default uuid() PRIMARY KEY,
device tinyint unsigned NOT NULL,
country_code varchar(2) NOT NULL,
canvas_id bigint unsigned NOT NULL,
paid_configured int unsigned NOT NULL,
paid_count int unsigned NOT NULL,
non_paid_configured int unsigned NOT NULL,
non_paid_count int unsigned NOT NULL,
timestamp timestamp NOT NULL default current_timestamp()
) engine = InnoDB;
字符串
我有一个索引:
create index temp_composite_index on temp (
timestamp,
canvas_id,
device,
country_code
)
型
我试图优化的查询是:
SELECT canvas_id AS canvas_id,
device AS device,
country_code AS country_code,
SUM(paid_configured) AS paid_configured_sum,
SUM(paid_count) AS paid_count_sum,
SUM(non_paid_configured) AS non_paid_configured_sum,
SUM(non_paid_count) AS non_paid_count_sum
FROM temp
WHERE timestamp BETWEEN '2023-12-02 12:00:00' AND '2023-12-02 12:59:59'
GROUP BY canvas_id, device, country_code;
型
解释结果为:
{
"query_block": {
"select_id": 1,
"filesort": {
"sort_key": "temp.canvas_id, temp.device, temp.country_code",
"temporary_table": {
"table": {
"table_name": "temp",
"access_type": "range",
"possible_keys": [
"temp_composite_index"
],
"key": "temp_composite_index",
"key_length": "4",
"used_key_parts": [
"timestamp"
],
"rows": 2609006,
"filtered": 100,
"index_condition": "temp.timestamp between '2023-12-10 12:00:00.000000' and '2023-12-10 12:59:59.000000'"
}
}
}
}
}
型
其他数据:
{
"rows_total": 30000000,
"rows_between_timestamps": 1249369,
"unique_combinations": {
"canvas_id": 20,
"device": 2,
"country_code": 4
}
}
型
我已经尝试了许多索引组合和顺序,也改变了在哪里和按列分组的顺序,但似乎没有工作.如果你需要任何其他信息随时要求.谢谢!
编辑:
1.我不知道为什么它决定使用UUID而不是BIGINT_INCREMENT。
1.是的,我总是在插入时使用当前时间戳。
- MariaDB版本10.6
1.它符合模式。
2条答案
按热度按时间rryofs0p1#
关于这一点的一些观察:
1.用90秒的时间从一个活动表中总结出几个兆行并不慢,也没有什么奇怪的SQL巫术能让它快得多。
1.您的结果集看起来相当小--每小时大约160行。
1.您正在对一个表执行历史报告,该表在最近一次只执行了INSERT操作。
1.如果可以更改,则
BIGINT AUTO_INCREMENT
主键将比DEFAULT UUID()
主键更有效。1.将
BETWEEN
用于日期范围筛选器是不明智的,因为范围的结尾是包含的。使用此方法时,请注意<
比较运算符。字符串
是我的建议。
1.在一个查询中完成一整天的报告,而不是为每小时运行一个单独的查询。
型
如果使用已有的索引,则所需的工作量与运行24个单独的有时间限制的查询大致相同。
1.因为您要对一个只包含INSERT的表进行历史报告,所以您可以将InnoDB的transaction isolation设置为一个允许级别,以减少INSERT操作和您的报告之间的争用。在您的报告查询之前给予此SQL命令。(不要在具有更复杂事务模式的数据库中执行此操作,除非您深入研究了它的功能,并使您的利益相关者相信它不会产生虚假的结果。)
型
1.您可能会考虑使用所谓的覆盖索引,以便通过顺序扫描索引来满足整个查询。这将为表增加SSD / HDD空间,但如果您迫切需要快速执行此报表查询,则这种折衷可能是值得的。
型
bq3bfh9z2#
在第一个索引列(
timestamp
)上有一个范围条件,这使得除了“覆盖”之外,不可能使用后续索引列。这意味着所有匹配范围条件(如1 M到3 M)的行必须在临时表中排序以执行GROUP BY操作。更好的索引应该支持时间戳的日期-小时部分的相等条件。您可以使用虚拟(生成)列创建这样的索引。示例:
字符串
现在可以将
WHERE
子句更改为型
而 filesort 应该在执行计划中消失。
更进一步的优化是在索引中包含所有其他列(使其覆盖),以便查询可以对该索引进行操作。
型
这将是新查询的完美索引。
不要忘记每个索引都会降低写操作的速度,索引越大(列越多),开销越大。
如果这在一个小时内运行良好,您也可以尝试每天查询:
型
WHERE子句:
型
我也想看看Partitioning。它可以改善这两个-读和写,如果你保持旧的数据,但几乎没有出来。