为什么这个 MySQL 查询需要这么长时间?

Chr*_*ris 6 mysql mysql-8.0

我希望从一个名为price 的表中做一个相对简单的过滤器。

价格是一个非常大的表(~2G 记录)。下面包含示例数据、查询和索引。我意识到被查询的表非常大,但对于如此简单的查询,我期望有更好的性能(目前大约 5 分钟并正在运行)。我确实注意到每个 MySQL Workbench 的 InnoDB 缓冲区使用率似乎为 100%,每秒读取约 6K InnoDB。有关为纠正此问题而进行的调整的指导。

价格

dataDate     ticker  optionSymbol    expDate     type    price   strike  last    bid     ask     volume  OI
2002-02-08   AAPL    AAQ020216C00005000 2002-02-16   call   24.03   5   0   18.8    19.1    0   0
2002-02-08   AAPL    AAQ020216P00005000 2002-02-16   put    24.03   5   0   0   0.05    0   0
2002-02-08   AAPL    AAQ020216C00007500 2002-02-16   call   24.03   7.5 0   16.3    16.6    0   0
2002-02-08   AAPL    AAQ020216P00007500 2002-02-16   put    24.03   7.5 0   0   0.05    0   0
2002-02-08   AAPL    AAQ020216C00010000 2002-02-16   call   24.03   10  12.2    13.9    14.2    0   1
2002-02-08   AAPL    AAQ020216P00010000 2002-02-16   put    24.03   10  0   0   0.05    0   0
2002-02-08   AAPL    AAQ020216C00012500 2002-02-16   call   24.03   12.5    13.5    11.4    11.7    0   8
2002-02-08   AAPL    AAQ020216P00012500 2002-02-16   put    24.03   12.5    0.05    0   0.05    0   50
2002-02-08   AAPL    AAQ020216C00015000 2002-02-16   call   24.03   15  7.1 8.9 9.1 0   10
2002-02-08   AAPL    AAQ020216P00015000 2002-02-16   put    24.03   15  0.1 0   0.05    0   30
2002-02-08   AAPL    AAQ020216C00017500 2002-02-16   call   24.03   17.5    5.5 6.4 6.7 0   371
2002-02-08   AAPL    AAQ020216P00017500 2002-02-16   put    24.03   17.5    0.05    0   0.05    0   147
2002-02-08   AAPL    AAQ020216C00020000 2002-02-16   call   24.03   20  3.9 3.9 4.1 7   1064
2002-02-08   AAPL    AAQ020216P00020000 2002-02-16   put    24.03   20  0.1 0   0.1 5   1448
2002-02-08   AAPL    AAQ020216C00022500 2002-02-16   call   24.03   22.5    1.7 1.7 1.75    1551    7069
2002-02-08   AAPL    AAQ020216P00022500 2002-02-16   put    24.03   22.5    0.2 0.15    0.25    136 3234
2002-02-08   AAPL    AAQ020216C00025000 2002-02-16   call   24.03   25  0.3 0.1 0.35    105 4237
2002-02-08   AAPL    AAQ020216P00025000 2002-02-16   put    24.03   25  1.25    1.2 1.35    629 589
2002-02-08   AAPL    AAQ020216C00027500 2002-02-16   call   24.03   27.5    0.05    0   0.1 0   1097
Run Code Online (Sandbox Code Playgroud)

询问

select *
from op.prices op
where ticker = 'AAPL'
and '2020-04-30' between date_add(expDate, INTERVAL 3 MONTH) and expDate
and '2020-04-30' = date_add(op.dataDate, INTERVAL 14 DAY);
Run Code Online (Sandbox Code Playgroud)

索引 在此处输入图片说明

bba*_*ird 33

代替

'2020-04-30' = date_add(op.dataDate, INTERVAL 14 DAY);
Run Code Online (Sandbox Code Playgroud)

op.dataDate = date_sub('2020-04-30', INTERVAL 14 DAY);
Run Code Online (Sandbox Code Playgroud)

您的第一条语句将被解释为“将所有天数添加 14 天dataDate并在 2020 年 4月 30 日返回。” 这将需要对表进行全面扫描。

第二个语句将评估为:“返回dataDate2020-04-16 的记录。” 这允许引擎对以 开头的索引执行搜索dataDate

做任何你想做的奇怪的事情,expDate因为这不会影响查询引擎将如何优化。

  • 在使用的索引上,`expDate` 落后于 `dataDate` 和 `Ticker`。通过修改,查询可以搜索 `dataDate` 和 `Ticker`,此时剩下的任何内容都无关紧要,因为您没有处理那么多行。 (6认同)
  • 聪明,事后看来完全正确。为什么“之间”子句没有受到类似影响的任何意义? (4认同)

Ric*_*mes 6

配方是主要问题。重写查询后,您拥有的索引可以用于整个WHERE子句。

SELECT  *
    FROM  op.prices
    WHERE  ticker = 'AAPL'
      AND expDate >= '2020-04-30'
      AND expDate  < '2020-04-30' + INTERVAL 3 MONTH
      AND dataDate = '2020-04-30' - INTERVAL 14 DAY
Run Code Online (Sandbox Code Playgroud)

请参阅维基百科中的 Sargeable。换种说法“不要在函数调用中隐藏列——它可能无法被索引使用。”

缩小文件大小会有所帮助:

  • 考虑将指标从 8 字节DOUBLE(大约 16 位有效数字)切换到 4 字节FLOAT(大约 7 位有效数字)。
  • 1 个字节ENUM('put','call')将每行节省 3 个字节。
  • 规范化选项(长字符串)。

可能还有更多提示。要了解缩小的重要性,请回答以下问题:您有多少 RAM?的价值是innodb_buffer_pool_size什么?表有多大(GB)?

你真的应该有一个PRIMARY KEY.