设计-Timestamp和Datetime的字节之争

【缘起】

      众所周知,在进行数据库设计的时候,关于存储到秒级的时间类型字段,常用的有Timestamp和Datetime这2种。而这2种类型的字段,在存储格式以及存储空间上,又有着很大的不同,列举如下:

      那么,在实际的数据库设计中,在Timestamp和Datetime这2种秒级别的时间列类型中,如何进行选择呢?让我们看下面示例。

【行为差别】

      为了方便对Timestamp和Datetime这2种列类型的数据存储行为进行研究,我们创建如下t_timestamp_demo测试表。

      其中,my_date为DATETIME日期类型的列字段;create_time在新增数据时,自动将当前系统时区的时间戳,赋值给该列字段;update_time列字段,会在新增数据和当前行数据发生修改时,自动更新为当前系统时区的时间(戳)。

      下面,我们插入一条数据:

      如上图,通过只向my_data字段插入指定时间字符串'2099-12-24 18:18:18'后,该行数据的create_time和update_time字段,会自动插入为当前系统的时间(戳)(见上图右下角时间)。

      接下来,我们在修改一下该条数据,如下:

      如上图,经过对my_date字段进行修改后(修改其它字段也可),update_time字段就会自动更新为当前系统的最新时间(戳),这是因为在update_time字段定义的时候,给了ON UPDATE CURRENT_TIMESTAMP的列特性。

【存储限制】

      接下来我们看下,Timestamp类型的字段,所能够存储的时间范围的上限和下限分别是什么,如下:

      如上图,我们修改了update_time的时间为'2038-01-19 11:14:07',这是阿K在本地环境中能够向Timestamp类型的列字段,插入的最大的值。

      接下来,我们再多加1秒,即将update_time列字段的时间,修改为'2038-01-19 11:14:08',如下:

      如上图,我们看到Mysql会报错,说'2038-01-19 11:14:08'这个时间值,在update_time列字段的存储是不正确的,也就是说'2038-01-19 11:14:07',是Timestamp类型的字段所能存储的最大值了,那么为什么会是'2038-01-19 11:14:07'呢?

      让我们先看一下Mysql中,整数类型的存储和范围,如下:

      其中,Int类型占用4个字节的空间,其有符号的存储范围为:-2147483648 ~ 2147483647。那么我们知道Timestamp类型的字段,也是占用4个字节的存储空间,那么,我们将'2038-01-19 11:14:07'换算成时间戳,如下:

      如上图,将'2038-01-19 11:14:07'换算成时间戳后,就是2147483647。这正好是Mysql中Int类型字段所能存储的最大值了,多1秒就会超出Int类型的存储空间大小,溢出报错。

      那么Timestamp类型的字段,所能存储的时间最小值,又是多少呢?让我们继续往下看:

      如上图,我们将update_time字段修改为'1970-01-01 08:00:01',这是阿K在本地环境所能存储的Timestamp列类型字段的最小值了。

      再减1秒,就会报错,如下:

      如上图,我们看到'1970-01-01 08:00:00'这个时间值,在Mysql的Timestamp类型的列字段update_time中,存储是会报错的。也就是说,Timestamp类型的列字段,所能存储的最小时间是'1970-01-01 08:00:01',换算成时间戳,如下:

      如上图,由此可见,Timestamp类型的列字段,能存储的时间戳范围,就是有符号Int类型字段的1 ~ 2147483647,因为Timestamp列类型字段,只能存储0以上的时间戳数值。如果是unsigned无符号整数,则可以存储的时间戳值为4294967295,也就是能存储到2106年,如下:

      如上图,这可比有符号的Int类型整数,所表示的最大时间戳2147483647(即2038年),能多存68年呢。可惜目前Timestamp类型的列字段,只能存储到2147483647(2038年1月19日 11点14分07秒)。

【深究】

      上面我们分析了Timestamp和Datetime在数据库表中的存储行为和大小限制。那么,Timestamp和Datetime究竟是如何存储在数据库表里面的呢?下面通过一个示例,来看看Timestamp和Datetime类型的字段,在磁盘上面的表空间中,是如何存储的吧。

      如上图,我们创建了2个表,t_one_datetime用来存储Datetime类型的数据,字段名为my_datetime。t_one_timestamp用来存储Timestamp类型的字段,字段名为my_timestamp。

      接下来,我们在这2个表中,分别插入1条数据,如下:

      接下来,我们找到磁盘上表t_one_datetime和表t_one_timestamp的表空间文件(后缀名是.ibd),打开进行对比,如下:

t_one_datetime表空间_元数据
t_one_timestamp表空间_元数据

      从上图的元数据文件中,我们看到t_one_datetime表中,存储的是:a7 af 32 c8 ee,这5个字节的数据。而t_one_timestamp表中,存储的是:5f 90 fb 4f,这4个字节的数据。

      那么这些数据到底代表着什么意思呢?让我们先将t_one_timestamp里面存储的数据,即:5f 90 fb 4f,转换为十进制数字,如下:

      得到十进制数字为:1603337039,将其进行时间戳计算,如下:

      这正好就是我们t_one_timestamp表中,my_timestamp字段存储的时间'2020-10-22 11:23:59'。由此可见,Timestamp类型的列字段,确实是以4字节的时间戳整数的方式存储在表空间里的。

      那么t_one_datetime中,数据:a7 af 32 c8 ee,这5个字节存储的是什么东西呢?让我们先看下Mysql官网上对Datetime类型的解释,如下:

      这里我们看到,从MySQL 5.6.4开始,Datetime的存储空间变成了5个字节了(准确的说应该是5字节+0~3个字节的FSP分秒精度),那么我们这里的5个字节数据:a7 af 32 c8 ee,是如何存储的呢?我们看官网的解释,如下:

      如上图,按照官网的解释,Datetime的5个字节,总共是40个bit。将我们的数据:a7 af 32 c8 ee,转换为比特,如下:

      得到40位bit,为:1010 0111 1010 1111 0011 0010 1100 1000 1110 1110,将其按照上面官网的Datetime解释,进行位对齐后,排列如下:

1 01001111010111100 11001 01100 100011 101110

转换为表格表示,如下:

      上面表格中,我们的数据经过位拆分,分别计算后,得到的时间就是'3125年11月25日 12点35分46秒',也就是t_one_datetime表中,my_datetime字段所存储的Datetime类型的数据'3125-11-25 12:35:46'。

      这里我要重点说明下,为什么年的存储,要采用"年*13 + 月"的方式进行存储呢?

      假设我们本次要存储的时间为'9999年12月',如果按照"年月"的格式进行存储,如下:

      我们看到,按照"年月"的方式进行'9999-12'的存储,共占用了20个bit的空间,也就是2.5个字节(1个字节8bit)。

      那么如果按照"年*13+月"的方式进行存储,如下:

      如上图,通过"年*13+月"的计算存储之后,只占用了17个bit的存储空间(前面000不计算在空间内),这样就节省了3个bit的空间,使得'9999-12-31 23:59:59'这样的时间范围,可以有效存储在5个字节中,Datetime这样的存储方式,只比Timestamp列类型的字段多占用了1个字节的存储空间,就能存储下足够大的时间范围了。

【FSP】

      根据MySQL的官方文档,我们知道在5.6.4的版本开始,Timestamp和Datetime列类型的字段,在原有所占字节空间的基础上,都加入了1个叫做FSP(fractional-seconds precision)的分秒精度的扩展存储,如下:

      这个分秒精度FSP的设计,主要是为保存毫秒值需求的时间字段而设计的,也就是说,自MySQL 5.6.4开始,我们开始存储时间,精确到秒后面的6位小数了。

      看下面这个例子:

      如上图,自MySQL 5.6.4开始,只要在Timestamp和Datetime列类型的字段后面,加上表示FSP(分秒精度)的小数点位数长度值,就可以存储时间秒后面为0~6位长度的毫秒时间了,上例中t_fsp_datetime表中,my_datetime字段的FSP长度值为6,所以就能插入,表示时间秒后6位长度的小数时间'2019-01-09 12:03:15.666999'。

      那么这个FSP的位数的长短,分别会占用多少存储空间呢?官网的解释如下:

      可见,指定不同FSP的位数长度,所占用的存储空间,分别从0-3个字节不等。

【总结】

      本篇主要针对MySQL数据库设计中,Timestamp和Datetime这2个字段类型,进行了对比和分析。

      从存储限制上来说,虽然Datetime字段类型多占用了1些存储空间,但是却可以存储足够大的时间范围,适应性和可控性都Timestamp要强。

      然而Timestamp字段类型的存储,几乎就等于是Int(有符号)类型的存储。这种情况下,如果有时间戳的存储需求,完全可以使用Unsigned Int(无符号Int)或者Bigint类型来进行时间戳的存储处理,这样它不但可以充分利用Int类型的存储空间来存储更长的时间,同时也能够进行插入赋于CURRENT_TIMESTAMP的能力,从而弥补Timestamp字段类型的不足。

      从存储行为上来说,Timestamp的自动插入能力,Datetime也是支持的,所以Timestamp的额外亮点,就是在于具备自动更新的能力。但是这个能力,还要看设计者的评估,是否适合放在MySQL数据库这里进行更新。

      因为在很多分布式的场景中,MySQL数据库往往与JAVA服务系统,不在同一个服务器节点上,那么可能存在MySQL的时间和JAVA服务系统的时间,没有进行NTP时间同步。而有的设计者认为,MySQL就应该只是负责单纯存储的地方,而时间戳的确认和计算,都应该交由服务系统来进行统一控制管理,这样就不会造成MySQL时间与服务系统时间不同步,造成的时间戳自动更新错乱的问题了。

      在实际的数据库设计开发中,每位设计者的观点和想法都不尽相同,都有自己的设计考量。所谓萝卜白菜,各有所爱,关于Timestamp和Datetime的设计选择,作为开发人员的您,又是如何思考和决定的呢?

      欢迎留言与阿K进行交流讨论,希望本篇文章对您有所帮助,谢谢!

【示例】

      本文中提到的MySQL的示例文件,在阿K的Gitee中都可以找到,如下:

      https://gitee.com/Kival/mysql-work-demo

【后记】

      关于Datetime在MySQL 5.6.4之前的版本中,占用8个字节的存储空间的计算方式,官方解释如下:

      简单来说,就是用了2个Int类型的4字节空间,来存储时间数据。如:3018-12-20 15:17:55,存储的实际数据就是'30181220 151755',这样的2段4字节的Int类型空间。所以就有了大家普遍认知的MySQL中,Datetime比Timestamp的字段类型,多占用1倍(4字节)的存储空间的说法了。

【特别鸣谢】

  jeremyCai

特别感谢以上各位朋友,对本文中的错误和用词不当,做出的指点批评。只有你们的批评指正,才能激发阿K的不断学习与进步,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,938评论 2 89
  • MYSQL 基础知识 1 MySQL数据库概要 2 简单MySQL环境 3 数据的存储和获取 4 MySQL基本操...
    Kingtester阅读 7,818评论 5 116
  • MySQL不权威总结 欢迎阅读 本文并非事无巨细的mysql学习资料,而是选择其中重要、困难、易错的部分进行系统地...
    liufxlucky365阅读 2,591评论 0 26
  • MySQL5.6从零开始学 第一章 初始mysql 1.1数据库基础 数据库是由一批数据构成的有序的集合,这些数据...
    星期四晚八点阅读 1,154评论 0 4
  • 诗|木生木 我是一只小猪仔 自由自在的生活着 因为我小,猪栏无法拦阻我 栏里妈妈在呼唤我 我却当做听不到 我是一只...
    木木叔叔阅读 522评论 0 1