【问题标题】:What would cause performance issues in query A but not query B?什么会导致查询 A 而不是查询 B 中的性能问题?
【发布时间】:2020-12-02 02:58:29
【问题描述】:

我有一个带有 apx 的表 (MYSQL 8)。 100M 条记录存储各种位的股票数据(价格、日期等),下面的查询 A 运行时间 date 上有一个索引,表的主键是(symboldate)。什么会导致这两个查询之间出现如此显着的差异,以及什么可能会加快表现不佳的速度?

查询 A:

SELECT symbol, MIN(date)
FROM Stocks
WHERE date BETWEEN '2015-01-01' AND '2020-01-01'
GROUP BY symbol

查询 B

SELECT symbol, MIN(date)
FROM Stocks
WHERE date BETWEEN '2015-01-01' AND '2020-01-01' AND market_cap > 20
GROUP BY symbol

我面临的另一个挑战是,有时我想按market_cap 进行过滤,但有时我想按其他数字字段(gross_profittotal_assets 等)进行过滤。查询是由一个表单生成的,该表单具有许多与参数相关的可选输入。

表架构

CREATE TABLE IF NOT EXISTS cri_v0_995 (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  company_id MEDIUMINT UNSIGNED NOT NULL, 
  dt DATE NOT NULL,
  price DECIMAL(18, 2),
  market_cap DECIMAL(12, 4),
  div_yield DECIMAL(4,2), -- this is always 0
  price_to_nte DOUBLE,
  price_to_mte DOUBLE,
  nte_to_price DECIMAL(16, 10),
  ante_to_price DECIMAL(16, 10),
  ate_to_price DECIMAL(18, 10),
  price_to_sales DOUBLE,
  price_to_earnings DOUBLE,
  cur_liq_to_mcap DECIMAL(4, 2), -- this is always 0
  net_liq_to_mcap DOUBLE,
  g3_rev_growth_and_trend DECIMAL(14, 10),
  p_cri_score DECIMAL(14, 10),
  f_cri_score DECIMAL(10, 7),
  cri_score DECIMAL(14, 10),
  PRIMARY KEY (id),
  FOREIGN KEY (company_id) REFERENCES companies (id),
  UNIQUE KEY (company_id, dt)
);

请注意,有几个列我不确定。它们一直是零,但我不知道它们背后的意图是什么。

(编辑 1 以解决缺少的GROUP BYs) (编辑 2 添加表架构)

【问题讨论】:

  • 您的查询格式错误。它们是没有group by 的聚合查询,但select 中有未聚合的列。
  • 是的,这两个查询都是格式错误的。这意味着他们正在返回随机结果,而您可能没有意识到这一点。 不要在生产环境中使用这些查询
  • 射击,感谢戈登和穿刺者。 GROUP BY 是我正在测试的查询的一部分,我只是将它们复制到这里很糟糕。
  • 显然你想索引market_cap
  • 您的日期范围是 5 年加 1 天。当心BETWEEN

标签: mysql sql


【解决方案1】:

第一个查询可以简单地遍历表:

  • 对于每个符号(方便地是 PK 的开始)
  • 找到第一行(“第一”,因为索引的第二部分是date)也是>=开始日期
  • 如果该日期不是

第二个查询需要查看每一行来检查market_cap;它不能跳过桌子。

如果您在Symbols 表中有current_market_cap,则可以在market_cap 之前 JOINing 过滤到此表。

WHERE 子句中的两个范围使得优化变得非常困难。 INDEX 是一维的。

使用PARTITION BY TODAYS(date) 需要对表进行重大的结构更改。它可能(也可能不会!)帮助您的查询运行得更快——通过使用“分区修剪”来限制需要检查的行数。 (我说“可能不是”,因为查询正在查看 5 年的范围,这可能是整个数据的很大一部分。)

更多关于分区的讨论:http://mysql.rjweb.org/doc.php/partitionmainthttp://mysql.rjweb.org/doc.php/find_nearest_in_mysql -- 后一个链接讨论了一个不同的 2D 问题(地理“查找最近”);将其应用于您的查询有点牵强。

由于您有很多最终用户可能会过滤的列和 1 亿行,让我们从另一个方向着手:最小化表大小。如果表不能完全缓存在 buffer_pool 中,这一点尤其重要——导致 I/O 绑定。告诉我们SHOW CREATE TABLE;让我们讨论一下每一列,以及它是否可以缩小。

更多

  • symbol VARCHAR... 更改为 company_id MEDIUMINT UNSIGNED 可能在数据和索引之间节省了 1GB。
  • 摆脱id,提升UNIQUE(company_id, dt)为PK。通过消除唯一的二级索引,这将节省几 GB。 (您的更改可能是有益的。
  • 大部分DOUBLEs 都是矫枉过正? FLOAT 每个会节省 4 个字节,但仍会为您提供 6-7 个有效数字。
  • 可能需要INDEX(dt) 进行其他查询。
  • market_cap 上的过滤器可能会妨碍分组最大优化。
  • 根据磁盘空间和其他查询,可能PARTITION BY RANGE(TO_DAYS(dt)) 有益,但按年分组。 (5 年 + 1 天)跨度将达到 6 个分区。 (参见“分区修剪”)这实际上不会对性能产生太大影响。
  • (大约 18 年前,我使用过这样的数据集。)
  • price DECIMAL(18, 2) 占用 9 个字节。它允许数十亿美元,尚未达到。它只有 2 位小数,因此在转换为小数之前(从 /2、/4、/8、/16 等),它不会精确保存金额。
  • market_cap DECIMAL(12, 4)(6 字节)对于某些公司来说可能不够大,对于索引当然也不够。小数点后 4 位可能是浪费。
  • 建议运行SELECT MAX(market_cap), MAX(price), ... 看看现在的数字有多大。

【讨论】:

  • 感谢您的想法。实际上今天下午发生在第二个链接上,正在寻找答案,但无法直接应用它。添加了模式定义,进行了一些更改以使用单独的公司表和 FK,而不是让符号成为关键 - 不确定它是否朝着正确的方向移动。无论如何,希望你有一些见解!
  • @0x11 - 我又加了一堆。
  • 关于双打是多余的,一些值有 10 位有效数字。它们适合漂浮吗?虽然现在我也想知道最后几个地方可能首先有多么重要。想我和你在一起。
  • 您能否详细说明市值阻碍了分组优化?
  • 我想测试分区。您是说分区需要 GROUP BY 吗?我仍在努力解决分区问题。
【解决方案2】:

对于这两个查询,您可能应该按符号进行聚合。因此,第二个当前非性能查询应该是:

SELECT symbol, MIN(date)
FROM Stocks
WHERE date BETWEEN '2015-01-01' AND '2020-01-01' AND market_cap > 20
GROUP BY symbol;

你想要的索引至少应该覆盖整个WHERE子句:

CREATE INDEX ON Stocks (date, market_cap);

如果您对两个查询都运行EXPLAIN,在添加GROUP BY 之后,您可能会发现您当前的日期单列索引甚至没有在第二个查询中使用。

【讨论】:

  • 很抱歉。有问题的查询确实有正确的GROUP BY symbol 子句,只是在复制时错过了它。
  • 也就是说,部分复杂性在于我有时使用market_cap,有时使用任意数量的其他参数(pricegross_profit 等),这使得添加适当的索引具有挑战性。
  • 当然。但这是另一个问题,而不是你实际问的问题。您的上述问题已得到解答。
  • 有什么建议吗?
  • @0x11 通常,在选择多列索引时,我们会将最严格的列放在首位(即最高基数)。
【解决方案3】:

也许您需要在 sql 查询 B 中强制使用索引。

SELECT symbol, MIN(date)
FROM Stocks use index (`indexNameOfDate`)
WHERE date BETWEEN '2015-01-01' AND '2020-01-01' AND market_cap > 20
GROUP BY symbol

或者你可以强制使用primaryKey index

这样做可以节省 sql 引擎选择索引本身的时间。 你也可以找到哪个更快。

另外,如果你通常使用datemarket_cap 来过滤数据,也许你需要创建一个索引来覆盖它们。

就像@Tim Biegeleisen 所说的那样。

创建股票指数(日期、市值);

【讨论】:

  • 我试过FORCE INDEX (PRIMARY),因为pk是(symbol, date),但这似乎没有效果。在回复@Tim 时提到我不确定创建索引是否对我来说是正确的答案,因为我有许多不同的参数用于过滤,并且理论上需要为每个参数创建一个索引来解决问题。关于添加这么多索引是否合理的任何想法?
  • 你试过只强制索引(date_index),而不是primay吗?如果您有很多字段要过滤,那么单独为每个字段创建索引是没有意义的,而这些字段不经常使用
  • 不,没有具体的日期,因为查询 A 仅使用主节点运行得如此之快。您认为添加特定日期的索引可能会有所帮助吗?
  • 强制 MySQL 使用索引通常不是一个好主意。
猜你喜欢
  • 2022-11-18
  • 2014-12-04
  • 1970-01-01
  • 2016-09-27
  • 1970-01-01
  • 2013-11-12
  • 1970-01-01
  • 2013-04-01
  • 1970-01-01
相关资源
最近更新 更多