【问题标题】:How does one create an index on the date part of DATETIME field in MySql如何在 MySql 中 DATETIME 字段的日期部分创建索引
【发布时间】:2010-09-10 20:17:21
【问题描述】:

如何在 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 用于保存交易发生时的日期和时间

My Table 中有超过 1,000,000 条记录和语句

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

需要很长时间。

编辑:

看看这篇关于“Why MySQL’s DATETIME can and should be avoided”的博文

【问题讨论】:

  • 对您建议查看的链接的警告评论:这篇文章写得如此激动和愤怒,几乎到了幼稚的地步。并且作者没有回击任何批评,同时仍然提到他支持他所说的,但他的观点却随着每一个而变得微不足道。但是,如果您阅读 cmets,也不会浪费时间。

标签: mysql


【解决方案1】:

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

我会做的事情是这样的:

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

这应该告诉你 2008-08-17 发生的一切。

【讨论】:

  • 我曾经认为这种用法只是'YYYY-MM-DD 00:00:00'的捷径
  • 我知道这是一个旧答案,但我不得不指出,因为 MySQL 使用字符串比较 DATETIME;您的查询返回正确的结果并且不包括带有TranDateTime=2008-08-18 00:00:00 的行。
  • Arth,你有 MySQL 使用字符串比较的来源吗?在旧版本中是这种情况吗?对于 MySQL 5.7,这绝对不是真的。尝试:创建表 foobar(mytime 时间戳);插入 foobar (mytime) 值 ('2008-08-18 00:00:00'); select * from foobar where mytime between '2008-08-17 00:00:00' and '2008-08-18 23:59:59';
  • BETWEEN 比使用 where TranDateTime >= '2008-08-17' and TranDateTime < '2008-08-18' 快吗?
  • 这不是正确的答案,问题是关于索引,而不是选择。请改用generated column 查看答案。
【解决方案2】:

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

【讨论】:

  • 是的 - 添加一个只有时间部分的列,并完全消除 DATETIME。
  • 我当前的解决方案是添加另一个字段调用“日期”,当我更新 TranDateTime 时,日期也会更新。我现在有一个关于“日期”的索引,并且查询速度更快,因为我的表的大小增加了 +-5%
【解决方案3】:

另一种选择 (relevant for version 5.7.3 and above) 是基于 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;

【讨论】:

  • 为什么是存储而不是虚拟的?
  • 如果你想索引它必须存储..没有索引它可以是虚拟的
  • 谢谢,我想,虽然我对这篇文章感到困惑percona.com/blog/2016/03/04/…
  • 这应该是正确的答案,我找到了date index outperforms a datetime index,即使是BTREE。
  • 顺便说一句,现在 InnoDB 也支持 VIRTUAL 列上的索引。
【解决方案4】:

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

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

我想你会发现的

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

高效,做你想做的事。

【讨论】:

    【解决方案5】:

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

    然后只需搜索:

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

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

    【讨论】:

    • 您可以使用&gt;= '2008-08-16' and ... &lt; '2008-08-18'。时间假定为00:00:00
    • 你的意思是:>= '2008-08-17' 和 ...
    【解决方案6】:

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

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

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

        我不知道 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 没有区别(我通常以这种格式使用它)。

        【讨论】:

          【解决方案9】:

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

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

          【讨论】:

            【解决方案10】:

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

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

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

            【讨论】:

            • 如果你使用 date() 你不会命中索引。 Mysql 不能在这样的函数调用中使用索引。
            【解决方案11】:

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

            哪里 TranDateTime > '2008-08-17 00:00:00' 和 TranDateTime

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

            【讨论】:

              【解决方案12】:

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

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

              【讨论】:

                【解决方案13】:

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

                【讨论】:

                • 为什么不直接使用date(date_field)
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2017-06-14
                • 1970-01-01
                • 2012-02-20
                • 2011-04-19
                • 1970-01-01
                • 1970-01-01
                • 2016-02-23
                相关资源
                最近更新 更多