为什么不使用索引(在主键列上)?

Why Index (on primary key column) is not used?

我有一个日期 table,其中有一列 date (PK)。 CREATE 脚本在这里:

CREATE TABLE date_table (
  date                DATE
 ,year                INT(4)
 ,month               INT(2)
 ,day                 INT(2)
 ,month_pad           VARCHAR(2)
 ,day_pad             VARCHAR(2)
 ,month_name          VARCHAR(10)
 ,year_month_index    INT(6)
 ,year_month_hypname  VARCHAR(7)
 ,year_month_name     VARCHAR(15)
 ,week_day_index      INT(1)
 ,day_name            VARCHAR(9)
 ,week                INT(2)
 ,week_interval       VARCHAR(13)
 ,weekend_fl          INT(1)
 ,quarter_num         INT(1)
 ,quarter_num_pad     VARCHAR(2)
 ,quarter_name        VARCHAR(2)
 ,year_quarter_index  INT(6)
 ,year_quarter_name   VARCHAR(7)
 ,PRIMARY KEY (date)
);

现在我想要来自此 table 的 select 行具有动态值,使用 LAST_DAY()DATE_SUB(DATE_FORMAT(SYSDATE(),'%Y-01-01'), INTERVAL X YEAR)

当我的一个查询失败并且没有在 30 秒内执行时,我知道有问题,看起来原因是主键列上的索引没有被使用。这是我的结果(很抱歉使用图像而不是复制查询,但我认为它足够简洁用于此目的,并且查询足够short/simple):

首先,BETWEEN>=<= 的工作方式不同,这很奇怪。其次,索引看起来只用于常量值。如果仔细观察,您会发现在右侧(使用 >=<= 的地方)显示了 ~9K 行,这是 table 中行数的一半( table 有大约 18k 行,日期从 2000-01-01 到 `2050-12-31).

看起来如果我使用 CURRENT_DATE()(或 NOW())而不是 SYSDATE(),它就可以工作。这两个查询:

SELECT *
  FROM date_table t
 WHERE 1 = 1
   AND t.ddate >= LAST_DAY(CURRENT_DATE()) AND t.ddate <= LAST_DAY(CURRENT_DATE());

SELECT *
  FROM date_table t
 WHERE 1 = 1
   AND t.ddate >= LAST_DAY(NOW()) AND t.ddate <= LAST_DAY(NOW());

给出同样的结果,就是这样:

我会接受我的回答作为解决方案,但我仍在寻找解释。我认为这可能与 SYSDATE() 不是 DATE 相关,但 NOW() 也不是 DATE...

编辑: 忘记补充了,BETWEEN 也如我所见。

SYSDATE() returns the time at which it executes. This differs from the behavior for NOW(), which returns a constant time that indicates the time at which the statement began to execute. (Within a stored function or trigger, NOW() returns the time at which the function or triggering statement began to execute.)

-- https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_sysdate

也就是说,优化器不会将此视为 "constant"。否则,优化器急切地评估任何 "constant expressions",然后尝试利用已知的价值。

另请参阅 sysdate_is_now 选项。

底线:不要将 SYSDATE() 用于正常的日期时间用法;使用 NOW()CURDATE().