MySQL关于timestamp和mysqldump的一个“bug”

php中文网
发布: 2016-06-07 17:24:03
原创
1071人浏览过

MySQL关于timestamp和mysqldump的一个ldquo;bugrdquo;

复现
 
来源于一个同事在做数据转储碰到的的问题,简化如下:
 
1、建表
 
drop table if exists tb;
 
create table tb (
 
  c timestamp not null default '0000-00-00 00:00:00'
 
) engine=innodb default charset=gbk;
 
insert into tb values(now());
 
select * from tb;
 
 
 
返回
 
mysql> select * from tb;
 
+---------------------+
 
| c                  |
 
+---------------------+
 
| 2012-12-14 00:42:45 |
 
+---------------------+
 
1 row in set (0.00 sec)
 
 
 
2、dump“出错”
 
  mysqldump  -srun/mysql.sock -uroot  test tb  --where='c="2012-12-14 00:42:45"' | grep insert
 
返回为空,也就是说导不到数据。
 
 
 
 
 
分析
 
从上面的结论看上去,似乎是mysqldump的”bug”,看得到的数据都导不出来。 如果我们先不加where条件,
 
mysqldump  -srun/mysql.sock -uroot  test tb    |grep insert                         

INSERT INTO `tb` VALUES ('2012-12-13 16:42:45');
 
 
 
接下来我们要说说关于timestamp这个字段类型。
 
首先,从大小上你可以看出来,它不是个字符串,实际上是一个整型。所以当我们执行 where c=” 2012-12-14 00:42:45”的时候,需要将其转换为整型。这就涉及到转换规则。也就是说,对于相同的时间戳,在不同的时区显示的结果是不一样的。反过来也一样,相同的字符串,在不同的时区解释下,会得到不同的时间戳。
 
我们来看一下整个mysqldump的结果。在文件头部,可以看到
 
/*!40103 SET TIME_ZONE='+00:00' */; 字样,说明mysqldump在默认情况下,是按’+00:00’(中时区).
 
而mysql客户端的默认值呢:
 
mysql> select @@time_zone;
 
+-------------+
 
| @@time_zone |
 
+-------------+
 
| SYSTEM      |
 
+-------------+
 
 
 
这个SYSTEM表示MySQL取操作系统的默认时区,因此是东8区。如果我们设置为与mysqldump相同时区,
 
mysql> set time_zone='+00:00'; 

Query OK, 0 rows affected (0.00 sec)
 
 
 
mysql> select * from tb;
 
+---------------------+
 
| t                  |
 
+---------------------+
 
| 2012-12-13 16:42:45 |
 
+---------------------+
 
1 row in set (0.00 sec)
 
就跟我们上面看到的全表导出的结果一样了。

也就是说,这个问题是因为mysqldump强行设置了时区为中时区造成的。
 
 
 
解决1
 
从mysqldump的代码中我们看到,可以用 --tz-utc=0 参数去掉前面的设置时区的动作。这样用的也是默认时区。
 
mysqldump  --tz-utc=0 -Srun/mysql.sock -uroot  test tb  --where='c="2012-12-14 00:42:45"'  |grep INSERT
 
INSERT INTO `tb` VALUES ('2012-12-14 00:42:45');
 
 
 
可以看到,这个貌似就是我们要的结果,导出的结果也很合理。
 
 
 
进一步
 
如果这个这么好,为什么mysqldump的开发者不把—tz-utc=0作为默认行为呢?也就是说哦这样做有什么风险?
 
实际上是因为要防止跨时区导数据。假设你把中国一个机器上的数据导入到美国的一个mysqld(想起@plinux 说的b2b就有这种情况),若不显式地设置一个时区,在导入时就会出错了。因为都用系统默认的时区,相同的字符串值会得到不同的时间戳。如我们前面说的, 时间戳是以整型方式存储的。
 
 
 
解决2
 
所以上面的--tz-utc=0存在风险。当然如果你确定源和目标系统时区没变,是ok的。我们讨论看看有没有更保险的方法。
 
既然是时间戳是保险的,其实可以考虑,用时间戳来做where条件。
 
mysql> select unix_timestamp(c) from tb;
 
+-------------------+
 
| unix_timestamp(c) |
 
+-------------------+
 
|        1355416965 |
 
+-------------------+
 
 
 
按照表里的这个值,,我们的dump命令改成
 
mysqldump  -Srun/mysql.sock -uroot  test tb  --where=' unix_timestamp(c)=1355416965' | grep INSERT

INSERT INTO `tb` VALUES ('2012-12-13 16:42:45');
 
这次对了,而且与是否使用 --tz-utc=0  无关,都能得到结果,区别只是显示问题。
 
 
 
不过对MySQL比较熟悉的同学就知道,这个写法还是存在一个问题:用不上索引,因为我们在字段上做了unix_timestamp这个操作。有时候我们在这种表上为了导出方便有一个索引专门建在timestamp字段上。
 
 
 
因此想到用逆函数
 
mysqldump  -Srun/mysql.sock -uroot  test tb  --where='c= from_unixtime(1355416965)'  | grep INSERT         

INSERT INTO `tb` VALUES ('2012-12-13 16:42:45');

linux

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
热门推荐
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号