74

如何在 DATETIME 字段的日期部分创建索引?

mysql> SHOW COLUMNS FROM transactionlist;
+-------------------+------------------+------+-----+---------+----------------+
| Field             | Type             | Null | Key | Default | Extra          |
+-------------------+------------------+------+-----+---------+----------------+
| TransactionNumber | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| WagerId           | int(11)          | YES  | MUL | 0       |                |
| TranNum           | int(11)          | YES  | MUL | 0       |                |
| TranDateTime      | datetime         | NO   |     | NULL    |                |
| Amount            | double           | YES  |     | 0       |                |
| Action            | smallint(6)      | YES  |     | 0       |                |
| Uid               | int(11)          | YES  |     | 1       |                |
| AuthId            | int(11)          | YES  |     | 1       |                |
+-------------------+------------------+------+-----+---------+----------------+
8 rows in set (0.00 sec)

TranDateTime 用于保存交易发生时的日期和时间

我的表中有超过 1,000,000 条记录和语句

SELECT * FROM transactionlist where date(TranDateTime) = '2008-08-17' 

花费很长时间。

编辑:

看看这篇关于“为什么 MySQL 的 DATETIME 可以而且应该避免”的博文

4

13 回答 13

68

如果我没记错的话,那将运行整个表扫描,因为您正在通过函数传递列。MySQL 会乖乖地为每一列运行函数,绕过索引,因为查询优化器无法真正知道函数的结果。

我会做的是这样的:

SELECT * FROM transactionlist 
WHERE TranDateTime BETWEEN '2008-08-17' AND '2008-08-17 23:59:59.999999';

这应该会给你 2008-08-17 发生的一切。

于 2008-09-18T18:21:33.693 回答
16

我并不是说听起来很可爱,但一种简单的方法是添加一个仅包含日期部分和索引的新列。

于 2008-09-18T18:22:34.720 回答
15

另一个选项(与5.7.3及更高版本相关)是基于 datetime 列创建生成/虚拟列,然后对其进行索引。

CREATE TABLE `table` (
`my_datetime` datetime NOT NULL,
`my_date` varchar(12) GENERATED ALWAYS AS (DATE(`my_datetime`)) STORED,
KEY `my_idx` (`my_date`)
) ENGINE=InnoDB;
于 2017-06-14T15:13:59.460 回答
9

您不能仅在日期部分创建索引。你有理由这样做吗?

即使您可以仅在日期部分创建索引,优化器也可能仍然不会将它用于上述查询。

我想你会发现

SELECT * FROM transactionlist WHERE TranDateTime BETWEEN '2008-08-17' AND '2008-08-18'

高效,做你想做的事。

于 2008-09-18T19:03:18.353 回答
4

我不知道 mySql 的细节,但是仅仅索引整个日期字段有什么害处?

然后只需搜索:

 select * from translist 
     where TranDateTime > '2008-08-16 23:59:59'
        and TranDateTime < '2008-08-18 00:00:00'

如果索引是 b-trees 或其他合理的东西,这些应该很快就能找到。

于 2008-09-18T18:21:23.023 回答
2

Valeriy Kravchuk 在 MySQL 网站上对这个问题的功能请求说使用这种方法。

“与此同时,您可以使用字符列将 DATETIME 值存储为字符串,只索引前 N 个字符。通过在 MySQL 5 中仔细使用触发器,您可以基于这个想法创建一个相当健壮的解决方案。”

您可以编写一个很容易添加此列的例程,然后使用触发器使此列保持同步。这个字符串列的索引应该很快。

于 2008-09-18T18:25:19.677 回答
2

一个很好的解决方案是使用时间戳作为时间,而不是日期时间。它被存储为 INT 并且被索引得足够好。我个人在事务表上遇到过这样的问题,它有大约百万条记录并且速度很慢,最后我指出这是由错误的索引字段(日期时间)引起的。现在它运行得非常快。

于 2012-02-27T16:16:01.677 回答
1

我不知道 mySQL 的细节,但是仅仅索引整个日期字段有什么害处?

如果你对*树使用函数魔法,散列,......就消失了,因为要获取值,你必须调用函数。但是,因为您不知道前面的结果,所以您必须对表进行全面扫描。

没有什么可补充的。

也许你的意思是计算(计算?)索引之类的东西......但到目前为止,我只在 Intersystems Caché 中看到过这个。我认为关系数据库(AFAIK)中没有案例。

在我看来,一个好的解决方案如下(更新的 clintp 示例):

SELECT * FROM translist 
WHERE TranDateTime >= '2008-08-17 00:00:00.0000'
  AND TranDateTime < '2008-08-18 00:00:00.0000'

无论您使用00:00:00.0000还是00:00我认为都没有区别(我通常以这种格式使用它)。

于 2010-03-11T11:38:55.657 回答
1

datetime LIKE something% 也不会捕获索引。

使用这个: WHERE datetime_field >= curdate();
这将捕获索引,
并涵盖today:00:00:00 到 today:23:59:59
Done。

于 2014-10-17T04:19:25.943 回答
0

“解释”说什么?(运行 EXPLAIN SELECT * FROM transactionlist where date(TranDateTime) = '2008-08-17')

如果由于 date() 函数而没有使用您的索引,则范围查询应该运行得很快:

SELECT * FROM transactionlist where TranDateTime >= '2008-08-17' AND TranDateTime < '2008-08-18'

于 2008-09-18T18:20:52.697 回答
0

而不是基于函数创建索引(如果这在 mysql 中甚至可能的话)让你的 where 子句进行范围比较。就像是:

其中 TranDateTime > '2008-08-17 00:00:00' 和 TranDateTime < '2008-08-17 11:59:59')

这使数据库可以使用 TranDateTime 上的索引(有一个,对吗?)进行选择。

于 2008-09-18T18:24:27.917 回答
0

如果可以选择修改表,或者您正在编写新表,请考虑将日期和时间存储在具有相应类型的单独列中。您可以通过更小的键空间和更少的存储来获得性能(与从日期时间派生的仅日期列相比)。这也使得在复合键中使用变得可行,甚至在其他列之前使用。

在 OP 的情况下:

+-------------------+------------------+------+-----+---------+----------------+
| Field             | Type             | Null | Key | Default | Extra          |
+-------------------+------------------+------+-----+---------+----------------+
| TransactionNumber | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| WagerId           | int(11)          | YES  | MUL | 0       |                |
| TranNum           | int(11)          | YES  | MUL | 0       |                |
| TranDate          | date             | NO   |     | NULL    |                |
| TranTime          | time             | NO   |     | NULL    |                |
| Amount            | double           | YES  |     | 0       |                |
| Action            | smallint(6)      | YES  |     | 0       |                |
| Uid               | int(11)          | YES  |     | 1       |                |
| AuthId            | int(11)          | YES  |     | 1       |                |
+-------------------+------------------+------+-----+---------+----------------+
于 2018-12-14T05:30:05.710 回答
-1

创建一个仅包含日期的新字段,convert(datetime, left(date_field,10))然后对其进行索引。

于 2011-08-08T09:11:54.080 回答