Phoenix中跟时间相关的类型有TIMESTAMP,DATE和TIME,这些类型对于时区的处理逻辑是相同的,所以这里就以TIMESTAMP类型为例来说明Phoenix关于时区的处理方式。首先,我们先来看下Phoenix文档中对于TIMESTAMP类型的描述:
The timestamp data type. The format is yyyy-MM-dd hh:mm:ss[.nnnnnnnnn]. Mapped to java.sql.Timestamp with an internal representation of the number of nanos from the epoch. The binary representation is 12 bytes: an 8 byte long for the epoch time plus a 4 byte integer for the nanos. Note that the internal representation is based on a number of milliseconds since the epoch (which is based on a time in GMT), while java.sql.Timestamp will format timestamps based on the client's local time zone.
意思明确指出TIMESTAMP类型在处理时是基于GMT时区的毫秒值(默认的基准都是"1970-01-01 00:00:00.000"),而java.sql.Timestamp使用的是客户端的本地时区。下面我们通过一个例子来说明这个设定在实际使用中,容易遇到的问题。
创建一个测试表
CREATE TABLE IF NOT EXISTS liqin_test(
id VARCHAR NOT NULL primary key,
name VARCHAR,
age VARCHAR,
date1 Date,
date2 TIMESTAMP
);
upsert into liqin_test values('1001', '李钦钦','1001', '2019-3-19 23:35:00', '2019-3-19 23:35:00')
PreparedStatement pstmt = conn.prepareStatement("upsert into liqin_test values(?, ?, ?, ?, ?)");
pstmt.setString(1, "0001");
pstmt.setString(2, "prepareStatement_Timestamp");
pstmt.setString(3, "0001");
pstmt.setTimestamp(4,Timestamp.valueOf("2018-11-11 10:00:00.000"));
pstmt.setTimestamp(5, Timestamp.valueOf("2018-11-11 10:00:00.000"));
pstmt.executeUpdate();
conn.commit();
执行查询语句
-----query----0001 存储时间为:2018-11-11 10:00:00.000
字段类型:date date type getDate=2018-11-11 date1 getstring=2018-11-11 02:00:00.000
字段类型:Timestamp Timestamp type getTimestamp=2018-11-11 10:00:00.0 date2 getstring=2018-11-11 02:00:00.000
-----query----1001 存储时间为:2019-3-19 23:35:00
字段类型:date date type getDate=2019-03-20 date1 getstring=2019-03-19 23:35:00.000
字段类型:Timestamp Timestamp type getTimestamp=2019-03-20 07:35:00.0 date2 getstring=2019-03-19 23:35:00.000
表象:用string写入用getTimestamp/getDate读取时时间戳多了8个小时;而用setTimestamp写入,用getString读出时间戳则少了8个小时。
原因:java.sql.Timestamp类型是带时区的,默认是本地时区,且不能通过函数参数设置。Phoenix在做String和Timestamp转换时使用的是GMT时区,也可以认为不带时区。比如对于"1970-01-01 08:00:00.000",Phoenix存储的数值是28800000,而Java的Timestamp.valueOf("1970-01-01 08:00:00.000").getTime()得到的数值则是0,两者混用就会出现偏差。这个逻辑也是造成程序测试结果的根本原因。
解决方法:对于读和写,字符串拼SQL和 PreparedStatement 设置日期对象,只选择一种处理方法。
Phoenix对于thin-client的处理方式也不一样,我样
-----query----0001 存储时间为:2018-11-11 10:00:00.000
字段类型:date date type getDate=2018-11-11 date1 getstring=2018-11-11
字段类型:Timestamp Timestamp type getTimestamp=2018-11-11 02:00:00.0 date2 getstring=2018-11-11 02:00:00
-----query----1001 存储时间为:2019-3-19 23:35:00
字段类型:date date type getDate=2019-03-19 date1 getstring=2019-03-19
字段类型:Timestamp Timestamp type getTimestamp=2019-03-19 23:35:00.0 date2 getstring=2019-03-19 23:35:00
表象:通过setDate/setTimestamp的值,getString和getTimestamp的结果是一样的(早于原值)
原理 :社区版轻客户端在实现getTimestamp的时候,在构造Timestamp对象之前先把得到的毫秒数值减去了时区。
解决方法:在获取的时间上增加8小时(转为北京时间)