[转自]http://hi.baidu.com/comdeng/blog/item/0fb05054b8d9c053574e00c1.html
给出这样一个标题,实在有些唐突,不过这是我最近一直在思考的一个问题的一个边界值,这个问题就是:在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竟然给我报了一个错,而且,杀毒软件诺顿也同样凑了一下热闹。看来,各种软件对于时间的处理可能也存在同样的限制。
