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


70

如何在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


6
您建议看的链接的警告警告:这篇帖子写得如此激动和愤怒,以至于几乎幼稚。这位作家并没有反驳任何批评,尽管他仍然提到他支持他所说的话,但他的观点却在各方面都变得苗条。但是,如果您阅读这些评论,就不会浪费时间。
kommradHomer

Answers:


64

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

我要做的是这样的:

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

那应该可以给您提供2008-08-17发生的一切。


1
我曾经认为这种用法只是“ YYYY-MM-DD 00:00:00”的
快捷方式

3
我知道这是一个古老的答案,但是我不得不指出,由于MySQL使用字符串比较作为DATETIME; 您的查询返回正确的结果,并且不包含带有的行TranDateTime=2008-08-18 00:00:00
Arth 2014年

1
Arth,您是否有资料表明MySQL使用字符串比较?在较旧的版本中是这种情况吗?对于MySQL 5.7绝对不是这样。试试:创建表foobar(mytime timestamp); 插入foobar(mytime)值('2008-08-18 00:00:00'); 从foobar中选择* mytime在'2008-08-17 00:00:00'和'2008-08-18 23:59:59'之间;
安德烈亚斯(Andreas)

BETWEEN不是使用速度更快where TranDateTime >= '2008-08-17' and TranDateTime < '2008-08-18'
Chloe

这不是正确的答案,问题在于索引,而不是选择。而是使用生成的列查看答案。
Δ○ 'deltazero'

13

另一个选项(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;

1
为什么存储而不是虚拟的?

1
如果您想索引,则必须将其存储。.没有索引,它可以是虚拟的
Liran Brimer

1
thx,我想像,我对此文章感到困惑percona.com/blog/2016/03/04/…–

这应该是正确的答案,我发现即使使用BTREE,日期索引也优于日期时间索引
Δ○ 'deltazero'

顺便说一下,如今,InnoDB也支持VIRTUAL列上的索引。
Δ○ 'deltazero'

12

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


是的-并添加一个仅包含时间部分的列,并完全消除DATETIME。
JBB

我当前的解决方案是添加另一个字段调用“日期”,当我更新TranDateTime时,日期也会更新。我现在在'date'上有一个索引,并且我的表的大小增加了+ -5%,查询速度要快得多
Charles Faiga

9

您不能仅在日期部分上创建索引。您有理由吗?

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

我想你会发现

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

是高效的,可以做您想要的。


4

我不了解mySql的细节,但是仅对整个日期字段编制索引有什么害处?

然后只需搜索:

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

如果索引是b树或其他合理的索引,则应该很快找到它们。


您可以使用>= '2008-08-16' and ... < '2008-08-18'。假定时间为00:00:00
Chloe

您的意思是:> ='2008-08-17'和... <'2008-08-18'。时间假定为00:00:00
AK

2

Valeriy Kravchuk在有关使用此方法的MySQL网站上针对此问题的功能请求中。

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

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


2

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


1

我不了解mySQL的详细信息,但是仅对整个日期字段编制索引有什么害处?

如果将功能魔术用于*树,则散列...将消失,因为必须获取函数才能调用值。但是,由于您不知道前面的结果,因此必须对表进行完整扫描。

没有要添加的内容。

也许您的意思是类似计算(计算的)索引……但是到目前为止,我仅在IntersystemsCaché中看到了这一点。我认为关系数据库(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我认为都没有区别(我通常以这种格式使用它)。


1

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

使用此命令: WHERE datetime_field> = curdate();
这将捕获索引,
并覆盖今天:00:00:00到今天:23:59:59
完成。


0

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

如果由于date()函数未使用索引,则范围查询应运行得很快:

SELECT * FROM transactionlist,其中TranDateTime> ='2008-08-17'和TranDateTime <'2008-08-18'


1
如果使用date(),则不会命中索引。Mysql不能在这样的函数调用中使用索引。
JBB

0

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

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

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


0

如果修改表是一种选择,或者您要编写一个新表,请考虑将日期和时间存储在具有相应类型的单独列中。通过减小键空间并减少存储量(与从datetime导出的仅日期的列相比),可以提高性能。这也使得甚至在其他列之前也可以在复合键中使用。

在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       |                |
+-------------------+------------------+------+-----+---------+----------------+

-1

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


为什么不只是使用date(date_field)
Chloe
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.