データベースの日時設計 - カラム型の選択から多タイムゾーン対応まで
データベースにおける日時データの設計判断を体系的に解説。DATE/TIME/TIMESTAMP の使い分け、タイムゾーン付きカラムの挙動、予約システムやイベント管理での実践的な設計パターンを紹介します。
MySQL のタイムゾーン挙動は、サーバー全体のデフォルト、各クライアント接続のセッション設定、カラムの型 (TIMESTAMP か DATETIME か) という 3 つの層で決まります。多くのバグは、開発者がこの三層を区別せず「MySQL のタイムゾーンを変更する」とだけ考えることから生まれます。具体的に何を変えれば何が変わるのかを正確に把握することが、保守可能な運用の第一歩です。
サーバー側のデフォルトは my.cnf や /etc/mysql/conf.d/ の設定ファイルで default-time-zone を指定します。クライアント側は接続後に SET time_zone = '+09:00' で変更でき、JDBC や ORM のドライバが接続時に自動設定するケースが多いです。カラム側は型ごとに既定の挙動が決まっており、TIMESTAMP と DATETIME で根本的に動作が異なります。
現在の設定を確認するには SELECT @@global.time_zone, @@session.time_zone; を実行します。global がサーバー全体のデフォルト、session が今の接続に適用されている値です。SYSTEM と表示された場合は、MySQL 独自の設定ではなく OS のタイムゾーンに従っている状態を意味し、実際にどのタイムゾーンとして解釈されているかは SELECT @@system_time_zone; で確認できます。
Asia/Tokyo のような IANA タイムゾーン名を使えるかどうかは、mysql データベースのタイムゾーンテーブルに tzdata が取り込まれているかで決まります。SELECT COUNT(*) FROM mysql.time_zone_name; が 0 を返す場合は未ロードの状態で、SET time_zone = 'Asia/Tokyo' は後述の ERROR 1298 で失敗します。この場合も '+09:00' のようなオフセット指定は常に有効なので、まず現在値の確認、次に tzdata の有無の確認、という順で切り分けるのが確実です。
TIMESTAMP 型は値を内部的に UTC に変換して保存し、読み出し時にセッションの time_zone でローカルタイムに戻します。これによりクライアントが世界中どこから接続しても、各自のタイムゾーンで適切に表示される設計です。範囲は 1970-01-01 00:00:01 UTC から 2038-01-19 03:14:07 UTC までと制限があり、これは Unix エポック秒の 32 ビット表現に由来します。この上限は 8.0 系でも変わっていません。8.0.28 以降の 64 ビット環境で広がったのは UNIX_TIMESTAMP() と FROM_UNIXTIME() が受け取れる引数の範囲 (3001 年まで) であり、TIMESTAMP 型そのものの限界は 2038 年のままです。
DATETIME 型はタイムゾーンの概念を持たず、入力された「2026-05-20 10:00:00」をタイムゾーン変換せずにそのまま保持します。クライアントの time_zone がどう変わっても表示値は変わらず、範囲も 1000-01-01 から 9999-12-31 まで広いのが特徴です。一見シンプルですが、複数のタイムゾーンから利用されるシステムでは「この値はどこの時刻か」を別途記録しないと意味が確定しないため、運用上はかえって難しい型です。
| 観点 | TIMESTAMP | DATETIME |
|---|---|---|
| 保存されるもの | 内部的に UTC へ変換された時刻 | 入力された「2026-05-20 10:00:00」がそのまま |
| 取り出し時の time_zone 依存 | 依存する。セッションの time_zone でローカルタイムに戻る | 依存しない。time_zone を変えても表示値は同じ |
| 表現できる範囲 | 1970-01-01 00:00:01 UTC から 2038-01-19 03:14:07 UTC (Unix エポック秒の 32 ビット表現に由来) | 1000-01-01 から 9999-12-31 |
| DEFAULT CURRENT_TIMESTAMP | 使える。セッションの time_zone を基準に UTC へ変換して格納される | MySQL 5.6 以降は使える。ただしサーバーの time_zone を変えると保存値の意味も静かに変わる |
| 値の意味を確定させるには | 型そのものが絶対時刻を表すため追加情報は不要 | 「この値はどこの時刻か」を別途記録する必要がある |
新規プロジェクトの推奨は、サーバーの time_zone を UTC に固定して TIMESTAMP を使い、表示時の変換はアプリケーション層に集約する構成です。
クライアント接続時に SET time_zone = 'Asia/Tokyo' のように指定すると、それ以降の TIMESTAMP の読み書きと NOW()、CURRENT_TIMESTAMP() が指定タイムゾーンで動作します。IANA タイムゾーン名を使う場合は、サーバーに mysql_tzinfo_to_sql で tzdata を取り込んでおく必要があります。Docker の公式 MySQL イメージは IANA タイムゾーンが未インストールのことが多く、SET time_zone = 'Asia/Tokyo' が ERROR 1298 で失敗する典型例です。
tzdata の取り込みを避けるなら、SET time_zone = '+09:00' のようにオフセットだけ指定する方法もあります。ただしこの方式ではサマータイムを表現できないため、日本のように年中固定オフセットの地域に限定的に有効な手段です。米国やオーストラリアなどサマータイムのある地域を扱うシステムでは、必ず IANA タイムゾーン名で運用する設計に切り替えるべきです。
MySQL Connector/J (JDBC ドライバ) では、接続 URL に serverTimezone もしくは connectionTimeZone のパラメータを付与してタイムゾーンを明示します。Connector/J 8.0 以降は connectionTimeZone が推奨で、SERVER (サーバー側設定を尊重) や任意の IANA 名を指定でき、既定値は LOCAL (JVM のデフォルトタイムゾーン) です。これを明示しないと、Java 側の Instant や OffsetDateTime を MySQL に書き込む際に、既定値 LOCAL に基づくタイムゾーン変換が暗黙に行われます。なお connectionTimeZone を指定してもサーバー側セッション変数の time_zone は変わらないため、セッションまで揃えたい場合は forceConnectionTimeZoneToSession=true を併せて指定します。
よくある事故は、開発環境では Java の TimeZone も MySQL の time_zone も JST だったため動いていたコードが、本番の UTC サーバーで時差 9 時間のずれを起こすケースです。これを防ぐには、JDBC の URL で connectionTimeZone を明示し、アプリケーション全体で Instant や OffsetDateTime を使う方針を徹底することです。レガシーコードの java.util.Date は内部的にローカルタイム情報を持つため、JDBC の自動変換でずれが入りやすく、java.time API への移行が根本治療になります。
TIMESTAMP カラムに DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP を設定するパターンは、created_at / updated_at の管理で多用されます。この場合の「現在時刻」はサーバーセッションの time_zone を基準に内部的に UTC へ変換されるため、サーバーの time_zone が UTC ならエポック秒に対応する正確な値が入ります。一方、サーバーが JST 設定で運用されている場合、後からタイムゾーンを変更すると過去データの解釈が変わるため、運用方針を変える際は慎重な検討が必要です。
DATETIME カラムに DEFAULT CURRENT_TIMESTAMP を指定することも MySQL 5.6 以降は可能です。ただし DATETIME はタイムゾーンを保持しないため、サーバーの time_zone が変わると保存値の意味も静かに変わります。本番環境のサーバー time_zone を変更する作業は、過去データの解釈に影響するためほぼ不可逆な決定であり、チーム全体での合意と十分なテストが不可欠です。
新規プロジェクトでの推奨は、サーバーの time_zone を UTC に固定し、TIMESTAMP 型を使い、アプリケーション側で Instant や OffsetDateTime を扱う構成です。表示時のタイムゾーン変換はアプリケーション層に集約され、データベースには常に UTC の絶対時刻が保存されます。マルチリージョン展開時にもデータの解釈に揺らぎが生じず、レプリケーションでの不整合も起きません。
既存システムが DATETIME 型と JST サーバーの組み合わせで動いている場合、すぐの全面移行は困難です。最低限の対策として、新規カラムは TIMESTAMP に統一し、JDBC の connectionTimeZone を明示し、アプリケーションログには必ず UTC オフセットを残す形に整えていきます。長期的には DATETIME を TIMESTAMP に置き換える移行計画を立て、各カラムの意味を「この値はどの時刻か」のドキュメントとともに整備することが、タイムゾーンバグを生まない組織への第一歩です。
この記事は役に立ちましたか?
データベースにおける日時データの設計判断を体系的に解説。DATE/TIME/TIMESTAMP の使い分け、タイムゾーン付きカラムの挙動、予約システムやイベント管理での実践的な設計パターンを紹介します。
タイムゾーンの基本概念から UTC を基準とした時差の決まり方、世界各国の標準時の採用状況までわかりやすく解説します。
国際日付変更線の位置と役割、なぜジグザグに引かれているのか、日付変更線を越えるとどうなるのかを解説します。