一篇讲透DATE、DATETIME 和 TIMESTAMP

究其原因,有两个:

一是,傻傻的分不清 DATE、DATETIME 和 TIMESTAMP 三种数据类型,记不住它们的格式;

二是,它们或多或少和时区相关,为了存储时区无关数据,推荐使用 int(11) 存储时间戳格式

一、 DATE、DATETIME 和 TIMESTAMP 的格式

首先要说明的是,这三种数据类型是息息相关的,它们有很多相似之处,也有各自的特征,因为这些特征,才导致了彼此的不同。

其次MySQL 可以识别多种格式的 date、datetime 和 timestamp ,具体支持的格式有

对于 date 类型,支持 YYYY-MM-DD 或 YY-MM-DD 以及它们各自的变形

说到变形,就是指中划线 ( - ) 可以替换为任意字符,除了数字 0-9 ,比如

格式范例

YYYY-MM-DD2018-09-13

YY-MM-DD18-09-13

YYYY/MM/DD2018/09/13

YY/MM/DD18/09/13

YYYY^MM^DD2018^09^13

YY^MM^DD18^09^13

YYYY@MM@DD2018@09@13

YY@MM@DD18@09@13

同时还支持 YYYYMMDD 和 YYMMDD 的字符串形式,例如 '20180913' 和 '180913'。

还支持 YYYYMMMDD 和 YYMMDD 的数字格式,例如 20180913 和 180913。

2、对于 datetime 和 timestamp 类型,支持 YYYY-MM-DD HH:MM:SS 或 YY-MM-DD HH:MM:SS,一起它们的变形

这个变形其实和 DATE 一样,而且就是指中划线 ( - ) 可以替换为任意字符,除了数字 0-9 。

更进一步,这两个类型可以针对日期部分和时间部分使用不同的分隔符

格式范例

YYYY-MM-DD HH:MM:SS2018-09-13 21:15:10

YY-MM-DD HH:MM:SS18-09-13 21:15:10

YYYYY^MM^DD HH+MM+SS2018^09^13 21+15+10

YY^MM^DD HH+MM+SS18^09^13 21+15+10

YYYY^MM^DD2018^09^13

YY^MM^DD18^09^13

YYYY@MM@DD HH^MM^SS2018@09@13 21^15^10

YY@MM@DD HH^MM^SS18@09@13 21^15^10

这种格式下也有几个点要注意

1、日期和时间部分与小数秒部分之间的唯一可用的分隔符号是小数点 ( . ) 例如 2018-09-13 21:15:10.112、日期与时间部分的分隔符不一定就要使用空格 ( ' ' ),还可以使用字符 T ,例如 2018-09-13 21:15:10 和 2018-09-13T21:15:10 是等价的

同时还可以使用没有任何分隔符的形式,比如 YYYYMMDDHHMMSS 或 YYMMDDHHMMSS,例如 '20180913211510' 与 2018-09-13 21:15:10 是等价的

但 071122129015 则是非法的,并不是因为年份缺少了,而是因为分钟太大 ( 90 ),然后会被视为 0000-00-00 00:00:00

因为 MySQL 同时支持超大整数类型,所以,数字形式的 YYYYMMDDHHMMSS 或 YYMMDDHHMMSS 也是被支持的,例如 20180913211510 和 180913211510 被认为与 2018-09-13 21:15:10 是等价的

二、DATE、DATETIME 和 TIMESTAMP 的特征

从上面的格式中可以看出,date 类型就是只有 年月日 没有 时分秒,MySQL 以 YYYY-MM-DD 格式检索并显示 date 类型的值。支持的范围是 1000-01-01 到 9999-12-31

datetime 类型通常包含完整的日期时间,也就是能够完整的表达某一个时刻。MySQL 以 YYYY-MM-DD HH:MM:SS 格式检索并显示 datetime 类型的值,支持的范围是 1000-01-01 00:00:00 到 9999-12-31 23:59:59

和 datetime 类型一样,timestamp 类型通常也包含完整的日期时间,也能够完整的表达某一个时刻。MySQL 也以 YYYY-MM-DD HH:MM:SS 格式检索并显示 timestamp 类型的值,但是,timestamp 支持的范围是不同的,只能是1970-01-01 00:00:00 UTC 到 2038-12-31 23:59:59 UTC

也就是说,支持的格式和显示上,datetime 和 timestamp 并没有什么不同,但在支持的范围上是有不同的

三、DATETIME 和 TIMESTAMP 的小数部分

datetime 和 timestamp 两种时间类型还可以精确到 微秒 ,具体的形式就是支持在 后面使用 6 位精度的小数来支持,例如 2018-09-13 21:15:10.111111。

而且,插入 datetime 或 timestamp 列的值中的任何小数部分都将被存储而不是被丢弃。

当包含小数部分时,datetime 和 timstamp 两个日期时间类型的格式为 YYYY-MM-DD HH:MM:SS [.fraction],datetime 值的范围为 1000-01-01 00:00:00.000000 至 9999 -12-31 23:59:59.999999 而 timestamp 类型的范围则为 1970-01-01 00:00:01.000000 到 2038-01-19 03:14:07.999999

注意: 小数部分与其它部分的唯一的分隔符只能是小数点 ( . ),且没有任何其它可替代的字符。

四、DATETIME 和 TIMESTAMP 支持自动赋值

MySQL 还为 datetime 和 timestamp 两种日期时间类型提供了自动赋值功能。也就是在 MySQL INSERT 时可以自动初始化为当前日期时间,在 MySQL Update 时自动更新为当前时间。

而具体的做法,就是创建表结构时为这两种日期时间类型添加以下约束:

CREATE TABLE t1 (

  ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  dt DATETIME  DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

);

1、DEFAULT CURRENT_TIMESTAMP  指示在 insert 操作时自动插入当前日期时间2、ON UPDATE CURRENT_TIMESTAMP 指示在 update 操作时自动更新为当前日期时间

五、DATETIME 和 TIMESTAMP 的时区问题

datetime 和 timestamp 两种类型的另一个区别,就是它们对待 时区 有所不同

1、timestamp 会将值的时区从当前时区转换为 UTC 然后存储,并在需要显示和检索时从 UTC 转换回当前时区

2、但对于 datetime 类型,什么都不会发生,值是什么时区,存储的就是什么时区

默认情况下,timestamp 和 datetime 都会将当前连接的 MySQL 服务器的时区当作当前时区,当然了,这个时区是可配置的,而且可以针对每个连接单独配置。

从某些方面说,在数据转移或者在不同地方显示时,只要设置了一样的时区,那么数据就是一致的,否则

1、datetime 的值虽然存储和显示的时候都是同一个值,但可能不是我们想要的,因为时区错了

2、timestamp 虽然可以保证时间是正常的,但存储的值和显示的值可能会不一样,可能会导致我们错觉发生

六、DATE、DATETIME 和 TIMESTAMP 的注意事项

1、对于这三种日期时间类型,无效的值将会转换为相应的 值,也就是,date 类型会转换为 0000-00-00 ,datetime 类型会转换为 0000-00-00 00:00:00 ,而 timestamp 则会转换为 1970-01-01 00:00:00。

2、虽然 MySQL 支持为指定为字符串的值使用 宽松 格式,其中任何标点字符都可以用作日期部分或时间部分之间的分隔符,但是,在某些情况下,这种语法可能是欺骗性的。

例如,10:11:12 之类的值可能看起来像时间值,因为它们的分隔符是 : ,但它也可以表示为一个日期 2010-11-12。

还有,年月日时分秒中的任何一部分只要非法,那么整个值就会 归零,例如 date 类型的值如果为 10:45:15 那么就会被转换为 0000-00-00,因为 45 不是有效月份值

3、上面也提到了,就是日期和时间部分与小数秒部分之间识别的唯一分隔符是小数点

4、MySQL 服务器会真实的判断一个日期是否有效,而不仅仅是判断月份和日期是否分别在 1 到 12 和 1 到 31 的范围内。

在严格模式下 ( sql_mode=TRADITIONAL ) 无效的月份和日期 ( 例如 ‘2004-04-31′ )直接会导致一个错误发生,

即使在禁用严格模式后,’2004-04-31’ 等无效日期也会被转换为 ‘0000-00-00’ 且生成一个警告

5、对于 timestamp 类型,MySQL 并不支持月份和日期中的 0 值,也就是没有什么 00 月和 00 日,这些都不是有效的值,会直接导致整体的值被转换为 1970-01-01 00:00:006、对于 datetime 和 timestamp 类型,两位数的年是不确定的日期,因为不知道是属于哪个世纪。

对于两位数的年份,MySQL 使用下面这些规则解释

1、00-69 范围内的年份值将转换为 2000-2069

2、70-99 范围内的年份值转换为 1970-1999

MySQL 可以帮我们做这些事情,前提就是使用 datetime 和 timestamp 两个类型。

timestamp 和 datetime 列可以自动初始化并更新为当前日期和时间 ( 即当前时间戳 ) 。

对于 MySQL 表中的任何 timestamp 和 datetime 列,我们可以将设置当前时间戳为默认值,自动更新值或两者皆有:

1、如果将某个列设置为插入时自动初始化为当前时间戳,那么就可以不用为这些列指定任何值。

2、当行中任何其他列的值发生变更时,设置了自动更新的列将自动更新为当前时间戳。

当然了,这里有一个要点,如果 update 时任何其它列的数据并没有发生变更,那么自动更新列保持不变。

如果要更新自动更新的列,即使其他列未更改,则需要显式设置值为应具有的值,例如使用 CURRENT_TIMESTAMP()

如果要防止在其他列更改时更新自动更新列,则需要显式的设置自动更新列的值为当前值

除了以上这些规则,如果禁用 ( disabled ) 了 explicit_defaults_for_timestamp 系统变量,则可以通过为其指定 NULL 值来初始化或更新任何 timestamp(但不是 datetime )列到当前日期和时间,除非已使用 NULL 属性定义允许 NULL 值,要指定自动属性,可以在创建表时在列定义中使用 DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP 两个约束,约束的顺序无关紧要。

如果这两个约束同时存在于某个列定义中,那么任何一个都可能先执行。

CURRENT_TIMESTAMP 可以使用任何同义词代替,任何 CURRENT_TIMESTAMP 的任何同义词与 CURRENT_TIMESTAMP 具有相同的含义,例如 CURRENT_TIMESTAMP()、NOW()、LOCALTIME、LOCALTIME()、LOCALTIMESTAMP 和 LOCALTIMESTAMP() 。

当在 datetime 和 timestamp 列上使用了 DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP 约束时,其实还是可以添加 DEFAULT 约束的,default 约束用于指定常量 (非自动 ) 默认值;例如 DEFAULT 0 或 DEFAULT '2018-09-14 00:00:00'

下面的示例中可能会使用到 default 0 ,这是一个默认值,但可以产生警告或错误,具体取决于是否启用了严格的 SQL 模式或 NO_ZERO_DATE SQL 模式。

timestamp 和 datetime 列可以同时指定默认值和自动更新值为当前时间戳,也可以值指定其中一个,或者两个都不指定。不同的列可以具有不同的自动属性组合。下面的规则则描述了一些可能的使用场景

1、同时指定 DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP,那么该列会自动设置当前时间为默认值,并自动更新为当前时间戳

CREATE TABLE t1 (

  ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  dt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

);

2、使用了 default 约束,当没有使用 ON UPDATE CURRENT_TIMESTAMP 约束时,该列会自动使用给定的默认值,但不会自动更新为当前时间戳

而默认的值取决于 default 子句是指定 CURRENT_TIMESTAMP 还是常量值。

如果使用 CURRENT_TIMESTAMP,默认值是当前时间戳

CREATE TABLE t1 (

  ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  dt DATETIME DEFAULT CURRENT_TIMESTAMP

);

3、如果使用了 ON UPDATE CURRENT_TIMESTAMP 约束和常量 default 约束,该列将自动更新为当前时间戳并具有给定的常量默认值

CREATE TABLE t1 (

  ts TIMESTAMP DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP,

  dt DATETIME DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP

);

4、如果使用了 ON UPDATE CURRENT_TIMESTAMP 约束当没有使用 default 约束,该列将自动更新为当前时间戳,且不会使用当前时间戳作为其默认值

这种情况下的默认值取决于所使用的类型。

timestamp 类型的默认值为 0,除非使用了 NULL 约束,那么默认值就是 NULL

CREATE TABLE t1 (

  ts1 TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,    -- default 0

  ts2 TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP -- default NULL

);

datetime 类型的默认值为 NULL ,除非使用 NOT NULL 约束,那么默认值就是 0

CREATE TABLE t1 (

  dt1 DATETIME ON UPDATE CURRENT_TIMESTAMP,        -- default NULL

  dt2 DATETIME NOT NULL ON UPDATE CURRENT_TIMESTAMP -- default 0

);

需要注意的是,datetime 和 timstamp 默认是没有任何自动属性的,如果需要自动属性需要显式的指明,但有一个例外情况:

「 如果禁用了系统变量 explicit_defaults_for_timestamp ,且没有任何一个 timestamp 类型的列明确的设置了自动属性,那么第一个 timestamp 类型的列会自动设置为 DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP 」

如果要禁止第一个 timestamp 列自动设置 DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP 属性,有以下几种方法

启用 explicit_defaults_for_timestamp 系统变量。

在这种情况下,可以为 timestamp 列设置自动初始化和自动更新的 DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP,但需要明确指明,否则任何 timetamp 都不会获得这些自动属性

或者,保持禁用 explicit_defaults_for_timestamp ,但通过以下方法来改变:

使用 default 约束为 timestamp 指定一个默认的常量值。

使用 null 约束。

但因为使用了 NULL 约束,导致列允许 NULL 值,也就意味着无法通过将列设置为 NULL 来分配当前时间戳。

使用 NULL 约束会将列设置为 NULL,而不是当前时间戳,如果要分配当前时间戳,则需要将列设置为 CURRENT_TIMESTAMP 或同义词,例如NOW()

比如下面这个表结构定义

CREATE TABLE t1 (

  ts1 TIMESTAMP DEFAULT 0,

  ts2 TIMESTAMP DEFAULT CURRENT_TIMESTAMP

                ON UPDATE CURRENT_TIMESTAMP);

CREATE TABLE t2 (

  ts1 TIMESTAMP NULL,

  ts2 TIMESTAMP DEFAULT CURRENT_TIMESTAMP

                ON UPDATE CURRENT_TIMESTAMP);

CREATE TABLE t3 (

  ts1 TIMESTAMP NULL DEFAULT 0,

  ts2 TIMESTAMP DEFAULT CURRENT_TIMESTAMP

                ON UPDATE CURRENT_TIMESTAMP);

这些表 t1、t2、t3 具有以下特征

每个表结构定义中,第一个 TIMESTAMP 列没有设置自动初始化或自动更新。

这些表的不同之处在于 ts1 列如何处理 NULL 值

对于 t1,ts1 为 NOT NULL,并为其赋值为 NULL,这会将其值设置为当前时间戳

对于 t2 和 t3 ,ts1 允许 NULL,并为其赋值 NULL

t2 和 t3 在 ts1 的默认值上有所不同。

对于 t2,ts1 被定义为允许 NULL,因此在没有显式 DEFAULT 子句的情况下,缺省值也为 NULL

对于 t3,ts1 允许 NULL 但显式默认值为 0

对于 datetime 和 timestamp 列,无论在任何位置定义了小数位数,则必须在整个列定义中使用相同的值,例如

CREATE TABLE t1 (

  ts TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)

);

但下面这种是不允许的

CREATE TABLE t1 (

  ts TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(3)

);

MySQL 服务的系统变量 explicit_defaults_for_timestamp 对 timestamp 类型的影响,知道禁用该系统变量时,timestamp 默认会添加 NOT NUTLL 的约束,此时,就不再接受 NULL 值了,同时会使用当前时间戳来填充。

这种情况下,如果要允许 timestamp 类型能够接受 NULL 值,则需要创建表结构时显式的指定 NULL 约束。

但一旦这么修改,那么默认值也会被设置为 NULL,除非使用 default 约束显式的指定默认值为其它常量值。

当然了,可以直接使用 NULL DEFAULT NULL 显示的指定 NULL 作为 timestamp 列的默认值

但是,这里有一个坑,就是不能直接使用 DEFAULT NULL 为 timestamp 列添加 NULL 作为默认值,因为这会和默认的 NOT NULL 约束冲突

是不是很绕口?

也就是在禁用系统变量 explicit_defaults_for_timestamp 的情况下,会发生以下事情

1、 timestamp 列默认会添加 NOT NULL 约束,并使用 CURRENT_TIMESTAMP() 返回的当前时间戳作为默认值

2、 可以显式的使用 NULL 约束将 timestamp 列改成接受 NULL 值,但这样默认值也会被设置为 NULL

3、 可以在显式的使用 NULL 约束的同时可以使用 DEFAULT 约束,这样,timestamp 列既可以接受 NULL 值,有可以设置其它常量值为默认值。

如果 timestamp 列添加了 NULL 约束,那么我们可以在 SQL 语句中使用 NULL 作为 timestamp 列的值,这样,timestamp 列的值就不会自动填充为当前时间戳了。

例如下面的建表语句,我们添加了几个允许 NULL 值的 timestamp 列

CREATE TABLE t

(

  ts1 TIMESTAMP NULL DEFAULT NULL,

  ts2 TIMESTAMP NULL DEFAULT 0,

  ts3 TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP

);

在这个表 t 中,允许 NULL 值的 TIMESTAMP 列在插入时并不会自动填充当前时间戳,除非触发了以下规则

像 ts3 列一样添加了 DEFAULT CURRENT_TIMESTAMP 默认值约束,这样只要未显式指定该列的值,那么就会自动填充为当前时间戳。

在插入数据或者更新数据时,使用 CURRENT_TIMESTAMP() 或任何其它相同的函数,比如 NOW() 显式的指定其值。

换句话说,定义为允许 NULL 值的 TIMESTAMP 列仅在添加了 DEFAULT CURRENT_TIMESTAMP 约束时才会自动初始化

CREATE TABLE t (ts TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP);

如果 TIMESTAMP 列允许 NULL 值但其并没有添加 DEFAULT CURRENT_TIMESTAMP 约束,则必须显式插入与当前日期和时间对应的值。

例如下面两个建表语句,表 t1 和 t2 都添加了这些约束

CREATE TABLE t1 (ts TIMESTAMP NULL DEFAULT '0000-00-00 00:00:00');

CREATE TABLE t2 (ts TIMESTAMP NULL DEFAULT NULL);

为了在任一表中为 TIMESTAMP 列在插入数据时填充当前的时间戳,需要显式为其分配该值。例如

INSERT INTO t2 VALUES (CURRENT_TIMESTAMP);

INSERT INTO t1 VALUES (NOW());

说了这么多系统变量 explicit_defaults_for_timestamp 禁用的情况,但如果开启了又会怎么样呢?

如果启用了 explicit_defaults_for_timestamp ,那么仅当添加了 NULL 约束时,TIMESTAMP 列才允许 NULL 值。

而且,TIMESTAMP 列不允许分配 NULL 以分配当前时间戳,无论是使用 NULL 约束还是使用 NOT NULL 约束。

要为 TIMESTAMP 列分配当前时间戳,则需要给列添加 CURRENT_TIMESTAMP() 或其它同义词,例如 NOW()

简单直白一点,就是,如果启用了 explicit_defaults_for_timestamp ,那么 TIMESTAMP 列是无论如何都不能填充 NULL 值的,而且一定要显式的使用 CURRENT_TIMESTAMP() 等同义词函数

你可能感兴趣的:(一篇讲透DATE、DATETIME 和 TIMESTAMP)