2038/01/19 11:14:07 MySQL

给出这样一个标题,实在有些唐突,不过这是我最近一直在思考的一个问题的一个边界值,这个问题就是:在MySQL中如何给时间字段定义数据类型。

在以前的mysql数据库设计中,我们把时间型的字段都设计成了bigint型,而基本上所有插入这里边的值都是间接或直接从time()这个函数得到的。

一、关于time()

php中time()函数返回的返回的是一个Unix时间戳,其官方文档 如此解释这个函数:


int
time ( void )

Returns the current time measured in the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT).

就是当前的时间的秒数减去1970年1月1日 00:00:00时的秒数所得到的一个整形差值。

从这个函数的返回值来看,这是个整型值,也就是说,它得到的结果是一个有范围的值。在php中,对于整形的定义没有有符号和无符号之分,虽然其字长和平台有关,但一般的最大值都是为231 -1=2147483647。运行下面的php代码:

print strftime("%Y/%d/%m", 2147483647);

就会得到和标题一样中的结果了,从现在来看的话,这个日期似乎也不是那么久远了。

在php中,对于时间的处理函数大部分都存在这种限制。比如,这样的代码会出现意想不到的效果:

print strftime("%Y/%d/%m %H:%M:%S/n", 2147483648);
// 输出为:1901/14/12 04:51:44

$last = strtotime("2038/01/19 11:14:08");
var_dump($last); // 输出为bool(false)

也就是说,超过这个日期之后的日期,用php相关的函数处理会出错。

回到数据库设计来说,我们把一个只会返回int型的数据插入到了bigint型的字段中了。那么,这会不会形成对数据库空间的浪费吗?有没有更加合理的方案呢?这正是我最近一直在思考的问题。

二、关于TIMESTAMP类型

在MySQL中存在一系列的时间类型日期时间类型,包括DATETIME、DATE、 TIMESTAMP、TIME和YEAR。在这其中,TIMESTAMP最接近于我们的系统中要使用的时间字段,TIMESTAMP值返回后显示为 “YYYY-MM-DD HH:MM:SS”格式的字符串,如果要获得数字值,在sql语句时应在其字段名称后加“+0”,比如select create_date+0 from table。TIMESTAMP类型的长度为4个字节,和int类型的一样,而且它们的日期范围是完全一致,所以,我觉得TIMESTAMP存取的实际数 值应该就是日期的时间戳,只不过在select出来的时候mysql做了一些转化而已。而且,在用php和mysql配合时,如果有字段为 TIMESTAMP的话,还存在进行一些额外的转化。在《php和mysql时间互换 》这篇文章中就总结了几种常用的互换方法:

第一种方法:使用 date()和strtotime()函数

       $mysqltime=date('Y-m-d H:i:s',$phptime);

       $phptime=strtotime($mysqldate);

第二种方法:在查询语句中使用mysql函数转换:UNIX_TIMESTAMP(DATETIME=>PHP TIMESTAMP)和FROM_UNIXTIME(PHP TIMESTAMP=>DATETIME).

        $sql="SELECT UNIX_TIMESTAMP(datetimefield) FROM table WHERE ...";

        $sql="UPDATE table set datetimefield=FROM_UNIXTIME($phptime) WHERE ..";

其实,该文还提到了第三种方法,这也就是我通过对这个类型字段的研究得出的结论——在mysql中使用整数字段来保存php的timestamp类型。

三、关于用int类型字段保存时间

 

在我们的系统里边,有一个表是用来抓取blog的文章的,每天都会增长很多记 录,到现在应该有上亿的数据记录了,我们对其进行了分表的处理。假设一个有1亿条记录的表里边的字段里边有一个时间字段被处理成bigint类型了,那么 每一条记录对应地会浪费掉4个字节,那么这1亿条记录会浪费掉4*100,000,000/1024/1024M≈381M的空间。这从数据库优化的角度 上还是可以考虑一试的。

当然,从另外一个角度来说,数据库是需要考虑其扩展性的。假如真到了2038年 1月19日,那么这些日期字段要再来扩充的话,是不是会比较麻烦呢?实际上,既然对这些表进行了分表处理,那么,至少对于这个大限之日之前的一些数据表, 我们是不用考虑要修改其数据字段的类型。另外,虽然php中对于整形变量并没有有符号无符号之别,但是在mysql数据库中,却是存在无符号整形的概念。 从MySQL的官方文档 ,我们可以看到这样的描述。

Type Bytes Minimum Value Maximum Value
(Signed/Unsigned) (Signed/Unsigned)
TINYINT 1 -128 127
0 255
SMALLINT 2 -32768 32767
0 65535
MEDIUMINT 3 -8388608 8388607
0 16777215
INT 4 -2147483648 2147483647
0 4294967295
BIGINT 8 -9223372036854775808 9223372036854775807
0 18446744073709551615

也就是说,如果将int字段设置成unsigned的话,其值的范围可以提升1倍,初略算了一下,按照time()函数求时间戳的思路(以后应该会考虑扩展其返回数值的范围的),4294967295这个值,大概可以支撑到2106年去。我想,这个范围的话,足可以将我们的系统应付到我们可以预见的有生之年吧。

ps 昨天还尝试直接把系统改到2038年,没想到的是,从未出错的gtalk竟然给我报了一个错,而且,杀毒软件诺顿也同样凑了一下热闹。看来,各种软件对于时间的处理可能也存在同样的限制。

原文地址:https://www.cnblogs.com/ylqmf/p/2208909.html