【发布时间】: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 条记录后,它停止了,这意味着 5 的 stock id 没有返回任何数据,尽管它确实存在。可以想象,当没有数据返回时,这会破坏应用程序中的内容。
第 3 部分:尝试解决
最明显的答案似乎是添加
GROUP BY stock_id作为一种方式,要求我获得与预期每只股票相同数量的结果。不幸的是,这使我回到了第 1 部分,其中该查询在运行时需要 1-2 秒,因为它最终必须遍历相同的 50k+ 行,就像之前没有限制一样。这让我好不了多少。下一个想法是任意使
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