mysql - SQL - 从大型数据集中返回多条记录中的最新记录
问题描述
背景
我有一张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 |
在这种情况下,您可以看到stock_id
的1
具有更频繁的数据间隔,因此在运行查询时,它返回了该 ID 的两条记录,然后沿着列表继续向下。stock id
在它达到 5 条记录后,它就停止了,这意味着它5
没有返回任何数据,尽管它确实存在。可以想象,当没有数据返回时,这会破坏应用程序中的内容。
第 3 部分:尝试解决
最明显的答案似乎是添加 a
GROUP BY stock_id
作为一种方式,要求我获得与每只股票预期数量相同的结果。不幸的是,这使我回到了第 1 部分,其中该查询在运行时需要 1-2 秒,因为它最终必须遍历相同的 50k+ 行,就像之前没有限制一样。这让我好不了多少。下一个想法是任意制作
LIMIT
比它需要的更大的值,以便它可以捕获所有行。这不是一个可预测的解决方案,因为查询可以是数千只股票的任意组合,每只股票都有不同的可用数据间隔。最极端的例子是每天与每分钟拉动的股票,这意味着在第二只股票出现之前可能有接近 350 多行的股票。将其乘以一个查询中的股票数量——比如 50,这仍然需要查询 15k+ 行。可行,但不理想,并且可能不可扩展。
第 4 部分:建议?
让一个 API 调用启动可能 50 多个数据库查询只是为了获取股票价格数据是一种糟糕的做法吗?是否有一些LIMIT
我应该使用的阈值来最大程度地减少失败的机会以使自己感到舒适?是否有其他 SQL 方法可以让我返回所需的行而无需查询大量表?
任何帮助表示赞赏。
解决方案
最快的方法是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
,无法有效地使用索引。
推荐阅读
- python - Python 如何检查缩进错误?
- audiokit - AudioKit 节拍器与时间音调的音频循环同步
- java - 如何阻止 Swing 打开新框架?
- html - HTML/CSS 中我的两个按钮之间有一条小蓝线
- android - 从 Fragment 中的 Content Uri 获取路径
- javascript - 将 Socket.io 与 Express.JS 生成项目一起使用的最佳方法是什么
- php - 使用 simplexml_load_string 在 php 中通过 URL (SRU) 加载搜索和检索返回一个空对象
- javascript - 用JS改变点击框的颜色
- powershell - Powershell - 如何在导入 csv 时忽略双行
- python - 通过字典中的系列映射创建新的二进制列