web-dev-qa-db-ja.com

postgres:タイムスタンプフィールドのインデックス

Postgresは初めてで、タイムスタンプの種類について質問があります。

シーンを設定するために、次のようなテーブルがあります。

CREATE TABLE IF NOT EXISTS tbl_example (
    example_id bigint not null,
    example_name text,
    example_timestamp timestamp,
    primary key (example_id)
);

次に、クエリを実行して、タイムスタンプを使用して、特定の日付に基づいて例のリストを見つけたいと思います。

たとえば、常に実行される一般的なクエリは次のとおりです。

select example_id, example_name, example_timestamp where example_timestamp = date_trunc('datepart', example_timestamp) order by example_timestamp desc;

ただし、検索プロセスを高速化するために、example_timestampフィールドにインデックスを追加することを考えていました。

CREATE INDEX idx_example_timestamp on tbl_example(example_timestamp);

私の質問は、postgresがタイムスタンプのインデックスをどのように実行するかです-言い換えると、日付/時刻に基づいてタイムスタンプにインデックスを付けますか、それとも秒/ミリ秒などになりますか?

あるいは、単純化するために、「example_date」を使用して新しい列を作成し、代わりにこの列にインデックスを付けることを考えていました。タイムスタンプフィールドから日付を取得できるため、日付とタイムスタンプフィールドの両方を使用することに熱心ではありませんでしたが、インデックスの目的で、別のフィールドを作成するのが最善かもしれないと思いました。

誰かがこれについて何か考えを持っているなら、それはありがたいですか?

ありがとう。

4
rm12345

心配しないで、幸せに

postgresはタイムスタンプのインデックスをどのように実行しますか?言い換えると、日付/時刻に基づいてタイムスタンプにインデックスを付けますか、それとも秒/ミリ秒などになりますか?

Postgresで使用されるインデックススキームの内部は、一般的に、関係なく、透過的である必要があります。また、今日学習している実装は、Postgresの将来のバージョンで変更される可能性があることに注意してください。

時期尚早の最適化 の罠に陥る可能性があります。明らかなパフォーマンスの問題があることがわかるまで、Postgresとそのデフォルトの動作を信頼してください。

瞬間

日時の処理は、理解しているよりも複雑です。

まず、 TIMESTAMP を使用しています。これは、実際には_TIMESTAMP WITHOUT TIME ZONE_の省略名です。このタイプは瞬間を表すことはできません。このタイプは、日付と時刻のみを格納します。たとえば、2020年1月23日の正午12:00です。しかし、それは日本の東京の正午を意味するのでしょうか?それとも、数時間後のフランスのパリの正午ですか?それとも、米国オハイオ州トレドの正午、さらに数時間後ですか?

SQLで明確にするために、常に型名を完全に拡張することをお勧めします。 TIMESTAMPではなく_TIMESTAMP WITHOUT TIME ZONE_を使用します。

ただし、実際にタイムライン上の特定のポイントである瞬間を表現しようとしている場合は、 _TIMESTAMP WITH TIME ZONE_ を使用する必要があります。この名前はSQL標準に由来します。しかし、Postgresや他のいくつかのデータベースでは、それは少し誤解されています。 Postgresは実際にはタイムゾーンを保存しません。代わりに、Postgresは、入力とともに送信された任意のタイムゾーンまたはUTCからのオフセット情報を使用してUTCに調整します。ストレージに書き込まれる値は常にUTCです。元のゾーン名またはオフセット番号(時間-分-秒)が気になる場合は、それを2番目の列に格納する必要があります。

データベースから取得すると、値はUTCでも出力されます。ただし、一部のミドルウェアツールは、取得後にデフォルトのタイムゾーン値を適用することを要求することに注意してください。善意ではありますが、このアンチ機能は多くの混乱を引き起こす可能性があります。以下に示すようにJava.timeオブジェクトを使用する場合、このような混乱はありません。

スパンオブタイムクエリ

Postgresは、データ型が64ビット(8オクテット)の整数として文書化されていることを考えると、おそらく Epoch-reference 日時からのカウントとしてUTCにモーメントを格納しています。ウィキペディアによると、Postgresは2000-01-01のエポックリファレンスを使用しています。これはおそらくUTCでのその日付の最初の瞬間である2000-01-01T00:00:00.0Zです。エポック参照が何に使用されているかを気にする理由はありませんが、そこに行きます。

本当のポイントは、Postgresの日時値が単純に数値として格納されているということです マイクロ秒 。ご想像のとおり、タイムスタンプの種類は特定の日付や時刻ではありません。クエリは確かにタイムスタンプ列のインデックスから恩恵を受ける可能性がありますが、日付指向(時刻なし)のクエリは特に恩恵を受けません。インデックスは日付指向ではなく、次に説明するようにすることもできません。

ある瞬間から日付を決定するには、タイムゾーンが必要です。いつでも、日付はタイムゾーンによって世界中で異なります。パリの真夜中から数分後、フランスは新しい日ですが、モントリオールケベックではまだ「昨日」です。

日付で瞬間をクエリするには、その日の最初の瞬間と翌日の最初の瞬間を決定する必要があります。次に、ハーフオープンアプローチを使用して、開始が包括的で終了が排他的である期間を定義します。開始と同じかそれ以降で、終了前でもある瞬間を検索します。ヒント:「最初と同じかそれより遅い」という別の言い方は、「前ではない」です。

Javaを使用しているので、業界をリードするJava.timeクラスを利用できます。

Java.timeクラスは nanoseconds の解像度を使用し、Postgresで使用されるマイクロ秒よりも細かいです。したがって、Postgres値をJavaにロードするのに問題はありません。ただし、ナノ秒はマイクロ秒のみを格納するためにサイレントに切り捨てられるため、反対方向に進む場合はデータの損失に注意してください。

その日の最初の瞬間を決定するとき、その日が00:00:00.0に始まると想定しないでください。一部のゾーンの一部の日付は、01:00:00.0などの別の時間に開始されます。常にJava.timeにその日の最初の瞬間を決定させます。

_ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;                          // Or `Africa/Tunis`, `America/Montreal`, etc.
LocalDate today = LocalDate.now( z ) ;
ZonedDateTime zdtStart = today.atStartOfDay( z ) ;              // First moment of the day.
ZonedDateTime zdtStop = today.plusDays( 1 ).atStartOfDay( z ) ; // First moment of the following day.
_

Half-OpenSQLステートメントを記述します。実行notハーフオープンではないため、SQLコマンドBETWEENを使用します。

_String sql = "SELECT * FROM tbl WHERE event !< ? && event < ? ;" ;  // Half-Open query in SQL.
_

開始値と終了値を準備されたステートメントに渡します。

JDBCドライバー サポートJ DBC 4.2 以降は、_PreparedStatement::setObject_&_ResultSet::getObject_を使用してほとんどのJava.timeで動作します。 。奇妙なことに、JDBC仕様はnot最も一般的に使用される2つのタイプのサポートを必要とします: Instant (常にUTC)および ZonedDateTime 。これらは、特定のドライバーで機能する場合と機能しない場合があります。標準ではOffsetDateTimeのサポートが必要なので、それに変換してみましょう。

_preparedStatement.setObject( 1 , zdtStart.toOffsetDateTime() ) ;
preparedStatement.setObject( 2 , zdtStop.toOffsetDateTime() ) ;
_

OffsetDateTimeに渡された結果のPreparedStatementオブジェクトは、その日時にそのタイムゾーンで使用されたオフセットを保持します。デバッグや好奇心のために、これらの値をUTCで確認することをお勧めします。それでは、Instantを抽出し、0時間-分-秒のオフセットを適用して、UTC自体のオフセットを保持するOffsetDateTimeを取得することにより、UTCに調整しましょう。

_OffsetDateTime start = zdtStart.toInstant().atOffset( ZoneOffset.UTC ) ;
OffsetDateTime stop = zdtStop.toInstant().atOffset( ZoneOffset.UTC ) ;
_

準備されたステートメントに渡します。

_preparedStatement.setObject( 1 , start ) ;
preparedStatement.setObject( 2 , stop ) ;
_

これらのstartstopの値がデータベースサーバーに到着すると、単純な整数であるEpochからのカウントを表す数値に変換されます。次に、Postgresは単純な数値比較を実行します。これらの整数にインデックスが存在する場合、Postgresクエリプランナーが適切と判断したときに、そのインデックスが使用される場合と使用されない場合があります。

行数が比較的少なく、それらをキャッシュするためにRAMが多い場合は、インデックスは必要ない場合があります。テストを実行し、EXPLAIN/ANALYZEを使用して実際のパフォーマンスを確認してください。

Javaによる日付列

日付指向のクエリでパフォーマンスの問題を証明する作業を行った場合は、タイプ DATE の2番目の列を追加できます。次に、その列にインデックスを付け、日付指向のクエリで明示的に参照します。

モーメントを挿入するときは、アプリにとって意味のあるタイムゾーンで認識される日付の計算値も含めてください。意図と、日付の決定に使用されたタイムゾーンの詳細を明確に文書化してください。ヒント:Postgresには、列名とそのデータ型とともに、列の定義の一部としてテキストの宣伝文句を含める機能があります。

2番目のDATE列は別の列から派生しているため、定義上冗長であり、非正規化されています。原則として、非正規化は最後の手段としてのみ検討する必要があります。

値を挿入するときのJavaコード。

_String sql = "INSERT INTO tbl ( event , date_tokyo ) VALUES ( ? , ? ) ;" ;
_

現在の瞬間と、タイムゾーン_Asia/Tokyo_で認識される現在の瞬間の日付を決定します。

_Instant now = Instant.now() ;  // Always in UTC, no need to specify a time zone here.
OffsetDateTime odt = now.atOffset( ZoneOffset.UTC ) ;  // Convert from `Instant` to `OffsetDateTime` if your JDBC driver does not support `Instant`.
ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
ZonedDateTime zdt = now.atZone( z ) ;
LocalDate localDate = zdt.toLocalDate() ; // Extract the date as seen at this moment by people in the Tokyo time zone.
_

準備したステートメントに渡します。

_preparedStatement.setObject( 1 , odt ) ;
preparedStatement.setObject( 2 , localDate ) ;
_

これで、_date_tokyo_列で日付指向のクエリを実行できます。必要に応じてインデックスを作成します。

SQLによる日付列

または、Postgres内でその_date_tokyo_列に自動的に入力することもできます。

引き金

Postgresに組み込まれている日時関数を使用して、タイムゾーン_Asia/Tokyo_で見られるその瞬間の日付を決定するトリガーを作成できます。次に、トリガーは結果の日付値をその2番目の列に書き込むことができます。

生成された値の列

または、Postgres 12を使用すると、新しく生成された列機能をより簡単に使用できます。この新機能は同じ機能を果たしますが、トリガーを定義してアタッチする手間がかかりません。この新機能の説明については、以下を参照してください。

Postgres 12では、GENERATED ALWAYS AS (…) STOREDを持つ列の値が物理的に格納されており、インデックスを付けることができます。

警告

このような日時作業にとって重要なのは、タイムゾーンの現在の定義に関する正しい情報です。通常、この情報は tz data[〜#〜] cann [〜#〜] /IANAによって維持されます。

JavaとPostgresの両方にtz dataの独自のコピーが含まれています。

世界中の政治家は、多くの場合、ほとんどまたはまったく警告なしに、タイムゾーンを再定義する傾向を示しています。したがって、気になるタイムゾーンの変更を追跡するようにしてください。 JavaまたはPostgresを更新すると、tzデータの新しいコピーが取得される可能性があります。ただし、場合によっては、どちらかまたは両方の環境(JavaとPostgres)を手動で更新する必要があります。ホスト [〜#〜] os [〜#〜] tzデータコピーもあります、fyi。

4
Basil Bourque