【问题标题】:SQL - return latest of multiple records from large data setSQL - 从大型数据集中返回多条记录中的最新记录
【发布时间】:2020-08-22 06:37:05
【问题描述】:

背景

我有一个 stock_price 表,其中存储了大约 1000 只股票的历史盘中股价。尽管定期清除旧数据,但该表定期有 5M+ 记录。结构松散:

| id     | stock_id | value | change |  created_at         |
|--------|----------|-------|--------|---------------------|
| 12345  | 1        | 50    | 2.12   | 2020-05-05 17:39:00 |
| 12346  | 2        | 25    | 1.23   | 2020-05-05 17:39:00 |

我经常需要为 API 端点一次获取大约 20 支股票的最新股价。最初的实现对每只股票执行一个查询:

select * from stock_prices where stock_id = 1 order by created_at desc limit 1

第 1 部分:低效查询

20 多个查询的效率有点低,但它确实有效。更新了代码 (Laravel 6) 以使用正确的关系 (stock hasMany stock_prices),进而生成如下查询:

select
  *
from
  `stock_prices`
where
  `stock_prices`.`stock_id` in (1, 2, 3, 4, 5)
order by
  `id` desc

虽然这样可以节省查询,但运行需要 1-2 秒。运行explain 表明它仍然必须在任何给定时间查询 50k+ 行,即使使用外键索引也是如此。我的下一个想法是我会在查询中添加一个limit,只返回等于我要求的股票数量的行数。现在查询:

select
  *
from
  `stock_prices`
where
  `stock_prices`.`stock_id` in (1, 2, 3, 4, 5)
order by
  `id` desc
limit
  5

第 2 部分:查询有时会丢失记录

性能是惊人的 - 毫秒级处理。 但是,它可能无法返回一个/多个股票的价格。由于添加了limit,如果任何股票在下一只股票之前有多个价格(行),它将“消耗”其中一个行数。

这是一个非常真实的场景,因为某些股票每分钟提取一次数据,其他股票每 15 分钟提取一次数据,等等。所以在某些情况下,由于limit 会为一只股票提取多行数据,随后不会返回其他人的数据:

| id   | stock_id | value | change | created_at     |
|------|----------|-------|--------|----------------|
| 5000 | 1        | 50    | 0.5    | 5/5/2020 17:00 |
| 5001 | 1        | 51    | 1      | 5/5/2020 17:01 |
| 6001 | 2        | 25    | 2.2    | 5/5/2020 17:00 |
| 6002 | 3        | 35    | 3.2    | 5/5/2020 17:00 |
| 6003 | 4        | 10    | 1.3    | 5/5/2020 17:00 |

在这种情况下,您可以看到1 中的stock_id 具有更频繁的数据间隔,因此在运行查询时,它返回了该ID 的两条记录,然后沿着列表继续向下。在达到 5 条记录后,它停止了,这意味着 5stock id 没有返回任何数据,尽管它确实存在。可以想象,当没有数据返回时,这会破坏应用程序中的内容。

第 3 部分:尝试解决

  1. 最明显的答案似乎是添加GROUP BY stock_id 作为一种方式,要求我获得与预期每只股票相同数量的结果。不幸的是,这使我回到了第 1 部分,其中该查询在运行时需要 1-2 秒,因为它最终必须遍历相同的 50k+ 行,就像之前没有限制一样。这让我好不了多少。

  2. 下一个想法是任意使LIMIT 大于所需的大小,以便捕获所有行。这不是一个可预测的解决方案,因为查询可以是数千只股票的任意组合,每只股票都有不同的可用数据间隔。最极端的例子是每天与每分钟拉动的股票,这意味着在第二只股票出现之前可能有接近 350 多行的股票。将其乘以一个查询中的股票数量——比如 50,这仍然需要查询 15k+ 行。可行,但不理想,并且可能不可扩展。

第 4 部分:建议?

让一个 API 调用启动可能 50 多个数据库查询只是为了获取股票价格数据是一种糟糕的做法吗?是否有一些LIMIT 的阈值我应该使用它来最大限度地减少失败的机会以使我感到舒适?是否有其他 SQL 方法可以让我返回所需的行而无需查询大量表?

任何帮助表示赞赏。

【问题讨论】:

  • 哪个mysql版本?
  • 我说错了 - 它实际上是 MariaDB,尽管我知道它们的操作方式相似。该版本被列为10.2.31
  • window functions。或者SELECT .. created > NOW() - INTERVAL 30 MINUTE 会覆盖所有记录,有多少不必要的数据?它如何索引SHOW CREATE TABLE {tablename}?另请查看“时间序列数据库”

标签: mysql database laravel mariadb groupwise-maximum


【解决方案1】:

最快的方法是union all:

(select * from stock_prices where stock_id = 1 order by created_at desc limit 1)
union all
(select * from stock_prices where stock_id = 2 order by created_at desc limit 1)
union all
(select * from stock_prices where stock_id = 3 order by created_at desc limit 1)
union all
(select * from stock_prices where stock_id = 4 order by created_at desc limit 1)
union all
(select * from stock_prices where stock_id = 5 order by created_at desc limit 1)

这可以使用stock_prices(stock_id, created_at [desc]) 上的索引。不幸的是,当您使用in 时,索引不能被有效地使用。

【讨论】:

  • 谢谢,戈登。这肯定很快。我想知道这在什么级别开始崩溃......我可以将 50 多个查询链接在一起并且仍然期望合理的性能吗?我也想知道在数据库级别这个查询是否会像 50 个单独的查询一样运行。
  • @itwasluck3 。 . .是的。每一个都是对索引的快速参考。不幸的是,如果没有union all,这不能(或者我不知道如何)制定,因为关于如何使用索引的规则。
【解决方案2】:

分组最大

SELECT b.*
    FROM ( SELECT stock_id, MAX(created_at) AS created_at
            FROM stock_proces
            GROUP BY stock_id
         ) AS a
    JOIN stock_prices AS b  USING(stock_id, created_at)

需要:

INDEX(stock_id, created_at)

如果您可以在同一秒内为同一股票设置两行,这将提供 2 行。有关替代方案,请参阅下面的链接。

如果该对是唯一的,则将其设为 PRIMARY KEY 并删除 id;这也有助于提高性能。

更多讨论:http://mysql.rjweb.org/doc.php/groupwise_max#using_an_uncorrelated_subquery

【讨论】:

    猜你喜欢
    • 2014-11-06
    • 1970-01-01
    • 2022-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-02
    • 1970-01-01
    • 2019-07-20
    相关资源
    最近更新 更多