mariadb 我在索引方面出了什么问题?还是别的什么问题?

vql8enpb  于 11个月前  发布在  其他
关注(0)|答案(2)|浏览(174)

请帮助我优化一个单一的查询开始。故事是这样的:我有一个表称为“临时”,其中统计数据行插入非常频繁的一天(大约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.是的,我总是在插入时使用当前时间戳。

  1. MariaDB版本10.6
    1.它符合模式。
rryofs0p

rryofs0p1#

关于这一点的一些观察:
1.用90秒的时间从一个活动表中总结出几个兆行并不慢,也没有什么奇怪的SQL巫术能让它快得多。
1.您的结果集看起来相当小--每小时大约160行。
1.您正在对一个表执行历史报告,该表在最近一次只执行了INSERT操作。
1.如果可以更改,则BIGINT AUTO_INCREMENT主键将比DEFAULT UUID()主键更有效。
1.将BETWEEN用于日期范围筛选器是不明智的,因为范围的结尾是包含的。使用此方法时,请注意<比较运算符。

WHERE timestamp >= '2023-12-02 10:00' AND timestamp < '2023-12-02 11:00'

字符串
是我的建议。
1.在一个查询中完成一整天的报告,而不是为每小时运行一个单独的查询。

SELECT  HOUR(timestamp) AS hour,
         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 >= '2023-12-02'
   AND timestamp < '2023-12-02' + INTERVAL 1 DAY 
 GROUP BY HOUR(timestamp), canvas_id, device, country_code;


如果使用已有的索引,则所需的工作量与运行24个单独的有时间限制的查询大致相同。
1.因为您要对一个只包含INSERT的表进行历史报告,所以您可以将InnoDB的transaction isolation设置为一个允许级别,以减少INSERT操作和您的报告之间的争用。在您的报告查询之前给予此SQL命令。(不要在具有更复杂事务模式的数据库中执行此操作,除非您深入研究了它的功能,并使您的利益相关者相信它不会产生虚假的结果。)

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;


1.您可能会考虑使用所谓的覆盖索引,以便通过顺序扫描索引来满足整个查询。这将为表增加SSD / HDD空间,但如果您迫切需要快速执行此报表查询,则这种折衷可能是值得的。

ALTER TABLE temp
DROP INDEX temp_composite_index,
ADD INDEX temp_composite_index 
    (timestamp, canvas_id, device, country_code,
     paid_configured, paid_count, non_paid_configured, non_paid_count);

bq3bfh9z

bq3bfh9z2#

在第一个索引列(timestamp)上有一个范围条件,这使得除了“覆盖”之外,不可能使用后续索引列。这意味着所有匹配范围条件(如1 M到3 M)的行必须在临时表中排序以执行GROUP BY操作。
更好的索引应该支持时间戳的日期-小时部分的相等条件。您可以使用虚拟(生成)列创建这样的索引。示例:

alter table temp
  add column virtual_date date as (date(timestamp)),
  add column virtual_hour tinyint unsigned as (hour(timestamp)),
  add index idx_virtual_date_hour_sum (
    virtual_date,
    virtual_hour, 
    canvas_id, 
    device, 
    country_code
  );

字符串
现在可以将WHERE子句更改为

WHERE virtual_date = '2023-12-02' AND virtual_hour = 12


filesort 应该在执行计划中消失。
更进一步的优化是在索引中包含所有其他列(使其覆盖),以便查询可以对该索引进行操作。

alter table temp
  add column virtual_date date as (date(timestamp)),
  add column virtual_hour tinyint unsigned as (hour(timestamp)),
  add index idx_virtual_date_hour_cov (
    virtual_date,
    virtual_hour, 
    canvas_id, 
    device,
    country_code,
    paid_configured,
    paid_count,
    non_paid_configured,
    non_paid_count
  );


这将是新查询的完美索引。
不要忘记每个索引都会降低写操作的速度,索引越大(列越多),开销越大。
如果这在一个小时内运行良好,您也可以尝试每天查询:

alter table temp
  add column virtual_date date as (date(timestamp)),
  add index idx_virtual_date_cov (
    virtual_date,
    canvas_id, 
    device,
    country_code,
    paid_configured,
    paid_count,
    non_paid_configured,
    non_paid_count
  );


WHERE子句:

WHERE virtual_date = '2023-12-02'


我也想看看Partitioning。它可以改善这两个-读和写,如果你保持旧的数据,但几乎没有出来。

相关问题