【问题标题】:Seek paginated query gets progressively slower on a big tableSeek 分页查询在大表上逐渐变慢
【发布时间】:2019-07-14 10:05:59
【问题描述】:

我有一个很大的事件表。 (目前为 530 万行)。我需要以线性方式从头到尾遍历这个表。大多数情况下没有随机搜索。目前的数据包括大约 5 天的这些事件。

由于表格的大小,我需要对结果进行分页,互联网告诉我“寻求分页”是最好的方法。

但是,这种方法在前 3 天运行时效果很好,速度很快,之后 mysql 真的开始变慢了。我发现它一定是 io-bound,因为随着减速开始,我的 cpu 使用率实际上会下降。

我相信这与我所做的 2 列排序以及文件排序的使用有关,也许 Mysql 需要读取所有行来对我的结果进行排序或其他什么。正确索引可能是一个正确的解决方法,但我还没有找到解决我的问题的索引。

这个数据库的复杂部分是 id 和时间戳的顺序并不完美。该软件要求数据按时间戳排序。但是,在向该数据库添加数据时,某些事件是在实际发生后 1 分钟添加的,因此自动递增的 id 不是按时间顺序排列的。

截至目前,减速非常严重,以至于我为期 5 天的遍历从未完成。它只是变得越来越慢......

我尝试过以多种方式为表建立索引,但 mysql 似乎不想使用这些索引,并且 EXPLAIN 一直显示“filesort”。不过,索引用于 where 语句。

我目前使用的解决方法是首先进行全表遍历并将所有行 ID 和时间戳加载到内存中。我在软件的 python 端对行进行排序,然后在遍历时从 mysql 以较小的块加载完整数据(仅通过 ids)。这可以正常工作,但由于相同数据总共需要遍历 2 次,因此效率很低。

表的架构:

CREATE TABLE `events` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `server` varchar(45) DEFAULT NULL,
  `software` varchar(45) DEFAULT NULL,
  `timestamp` bigint(20) DEFAULT NULL,
  `data` text,
  `event_type` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index3` (`timestamp`,`server`,`software`,`id`),
  KEY `index_ts` (`timestamp`)
) ENGINE=InnoDB AUTO_INCREMENT=7410472 DEFAULT CHARSET=latin1;

查询(一个可能的行):

SELECT software,
       server,
       timestamp,
       id,
       event_type,
       data
FROM   events
WHERE  ( server = 'a58b'
         AND ( software IS NULL
                OR software IN ( 'ASD', 'WASD' ) ) )
       AND ( timestamp, id ) > ( 100, 100 )
       AND timestamp <= 200
ORDER  BY timestamp ASC,
          id ASC
LIMIT  100; 

查询基于https://blog.jooq.org/2013/10/26/faster-sql-paging-with-jooq-using-the-seek-method/(以及其他一些具有相同想法的帖子)。我相信它被称为“用搜索谓词搜索分页”。基本要点是我有一个开始时间戳和结束时间戳,我需要使用我指定的服务器上的软件获取所有事件,或者只获取特定于服务器的事件(软件 = NULL)。奇怪的()-东西是由于python根据给定的参数构造查询。如果它们可能会产生一些影响,我会让它们可见。

除了在宇宙热寂之前完成的遍历。

【问题讨论】:

  • 我从未在 MySQL 中看到过这种结构:(timestamp,id) &gt; (100,100)。我测试了它,它做了一些事情,但我不知道是什么,也找不到任何文档。有谁能赐教吗?
  • @KIKOSoftware 称为Row Subqueries 或元组比较。检查thisthis
  • @MadhurBhaiya 谢谢,是的,似乎就是这样。在不知道它叫什么的情况下很难找到它。我发现(a, b) &gt; (x, y) 等价于(a &gt; x) OR ((a = x) AND (b &gt; y))。难怪我无法通过测试来弄清楚。
  • serversoftware 的基数是多少(运行:select count(distinct server), count(distinct software) from events
  • 请发布 A) SHOW INDEX FROM events 的 TEXT 结果;和 B) EXPLAIN SELECT SQL_NO_CACHE 软件、服务器、时间戳......我们将了解每个索引的基数,并了解 OPTIMIZER 可能对您的查询做什么。

标签: mysql innodb


【解决方案1】:

第一次改变

AND ( timestamp, id ) > ( 100, 100 )

AND (timestamp > 100 OR timestamp = 100 AND id > 100)

这个优化建议在官方文档中:Row Constructor Expression Optimization

现在引擎将能够使用(timestamp) 上的索引。根据serversoftware 列的基数,这可能已经足够快了。

(server, timestamp, id) 上的索引应该会进一步提高性能。

如果仍然不够快,我建议对

进行 UNION 优化
AND (software IS NULL OR software IN ('ASD', 'WASD'))

那就是:

(
    SELECT software, server, timestamp, id, event_type, data
    FROM events
    WHERE server = 'a58b'
      AND software IS NULL
      AND (timestamp > 100 OR timestamp = 100 AND id > 100)
      AND timestamp <= 200
    ORDER BY timestamp ASC, id ASC
    LIMIT 100
) UNION ALL (
    SELECT software, server, timestamp, id, event_type, data
    FROM events
    WHERE server = 'a58b'
      AND software = 'ASD'
      AND (timestamp > 100 OR timestamp = 100 AND id > 100)
      AND timestamp <= 200
    ORDER BY timestamp ASC, id ASC
    LIMIT 100
) UNION ALL (
    SELECT software, server, timestamp, id, event_type, data
    FROM events
    WHERE server = 'a58b'
      AND software = 'WASD'
      AND (timestamp > 100 OR timestamp = 100 AND id > 100)
      AND timestamp <= 200
    ORDER BY timestamp ASC, id ASC
    LIMIT 100
)
ORDER BY timestamp ASC, id ASC
LIMIT 100

您需要在(server, software, timestamp, id) 上为此查询创建一个索引。

【讨论】:

  • 据我所知,对于分页查询 LIMIT(offset, count),此查询将不起作用(提供所需的结果)。
  • @RajatGoel 此查询将返回与原始查询相同的结果,该查询也不使用偏移量。要点是不要使用偏移量,因为它对于高偏移量是低效的。
  • 如问题中所述,他正在使用分页查询。对于分页,需要偏移量。
  • @RajatGoel 有不同的方法来实现分页。他们中的许多人避免使用OFFSET(主要是为了提高性能——但不仅仅是)。这是good example。然而 - 这个讨论是没有意义的,因为我没有改变结果集。如果您认为原始查询不符合 OP 的要求,那么您可以对该问题写评论。
  • 您链接到的手册页说“使用等效的非构造函数表达式重写行构造函数表达式可能会导致更完整的索引使用”——这一直是正确的。我刚刚检查了 8.0.15,发现 (a,b) > (3,4) 的使用仍然没有得到很好的优化。确实,该构造有效。但它不如ANDOR 的等效组合那么快。
【解决方案2】:

有多种复杂情况正在发生。

快速解决方法是

INDEX(software, timestamp, id)   -- in this order

一起

    WHERE  server = 'a58b'
      AND  timestamp BETWEEN 100 AND 200
      AND ( software IS NULL
                OR software IN ( 'ASD', 'WASD' ) ) )
      AND ( timestamp, id ) > ( 100, 100 )
    ORDER  BY timestamp ASC,
              id ASC
    LIMIT  100; 

请注意,server 需要在索引中首先,而不是在您正在执行范围的事情之后 (timestamp)。另外,我打破了timestamp BETWEEN ...,让优化器清楚ORDER BY 的下一列可能会使用索引。

您说的是“分页”,所以我假设您也有一个 OFFSET?将其重新添加,以便我们讨论其含义。我的blog“记住你离开的地方”而不是使用OFFSET可能(或可能不)实用。

【讨论】:

  • 其实我以前读过你关于分页的博客。我的查询的分页部分是“AND (timestamp, id) > (100, 100)”。我只是根据最后一次查询的最后结果来增加时间戳和 ID。
  • 行构造函数的使用应该改为 AND-OR 等效项,直到(如果)它得到优化。
猜你喜欢
  • 2023-04-10
  • 1970-01-01
  • 2021-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-30
相关资源
最近更新 更多