0000-00-00 00:00:00 的坑,你踩了吗?

2023-11-06

本文内容:

1、起因

2、MySQL 对 0000-00-00 的支持

3、Java 对 0000-00-00 的支持

4、为什么线上的代码能正常运行?

||  起因

前几天组内有系统做了数据库迁移,MySQL版本 5.6.16,其中某张表的一个字段是这么定义的:

create_time TIMESTAMP DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间'

迁移后发现新表里原本应该为 '0000-00-00 00:00:00' 的字段的值,都变成了迁移时当前时间,后来得知是XX云RDS的bug导致。

今天先抛开这个bug,只研究一下这个默认值的问题,因为之前从来没这么用过,正好借此问题学习一下。

||  MySQL 对 0000-00-00 的支持

| “zero” date or time

https://dev.mysql.com/doc/refman/5.6/en/date-and-time-types.html

官网在日期数据类型的使用注意事项中提到了"zero" date or time:

  • 日期或时间类型的值超出了范围或该类型无效时,默认会转换成"zero"值。特殊情况会转换成对应类型范围的极值。

  • DATE、DATETIME类型中,允许月、日为空, 例如"2009-00"或"2009-01-00",但是这种值用在日期函数中会出错。

  • 支持用 "0000-00-00" 代替 "zero" 值存储。在某些情况下比使用NULL要方便,还节省数据和索引空间。如果要禁用'0000-00-00',需要启用 NO_ZERO_DATE 模式。

  • Connector/ODBC 不支持 "zero" 值,会把这种值转换成NULL(说白了就是我们平时使用的图形界面的客户端)。

588fbab1be90aa4c86be4b61f35b4b50.png

下图是官网给出的几种日期类型对应的零值(也可以使用 '0' 或 0 代替):

b9114272bc1c6544e4762a7f4465dadf.png

| NO_ZERO_DATE 模式

上面提到了 NO_ZERO_DATE模式,我们就继续看一下这个模式是什么。

https://dev.mysql.com/doc/refman/5.6/en/sql-mode.html

NO_ZERO_DATE是众多SQL Mode之一,可以通过命令行启动参数或者修改MySQL的配置文件来控制具体启用哪些模式。下面是启用、查看SQL Mode的语法。

-- 设置模式
SET GLOBAL sql_mode = 'modes';
SET SESSION sql_mode = 'modes';
-- 查看模式
SELECT @@GLOBAL.sql_mode;
SELECT @@SESSION.sql_mode;

这个模式决定着 '0000-00-00' 是否是一个有效值。

  • 未启用,可以插入 '0000-00-00'。

  • 启用,插入 '0000-00-00' 有警告。

  • 启用 并且在Strict SQL模式下,插入会报错,如果指定了INSERT IGNORE 和 UPDATE IGNORE,插入的时候只会发出警告。

| Strict SQL 模式

因为很多模式的效果都会受到Strict SQL模式的影响,包括上面说的NO_ZERO_DATE,所以这里捎带说一下Strict SQL模式。

严格模式主要是控制插入、更新时的无效值(包括类型错误、数值越界、除数为0),如果是查询类的只会发出警告。从5.6.11开始,严格模式下如果数值越界会报错,但这之前会截取到边界值来使用。

如果STRICT_ALL_TABLES或STRICT_TRANS_TABLES两种模式任意启用一个,都相当于启用了Strict SQL。

所以到底到报错还是警告,是受多种模式共同影响的。

||  Java 对 0000-00-00 的支持

| Date / SimpleDateFormat

自己写了一个测试类,运行结果很是诧异,看了几遍内部处理逻辑,没看懂。

结论就是:不报错,但结果是不准确的。

public class DateTest {
    public static void main(String[] args) throws Exception{
        Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("0000-00-00 00:00:00");
        System.out.println(date);
        // 运行结果:Sun Nov 30 00:00:00 CST 2
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));
        // 运行结果:0002-11-30 00:00:00
        System.out.println(date.getTime());
        // 运行结果:-62170185600000
        System.out.println(new Date(-62170185600000L));
        // 运行结果:Sun Nov 30 00:00:00 CST 2
    }
}

| LocalDate

public class DateTest {
    public static void main(String[] args) throws Exception{
        System.out.println(LocalDateTime.parse("0000-00-00 00:00:00",
                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    }
}

运行结果:

Exception in thread "main" java.time.format.DateTimeParseException: Text '0000-00-00 00:00:00' could not be parsed: Invalid value for YearOfEra (valid values 1 - 999999999/1000000000): 0
  at java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1920)
  at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1855)
  at java.time.LocalDateTime.parse(LocalDateTime.java:492)
  at com.DateTest.main(DateTest.java:17)
Caused by: java.time.DateTimeException: Invalid value for YearOfEra (valid values 1 - 999999999/1000000000): 0

异常信息已经很明确了,Year的数值不在有效范围。

其他各个时间单位的有效值范围,都可以在这个类里找到java.time.temporal.ChronoField

| 通过 JDBC 查询这种数据

Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
ResultSet resultSet = conn.prepareStatement("select create_time from bla limit 1").executeQuery();
if(resultSet.next()){
    System.out.println(resultSet.getTimestamp(1));
}

结果报错了:

Exception in thread "main" java.sql.SQLException: Value '0000-00-00 00:00:00' can not be represented as java.sql.Date

||  为什么线上代码没问题?

| MySQL Client 对 0000-00-00 是如何转换的?

顺着代码捋一下,看看到底哪一步报的错,首先看服务端返回的信息,是一个字节数组,转成字符串后就是建表时的默认值 "0000-00-00 00:00:00",说明server是支持把这种默认值吐给client的。

fbf89c1e7c525a7f179c70d56400cc97.png

继续向下找,看到是在字节数组转日期的时候报错了。

com.mysql.jdbc.ResultSetRow#getDateFast(int columnIndex, byte[] dateAsBytes, int offset, int length, MySQLConnection conn, ResultSetImpl rs, Calendar targetCalendar) L118

这个方法里规定了几个合法日期里的肯定会包含字符,结果字节数组里的值都没能满足,所以就报错了。

| 玄机竟然在这里 !!!

再仔细研究一下上面的方法,zeroDate 抛出异常是因为 connection 的一个属性导致的。

9faa53bd335bb868fbdf4cd939df08e3.png

这个属性是什么时候赋值为"exception"的?

从头再debug一遍,发现在建立连接前,解析URL的过程中会实例化

com.mysql.jdbc.ConnectionPropertiesImpl,这时已经有了默认值"exception",如果URL带有此属性的参数,就会覆盖这个默认值。

a8e4119cf3efb3670dd16af70ddfcaff.png

既然是在解析URL时赋的值,那就翻一下线上的代码,还真是URL上的参数影响的。

URL = "jdbc:mysql://****:3306/****?zeroDateTimeBehavior=convertToNull";

建立连接的时候有了这个参数,在接收转换日期数据的时候,"zeroDate" 就会被转换成null,所以线上这种情况一直是按null处理的。

顺便整理一下zeroDateTimeBehavior参数对应的几个值都有什么作用:

ZERO_DATETIME_BEHAVIOR_EXCEPTION
抛异常
ZERO_DATETIME_BEHAVIOR_ROUND
转换为边界值返回
ZERO_DATETIME_BEHAVIOR_CONVERT_TO_NULL
返回null

||  总结

1、不要使用 0 做默认值了,可以使用 1970-01-01 00:00:00

2、尽量开启 Strict SQL Mode

3、注意MySQL不同版本间各种模式的差异

4、工作中试着用LocalDate 替换 Date吧

之前写过一篇 #日期 相关的文章,可以参考。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

0000-00-00 00:00:00 的坑,你踩了吗? 的相关文章

随机推荐

  • sql-labs 29 waf 绕过参数污染

    HTTP参数污染 HTTP Parameter Pollution 攻击者通过在HTTP请求中插入特定的参数来发起攻击 如果Web应用中存在这样的漏洞 可以被攻击者利用来进行客户端或者服务器端的攻击 waf服务器 tomcat 只解析重复参
  • 前端自适应布局

    在前端开发中 我们不可避免要面临适配问题 本文将介绍几种适配方式 一 px和em 1 1 px 1 2 em 二 rem 2 1 rem原理 2 2 rem如何计算的 2 3 rem使用 三 使用插件px2rem转换 3 1 原理和优点 3
  • MySQL笔记(五)使用python调用数据库进行操作

    python 访问数据库流程 在pycharm中下载pymysql 打开数据库视图 相当去navicat 设置数据库 使用python对数据库进行操作 Python2中使用的是MySQLdb模块 from pymysql import de
  • 图结构与图算法综述

    图结构与图算法综述 图结构以及图算法 无向图 有向图和网络能运用很多常用的图算法 这些算法包括 各种遍历算法 这些遍历类似于树的遍历 寻找最短路径的算法 寻找网络中最低代价路径的算法 回答一些简单相关问题 例如 图是否是连通的 图中两个顶点
  • Oracle数据库基础知识

    1 Oracle 数据库服务器体系所包含的三种主要结构是 内存结构 进程结构 存储结构 2 安装 11gR2 数据库要经过哪几个主要阶段 Grid基础架构安装 数据库软件安装 DBCA创建数据库 3 数据库实例所必须的后台进程包括 DBWn
  • QT——实战动态链接库调用

    如何在Debug模式下调用外部的动态链接库 首先在工程文件夹下 通过右键可以选择添加后 进入下图界面 选中外部库 点击下一步 选择所要调用的外部库文件debug生成的buliding文件里的libxxxxx a文件 如下图所示 点击下一步
  • 移动端中的坑和 vue中事件修饰符详解(stop, prevent, self, once, capture, passive)

    stop 是阻止冒泡行为 不让当前元素的事件继续往外触发 如阻止点击div内部事件 触发div事件 prevent 是阻止事件本身行为 如阻止超链接的点击跳转 form表单的点击提交 self 是只有是自己触发的自己才会执行 如果接受到内部
  • Eclipse 运行web项目 HTTP404错误

    Eclipse 引入web项目后 run as on server tomcat启动成功 但网页提示404 问题排查 404 服务器找不到资源 首先检查Eclipse部署路径 是否部署了资源文件 查找部署路径 发现该路经下只有一个WEB I
  • QT 计算两个日期时间差?(时间转时间戳)

    时间戳时间转换工具 时间换算工具 1 得出的结果单位是 天 不足一天为0 没有半天的说法 QDateTime time1 QDateTime fromString 2022 4 25 16 40 02 yyyy MM dd HH mm ss
  • CMake中option和cmake_dependent_option的使用

    CMake中的option命令为用户提供可以选择的布尔选项 boolean option 其格式如下 option
  • centos7.4中安装Apache服务

    安装Apache服务 大家好 今天我们在cenots7 4中安装一个web服务Apache 接下来我们先来简单了解一下Apache服务吧 Apache Http server是开源软件项目的杰出代表 基于标准的http网络协议提供网页浏览服
  • 黄鱼车

    本文转载至 http www zynews com news 2010 12 19 content 788498 htm 文 佘建民 有交关外地朋友问我迭个老上海 为啥上海人拿三轮脚踏货车叫作 黄鱼车 对这个疑问 现借 上海闲话 一角 讲讲
  • 【C++】内存管理初阶

    1 C C 内存管理 1 C C 内存分布 int globalVar 1 static int staticGlobalVar 1 void Test static int staticVar 1 int localVar 1 int n
  • 游戏数据库设计经验

    一 游戏模板数据库设计特点 软件行业一般数据库设计原则 保持数据的完整性一致性 避免数据冗余 范式设计 但游戏领域的游戏模板表设计上还需要考虑这些特点 1 1 对游戏程序只读 游戏程序只需要考虑读取性能 不需要过多考虑修改性能 1 2 数据
  • 【代码审计】模板注入

    0x00 介绍 这里主要学习下 FreeMarker 模板注入 FreeMarker 是一款模板引擎 FreeMarker 模板文件与 HTML 一样都是静态页面 当用户访问页面时 FreeMarker 引擎会进行解析并动态替换模板中的内容
  • 学一点Ceph知识:初识Ceph

    Ceph是什么 Ceph是一个开源的分布式存储系统 可大规模扩展 高性能 无单点故障 在普通的服务器上可以支持到PB级容量 商用机器上支持的容量可以达到EB级别 Ceph的竞争力 市面上提供云存储的分布式系统如阿里云的OSS 底层存储框架为
  • 深度学习语义分割(二)SegNet论文解读

    SegNet是是第一次在语义分割中应用编码器 解码器 encoder decoder 的结构 其中 编码器使用池化层逐渐缩减输入数据的空间维度 而解码器通过反卷积层等网络层逐步恢复目标的细节和相应的空间维度 从编码器到解码器之间 通常存在直
  • 学习使用flex

    会对flex做词法分析了解很多 下面我通过一个例子来详细说明如何使用flex 根据所学的词法分析内容 利用flex构造PL 0语言的词法分析器 既然是构造PL 0的词法分析器 那么我们有必要看一下pl0语言的简介和相应文法 2 PL 0语言
  • Spring Boot集成websocket

    像目前的直播 弹幕 小游戏等方面都用到了websocet进行长链接 相对于http的一次请求一次响应websocket只需要进行一次握手即长久性的建立链接进行消息互通 为什么一些场景要用websocet呢 http的请求每次都会进行校验而请
  • 0000-00-00 00:00:00 的坑,你踩了吗?

    本文内容 1 起因 2 MySQL 对 0000 00 00 的支持 3 Java 对 0000 00 00 的支持 4 为什么线上的代码能正常运行 起因 前几天组内有系统做了数据库迁移 MySQL版本 5 6 16 其中某张表的一个字段是