MySql数据库毫秒进位引发的噩梦之旅

灵异bug:神奇的23:59:59 变00:00:00

数据库中本来预期为 xxxx-xx-xx 23:59:59的字段竟有一部分是xxxx-xx-xx 00:00:00

​​​背景描述:

最近在帮朋友的公司处理一些技术工作,项目中涉及一个优惠券活动,系统逻辑设置为优惠券的截至时间(endtime)为当前时间+7天后的最后一秒,如当前时间为2017-08-22 09:15:15,那么endtime应该是2017-08-29 23:59:59,但在数据库中发现了部分行为2017-08-30 00:00:00。项目使用java开发语言,采用了hibernate,数据为mysql 。 既然发现了这个问题,那咱就得想办法解决掉。

除错之旅一:

第一眼看到这个问题时, 因为不是自己写的代码,猜测肯定是代码写错了,出现了分支代码导致一部分数据正确一部分不正确。按照业务逻辑对代码进行了定位,经简化相关代码如下:


@Service

public class CouponServiceImpl implements CouponService{

.....

public void addCoupon(){

.........

coupon.setEndDate(lastMoment(getEndTime(3, new Date(), 7)));

couponDao.create(coupon);

}

public static Date lastMoment(Date date) {

Calendar cl = Calendar.getInstance();

cl.setTime(date);

cl.set(Calendar.HOUR_OF_DAY, 23);

cl.set(Calendar.MINUTE, 59);

cl.set(Calendar.SECOND, 59);

return cl.getTime();

}

}

仔细看了代码没有分支逻辑,只有这一处设置endtime的业务代码,看来代码本身好像没什么错误。

除错之旅二:

代码没有明显错误,那会不是jdk的问题,项目使用的jdk1.6。按照这个思路百度了jdk1.6的一些bug没有发现有价值的内容,所以决定看一下Calendar 和Date的源码。在Calendar源码中发现如下描述信息:

*Example: Consider a GregorianCalendar

  • originally set to August 31, 1999. Calling set(Calendar.MONTH,

  • Calendar.SEPTEMBER) sets the date to September 31,

    1. This is a temporary internal representation that resolves to
  • October 1, 1999 if getTime()is then called. However, a

  • call to set(Calendar.DAY_OF_MONTH, 30) before the call to

  • getTime() sets the date to September 30, 1999, since

  • no recomputation occurs after set() itself.

既然会存在9月31日的中间状态,那会不会秒或者毫秒也存在这种状态呢?比如业务代码在处理时获得的new Date()的毫秒正好处于1000的临界状态呢,那么在设置时分秒后,毫秒进位导致变成了00:00:00。按照这个假设写测试代码:

public static void main(String[] args) {

for (int i = 0; i < 100000; i++) {

try {

Date now = new Date();

Calendar cl = Calendar.getInstance();

cl.setTime(now);

// cl.set(Calendar.MILLISECOND, 1000);

if (cl.get(Calendar.MILLISECOND) < 900) {

Thread.sleep(100);//将时间逼近900毫秒以上

continue;

}

System.out.print(cl.get(Calendar.MILLISECOND) + "::");

now = new Date();

System.out.print(now + " " + getEndTime(now, 7) + " ");

Date date = lastMoment(now);

Thread.sleep(1);

System.out.println(date);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

虽然毫秒逼近了999,但并没有出现神奇的结果,结果无一例外的否定这个思路。时分秒都不存在类似于日的临界状态,其实事后一想确实这样,毕竟时分秒的上限都是固定不变,只有日会随着月而出现上限不固定的情况,比如2月28日、29日,3月31日等。

除错之旅三:

不断的被否定才能更好的成长,一时没有思路就先好好吃饭睡觉第二天再思考(当然前提是生产环境已经处理掉问题了)。翌日也就是今天开始重新思考这个bug,既然代码层、jdk层没有问题,那会不会和数据库或者中间件有关呢?按照这个思路查看这个实体类的字段类型以及表字段的绑定类型和数据库ddl

@Temporal(TemporalType.TIMESTAMP)

private Date endDate;

endDate` datetime DEFAULT NULL,

忽然想到数据库的datetime可能会把毫秒做四舍五入处理,而ddl中默认datetime为0也就是不存放毫秒位,所以出现这种bug。还是以结果说话吧,创建表进行测试:

use test;

CREATE TABLE jamtest (

id INT (11) NOT NULL AUTO_INCREMENT,

date1 datetime NULL,

date2 datetime(2) NULL,

PRIMARY KEY (id)

) ENGINE = INNODB DEFAULT CHARACTER

SET = utf8 COLLATE = utf8_general_ci;

insert into jamtest(date1,date2) VALUES ('2017-01-01 23:59:59.551','2017-01-01 23:59:59.451');

SELECT * from jamtest;

date1字段自动进行了进位。

查询结果
​果然问题复现了,既然复现了解决办法自然就好找了:

1、在设置时分秒时也设置毫秒

2、设置数据库字段的精度为3,保留所有毫秒值,避免进位

3、或者设置数据库不启用毫秒功能,慎用。

问题引申:

如果你在执行上一步的sql语句时报错了,那说明的mysql版本低不支持毫秒特性,而这个低版本的mysql是不会出现这个灵异bug的,所以保持生产环境和开发环境的一致对解决问题是有很大影响的。

这个问题还是代码或者表结构设计不合理造成。继续深入分析,在网上找到了一博文解读的很深入博客,主要是mysql-connector-java在5.1.6以后的版本中会根据服务端的特性进行毫秒的传递或者舍弃,而5.1.6以前是直接舍弃毫秒的。 mysql 5.6.4及以上版本的mysql server端支持fractional second part(fsp),但如果client提交过来的小数位数超过server端建表时指定的小数位数,mysql server会自动进行四舍五入的截断,没有任何警告或异常。

​​​​​

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