Phoenixタイムゾーンの処理方法について説明


一、コミュニティ版Phoenix時間関連タイプ紹介
時間データ処理はデータ開発者がよく遭遇する問題であり、時間はタイムゾーンに関連していることが知られており、タイムゾーンの処理が適切でないと、時間データの誤りをもたらし、さらに一連の厄介な問題を導入する.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:00.000」)に基づいていることを明確に示し、java.sql.Timestampはクライアントのローカルタイムゾーンを使用している.この設定が実際に使用される場合、容易に発生する問題を一例で説明する.
Statement stmt = con.createStatement();
stmt.execute("drop table test");
stmt.execute("create table test(mykey integer primary key, mytime timestamp)");
stmt.execute("upsert into test values(1, '2018-11-11 10:00:00.000')");
PreparedStatement pstmt = con.prepareStatement("upsert into test values(?, ?)");
pstmt.setInt(1, 2);
pstmt.setTimestamp(2, Timestamp.valueOf("2018-11-11 10:00:00.000"));
pstmt.executeUpdate();
con.commit();
stmt.execute("select * from test");
ResultSet rs = stmt.getResultSet();
System.out.println("select without filter results:");
while (rs.next()) {
    System.out.println(rs.getInt(1) + " : " + rs.getString(2) + " : " + rs.getTimestamp(2));
}
stmt.execute("select * from test where mytime = timestamp'2018-11-11 10:00:00.000'");
rs = stmt.getResultSet();
System.out.println("select with statement:");
while (rs.next()) {
    System.out.println(rs.getInt(1) + " : " + rs.getString(2) + " : " + rs.getTimestamp(2));
}
pstmt = con.prepareStatement("select * from test where mytime = ?");
pstmt.setTimestamp(1, Timestamp.valueOf("2018-11-11 10:00:00.000"));
pstmt.execute();
rs = pstmt.getResultSet();
System.out.println("select with preparedStatement:");
while (rs.next()) {
    System.out.println(rs.getInt(1) + " : " + rs.getString(2) + " : " + rs.getTimestamp(2));
}

結果は次のように出力されます.
select without filter results:
1 : 2018-11-11 10:00:00.000 : 2018-11-11 18:00:00.0
2 : 2018-11-11 02:00:00.000 : 2018-11-11 10:00:00.0
select with statement:
1 : 2018-11-11 10:00:00.000 : 2018-11-11 18:00:00.0
select with preparedStatement:
2 : 2018-11-11 02:00:00.000 : 2018-11-11 10:00:00.0

次の法則を見つけることができます.
  • string書き込み用gettimestamp読み取り時にタイムスタンプが8時間増えた.一方settimestampで書き込み、getStringでタイムスタンプを読み出すのは8時間少ない.
  • クエリの場合、文字列に従ってwhere条件をつづるのはstringで書き込まれたデータにのみ一致し、settimestampでwhere条件を設定したフィールドはsettimestampで書き込まれたタイムスタンプにのみ一致する.

  • クライアント、すなわちsqllineを使用するとpyの場合、文字列でのみ書き込み、文字列読み出しが可能です.ユーザがよく遭遇する使用シーンは,オンラインシステムがsettimestampで書き込まれ,sqllineが使用される.pyでクエリーをしたり、getStringでページに展示したりすると、このとき8時間以上になることがあります.条件フィルタリングを行う場合は、ユーザーは使用方法に注意しなければなりません.そうしないと、一致しない場合があります.sqllineクエリーを使用する場合はconvert_を使用する必要があります.tzメソッドはタイムゾーン変換をしてこそ正確な結果を得ることができる.
    振り返って、オープンソースPhoenix内部のタイムゾーンに関する実装ロジックを見て、ドキュメント内のタイムゾーンに関する記述をさらに理解します.java.sql.Timestampタイプはタイムゾーン付きで、デフォルトはローカルタイムゾーンであり、関数パラメータでは設定できません.PhoenixはStringやTimestamp変換にGMTタイムゾーンを用いているが,タイムゾーンを持たないと考えられる.例えば「1970-01-01 08:00:00.000」については、Phoenixが記憶する数値は28800000であり、Timestamp.valueOf("1970-01-01 08:00:00.000").gettime()で得られた数値は0であり,両者を混用するとばらつきが生じる.この論理もプログラムテストの結果をもたらす根本的な原因である.
    また、上述したのはPhoenix重クライアントの論理であり、Phoenix軽クライアントのタイムゾーンに対する処理はPhoenix重クライアントとは異なる点がある.前述の全く同じ論理を使用して、jdbc urlを軽クライアントのフォーマットにシリアル変換し、印刷結果は以下の通りです.
    select without filter results:
    1 : 2018-11-11 10:00:00 : 2018-11-11 10:00:00.0
    2 : 2018-11-11 02:00:00 : 2018-11-11 02:00:00.0
    select with statement:
    1 : 2018-11-11 10:00:00 : 2018-11-11 10:00:00.0
    select with preparedStatement:
    2 : 2018-11-11 02:00:00 : 2018-11-11 02:00:00.0

    次の法則を見つけることができます.
  • 印刷時に軽クライアントのgetStringとgetTimestampの結果は同じであり、重クライアントのgetStringと一致している.
  • 書き込みとクエリの場合、ライトクライアントとライトクライアントの論理は同じです.

  • これは,コミュニティ版軽クライアントがgettimestampを実現する際に,Timestampオブジェクトを構築する前に得られたミリ秒数値をタイムゾーンから減算し,他の操作は重クライアントに直接伝達して実現されるためである.
    以上の説明から,Phoenixはタイムゾーンの処理が非常に複雑で,少し注意しないとエラーが発生することが分かった.さらに、ユーザーが書き込み中にSQL文とsettimestampを混用した場合、汚いデータが発生し、区別できません.
    二つの方法を混用しないでください.文字列スペルSQLとオブジェクト設定PreparedStatementは、読み書きにかかわらず1つだけ選択します.
    二、アリ雲Phoenixのタイムゾーン問題の解決
    まず、従来のオープンソースデータベースにおけるタイムゾーン問題の処理方法を見てみましょう.
    ANSI SQL規格では、TIMESTAMPタイプはTIMESTAMP WITH TIMEZONEとTIMESTAMPの2種類に分けられ、前者はタイムゾーンを考慮し、後者はタイムゾーンを考慮しない.MYSQLではTIMESTAMPタイプがデフォルトのタイムゾーンであり、ユーザーが入力したタイムゾーンを指定しなければ、デフォルトはローカルタイムゾーンであり、実際に記憶するとGMTタイムゾーンに変わり、ユーザーが読み取るとローカルタイムゾーンに変換される.タイムゾーンを持たないタイプはMYSQLではDATETIMEタイプであり,ユーザがgettimestampインタフェースを呼び出すと,DATETIMEの年月日時分秒に基づいてTimestampオブジェクトが構築され,getStringとgettimestampで得られる時間は常に一致する.
    PostgresSQLのタイムゾーンに対する処理はMYSQLと異なり,PGのTIMESTAMPタイプはタイムゾーンを持たず,TIMESTAMPTZはタイムゾーンを持たない.処理の論理はMYSQLと類似しているが,内部記憶と実装が異なるだけで,ここでは後述しない.文末にはMYSQLとPGのタイムゾーンに関する参考文書が添付されており,興味のある読者はさらに検討することができる.一つ同じことは、MYSQLやPGがどのように実現され、記述されても、ユーザーが使用する過程でオープンソースPhoenixのように困惑することはありません.
    アリクラウドチームはPhoenix 5.xバージョンでは、ユーザが軽クライアントと重クライアントを使用しても、以前のように難解ではないタイムゾーンの問題を統一的に解決しました.実装ロジックはMYSQLと類似しており,TIMESTAMPタイプは実際の記憶時にGMTタイムゾーンを用い,ユーザがクライアントを用いて読み書きを行うと,ローカルタイムゾーンに応じて変換される.ユーザが軽クライアントを使用しても重クライアントを使用しても、書き込み時にstatementやPreparedStatementを使用しても、読み込み時にgetStringやgetTimestampを使用しても、クエリ時にスペル文字列やsetTimestampを使用しても、得られた結果は一致しており、理解しやすく、予想に合っている.
    前述のテストプログラムを使用して、PhoenixバージョンをアリクラウドバージョンのPhoenix 5に変更しました.x,得られた結果は以下の通りである.
    select without filter results:
    1 : 2018-11-11 10:00:00.000 : 2018-11-11 10:00:00.0
    2 : 2018-11-11 10:00:00.000 : 2018-11-11 10:00:00.0
    select with statement:
    1 : 2018-11-11 10:00:00.000 : 2018-11-11 10:00:00.0
    2 : 2018-11-11 10:00:00.000 : 2018-11-11 10:00:00.0
    select with preparedStatement:
    1 : 2018-11-11 10:00:00.000 : 2018-11-11 10:00:00.0
    2 : 2018-11-11 10:00:00.000 : 2018-11-11 10:00:00.0

    三、参考文献
    http://phoenix.apache.org/language/datatypes.html#timestamp_type
    https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
    https://www.postgresql.org/docs/current/datatype-datetime.html