[转帖]MySQL datetime类型字段的毫秒四舍五入_MySQL, Oracle及数据库讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  MySQL, Oracle及数据库讨论区 »
总帖数
3
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 2752 | 回复: 2   主题: [转帖]MySQL datetime类型字段的毫秒四舍五入        下一篇 
panpan.nie
注册用户
等级:大校
经验:4754
发帖:217
精华:2
注册:1970-1-1
状态:离线
发送短消息息给panpan.nie 加好友    发送短消息息给panpan.nie 发消息
发表于: IP:您无权察看 2015-12-14 11:33:41 | [全部帖] [楼主帖] 楼主

是由一则生产环境问题引出的MySQL对于datetime、timestamp等时间类型字段中毫秒的处理的深究。

  【问题描述】

  现有的生产应用升级mysql-connector-java驱动后,datetime字段值xxx入库后,按照该xxx值却查不到刚刚写入的记录。即:select * from table where insert_time=#xxx#返回为空。

  【问题原因】

  1、断点debug发现:datetime字段值入库前为”2015-01-01 00:00:00″,入库后却是”2015-01-01 00:00:01″

  2、开启debug日志,输出执行sql,发现实际提交至mysql server的参数是”2015-01-01 00:00:00.560″,判断是毫秒部分发生四舍五入所致

  3、追查mysql-connector-java驱动,发现驱动由5.1.6升级至5.1.30时,datetime字段的毫秒处理不同。”com.mysql.jdbc.PreparedStatement“中的

  (1)5.1.6版本,直接丢弃毫秒部分(代码飘黄部分)

private void setTimestampInternal(int parameterIndex, Timestamp x, Calendar targetCalendar, TimeZone tz, boolean rollForward)
                throws SQLException
{
    if(x == null)
    {
        setNull(parameterIndex, 93);
    } 
    else
    {
        checkClosed();
        if(!useLegacyDatetimeCode)
        {
            newSetTimestampInternal(parameterIndex, x, targetCalendar);
        } else
        {
            String timestampString = null;
            Calendar sessionCalendar = connection.getUseJDBCCompliantTimezoneShift() ? connection.getUtcCalendar() : getCalendarInstanceForSessionOrNew();
            synchronized(sessionCalendar)
            {
                x = TimeUtil.changeTimezone(connection, sessionCalendar, targetCalendar, x, tz, connection.getServerTimezoneTZ(), rollForward);
            }
            if(connection.getUseSSPSCompatibleTimezoneShift())
                doSSPSCompatibleTimezoneShift(parameterIndex, x, sessionCalendar);
            else
                synchronized(this)
                {
                    if(tsdf == null)
                        tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss''", Locale.US);
                    timestampString = tsdf.format(x);
                    setInternal(parameterIndex, timestampString);
                }
        }
        parameterTypes[(parameterIndex - 1) + getParameterIndexOffset()] = 93;
    }
}

(2)5.1.30版本,判断若server端版本为5.6.4及以上时,则将毫秒部分一同提交至server

protected void detectFractionalSecondsSupport() throws SQLException {
        this.serverSupportsFracSecs = this.connection != null && 
                        this.connection.versionMeetsMinimum(5, 6, 4);
}

private void setTimestampInternal(int parameterIndex,
                Timestamp x, Calendar targetCalendar,
                TimeZone tz, boolean rollForward) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
        if (x == null) {
            setNull(parameterIndex, java.sql.Types.TIMESTAMP);
        } else 
        {
            checkClosed();
            
            if (!this.useLegacyDatetimeCode) {
                    newSetTimestampInternal(parameterIndex, x, targetCalendar);
            } 
            else 
            {
                Calendar sessionCalendar = this.connection.getUseJDBCCompliantTimezoneShift() ?
                                this.connection.getUtcCalendar() : 
                                        getCalendarInstanceForSessionOrNew();
                        
                synchronized (sessionCalendar) {
                    x = TimeUtil.changeTimezone(this.connection, 
                                    sessionCalendar,
                                    targetCalendar,
                                    x, tz, this.connection
                            .getServerTimezoneTZ(), rollForward);
                }

                if (this.connection.getUseSSPSCompatibleTimezoneShift()) {
                        doSSPSCompatibleTimezoneShift(parameterIndex, x, sessionCalendar);
                } 
                else {
                    synchronized (this) {
                        if (this.tsdf == null) {
                                this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss", Locale.US); //$NON-NLS-1$
                        }
                        
                        StringBuffer buf = new StringBuffer();
                        buf.append(this.tsdf.format(x));

                        if (this.serverSupportsFracSecs) {
                                int nanos = x.getNanos();
                                
                                if (nanos != 0) {
                                        buf.append('.');
                                        buf.append(TimeUtil.formatNanos(nanos, this.serverSupportsFracSecs, true));
                                }
                        }

                        buf.append('\'');

                        setInternal(parameterIndex, buf.toString()); // SimpleDateFormat is not thread-safe
                    }
                }
            }
            
            this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.TIMESTAMP;
        }
    }
}


3、经查,生产mysql server版本是5.6.16,按照推理,server会存储client提交过来的毫秒部分,不应发生四舍五入的截断。


4、查看MySQL 5.6 manual发现,5.6.4及以上版本的mysql server端确实支持fractional second part(fsp),但如果client提交过来的小数位数超过server端建表时指定的小数位数,mysql server会自动进行四舍五入的截断,没有任何警告或异常。









赞(0)    操作        顶端 
夜华君93
注册用户
等级:下士
经验:190
发帖:0
精华:0
注册:2015-12-8
状态:离线
发送短消息息给夜华君93 加好友    发送短消息息给夜华君93 发消息
发表于: IP:您无权察看 2016-3-17 18:01:27 | [全部帖] [楼主帖] 2  楼

看不懂的代码



赞(0)    操作        顶端 
你会玩卡五星
注册用户
等级:下士
经验:187
发帖:0
精华:0
注册:2016-2-17
状态:离线
发送短消息息给你会玩卡五星 加好友    发送短消息息给你会玩卡五星 发消息
发表于: IP:您无权察看 2016-3-21 19:19:03 | [全部帖] [楼主帖] 3  楼

谢谢楼主



赞(0)    操作        顶端 
总帖数
3
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论