我们有一个基于java的程序,其中包含几十万行代码,这些代码在过去8到9年之前完美地运行在mysql 5.5之前.客户已经安装了mysql 5.6.17,现在我们面临一个大问题:日期时间值变为0000-00-00 00:00:00这是其中一个表:如您所见,默认值为Null:
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| Id | int(11) | NO | PRI | NULL | auto_increment |
| Key1 | varchar(10) | NO | MUL | NULL | |
| Key2 | varchar(25) | NO | | NULL | |
| Date | datetime | YES | | NULL | |
| Value | text | NO | | NULL | |
Run Code Online (Sandbox Code Playgroud)
我们在java代码中通过mysql-connector这样插入:
String sql = "INSERT INTO DATA_SUNDRYMATRIX (Key1, Key2, Date, Value) VALUES(?,?,?,?)";
PreparedStatement p = c.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
p.setString(1, i.getKey1());
p.setString(2, i.getKey2());
p.setTimestamp(3, i.getDate()==null?null:new Timestamp(i.getDate().getTime()));
p.setString(4, i.getValue());
p.executeUpdate();
Run Code Online (Sandbox Code Playgroud)
我可以在Preparedstatement上使用println系统在executeupdate之前查看sql,它看起来像这样:
com.mysql.jdbc.ServerPreparedStatement[1] - INSERT INTO DATA_SUNDRYMATRIX (Key1, Key2, Date, Value) VALUES('TEST2','','2014-08-04 14:46:15','')
Run Code Online (Sandbox Code Playgroud)
但结果仍然是'0000-00-00 00:00:00在表中.这是为什么??真正奇怪的是,如果我将插入查询复制/粘贴到mysql控制台,则正确设置日期时间值.我试过没有运气升级到mysql-connector-java-5.1.31-bin.jar.SELECT @@ sql_mode; 给出NO_ENGINE_SUBSTITUTION唉,由于存在大量遗留表和代码,我们无法将其设置为严格模式.我不认为?zeroDateTimeBehavior = convertToNull可以提供帮助,因为我们不想要空值,我们想要实际值,并且它们以某种方式转换为0000-00-00,即使NULL是表的默认值. (顺便说一下,更新也是如此)这个mysql设置到底有什么问题?
编辑:我有更多的信息.我现在用以下代码扩展了我的代码:
System.out.println(p);
p.executeUpdate();
SQLWarning warning = p.getWarnings();
while (warning != null){
System.out.println("ToString(): "+warning.toString());
System.out.println("ErrorCode: "+warning.getErrorCode());
System.out.println("LocalizedMessage: "+warning.getLocalizedMessage());
System.out.println("State: "+warning.getSQLState());
warning = warning.getNextWarning();
}
Run Code Online (Sandbox Code Playgroud)
输出是:
com.mysql.jdbc.ServerPreparedStatement[1] - INSERT INTO DATA_SUNDRYMATRIX (Key1, Key2, Date, Value) VALUES('TEST2','','2014-08-04 15:35:02','')
ToString(): java.sql.SQLWarning: Data truncated for column 'Date' at row 1
ErrorCode: 1265
LocalizedMessage: Data truncated for column 'Date' at row 1
State: 01000
Run Code Online (Sandbox Code Playgroud)
所以我实际上得到了一个数据截断警告,即使我正确使用了准备好的setTimeStamp(我们已经尝试过的所有其他mysql版本),并且在执行它之前打印PreparedStatement对象时sql似乎没问题.为什么然后数据截断的原因?
编辑#2我被告知尝试使用setDate而不是setTimestamp来查看是否会出现相同的错误.它没有,但我不能使用setDate,因为我没有得到DATETIME的时间组件,只有日期.正如我所说,准备好的PreparedStatement的System.out.println给出了"INSERT INTO DATA_SUNDRYMATRIX(Key1,Key2,Date,Value)VALUES('TEST2','','2014-08-04 15:35:02', '')"这是有效的sql,在将其复制到mysql控制台时没有警告和截断.我现在也通过以下代码直接尝试了这个sql:
String sql = "INSERT INTO DATA_SUNDRYMATRIX (Key1, Key2, Date, Value) VALUES('TEST2','','2014-08-04 15:35:02','')";
PreparedStatement p = c.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
p.executeUpdate();
Run Code Online (Sandbox Code Playgroud)
这也给出了正确的结果,没有警告也没有截断.只有当我在PreparedStatement上使用setTimestamp时才会发生截断.我尝试过jconnector的旧版本和新版本3.1.10,5.1.18和5.1.31.所有版本都可以使用我的旧mysql版本,并在mysql 5.6.17中产生这个特殊的错误.
编辑#3我注意到setTimestamp(1,新的时间戳(0)); 没有截断,但1970年1月的正确日期.因此,我做了一些litte测试代码:
//CREATE TABLE `test` (Id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `Test` DATETIME NULL DEFAULT NULL);
String sql = "INSERT INTO Test (Id, Test) VALUES (?,?)";
PreparedStatement p = c.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
long current = System.currentTimeMillis();
for (long t = 0; t < current; t += (long)100000000){
p.setLong(1,t);
p.setTimestamp(2, new Timestamp(t));
p.executeUpdate();
}
p.close();
p = c.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
p.setLong(1,current);
p.setTimestamp(2, new Timestamp(current));
p.executeUpdate();
p.close();
Run Code Online (Sandbox Code Playgroud)
这只会给出一个不正确的结果,即最后一个.SELECT*FROM test给出了以下最后一行:
| Id | Test |
+---------------+---------------------+
....
| 1407000000000 | 2014-08-02 19:20:00 |
| 1407100000000 | 2014-08-03 23:06:40 |
| 1407200000000 | 2014-08-05 02:53:20 |
| 1407224107714 | 0000-00-00 00:00:00 |
Run Code Online (Sandbox Code Playgroud)
编辑#4似乎有一个错误,如果在设置setTimestamp时时间戳中存在亚秒级精度,那么整个日期时间将被破坏,而不是截断纳秒.如果我执行以下操作似乎工作正常:
p.setTimestamp(2, new Timestamp(current/1000*1000));
Run Code Online (Sandbox Code Playgroud)
然后我会得到结果
| 1407225888000 | 2014-08-05 10:04:48 |
Run Code Online (Sandbox Code Playgroud)
我很快就会把它写成一个解决方案.但我必须在代码中修复很多setTimestamp值.在5.6.17之后,任何人都可以确认这是否仍然是mysql数据库中的一个错误?
编辑#5(解决方案)TheConstructors建议useServerPrepStmts = false解决了这个问题!并且是一个更好的解决方案,我自己的/ 1000*1000解决方案,我发现,因为我只需要更改连接字符串而不是每个setTimestamp调用(好吧,我已经在最后一小时完成了这个,但很高兴有"真正的"解决方案;-))
由于从Connector/J 5.0.5 开始,服务器端准备好的语句默认被禁用,因此很可能结合MySQL 5.6.4 中引入的小数秒支持,它们会导致您的问题。
尝试添加useServerPrepStmts=false到您的连接字符串(这还应该确保System.out.println真正打印执行的 SQL 而不仅仅是近似值)。
| 归档时间: |
|
| 查看次数: |
1531 次 |
| 最近记录: |