メインコンテンツへ
プログラミング

MySQL のタイムゾーン設定 - サーバー・接続・カラム別の挙動

三層構造の理解 - サーバー・接続・カラム

MySQL のタイムゾーン挙動は、サーバー全体のデフォルト、各クライアント接続のセッション設定、カラムの型 (TIMESTAMP か DATETIME か) という 3 つの層で決まります。多くのバグは、開発者がこの三層を区別せず「MySQL のタイムゾーンを変更する」とだけ考えることから生まれます。具体的に何を変えれば何が変わるのかを正確に把握することが、保守可能な運用の第一歩です。

サーバー側のデフォルトは my.cnf や /etc/mysql/conf.d/ の設定ファイルで default-time-zone を指定します。クライアント側は接続後に SET time_zone = '+09:00' で変更でき、JDBC や ORM のドライバが接続時に自動設定するケースが多いです。カラム側は型ごとに既定の挙動が決まっており、TIMESTAMP と DATETIME で根本的に動作が異なります。

TIMESTAMP と DATETIME の本質的な違い

TIMESTAMP 型は値を内部的に UTC に変換して保存し、読み出し時にセッションの time_zone でローカルタイムに戻します。これによりクライアントが世界中どこから接続しても、各自のタイムゾーンで適切に表示される設計です。範囲は 1970-01-01 から 2038-01-19 までと制限がありますが、これは Unix エポック秒の 32 ビット表現に由来します (MySQL 8.0.28 以降は内部実装が拡張されたものの、互換性のため範囲制限は残っています)。

DATETIME 型はタイムゾーンの概念を持たず、値をそのまま「2026-05-20 10:00:00」という文字列として保存します。クライアントの time_zone がどう変わっても表示値は変わらず、範囲も 1000-01-01 から 9999-12-31 まで広いのが特徴です。一見シンプルですが、複数のタイムゾーンから利用されるシステムでは「この値はどこの時刻か」を別途記録しないと意味が確定しないため、運用上はかえって難しい型です。

セッションの time_zone - 動作の中心

クライアント接続時に 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 タイムゾーン名で運用する設計に切り替えるべきです。

JDBC ドライバの罠 - serverTimezone と connectionTimeZone

MySQL Connector/J (JDBC ドライバ) では、接続 URL に serverTimezone もしくは connectionTimeZone のパラメータを付与してタイムゾーンを明示します。Connector/J 8.0 以降は connectionTimeZone が推奨で、SERVER (サーバー側設定を尊重) や任意の IANA 名を指定できます。これを忘れると、Java 側の Instant や OffsetDateTime を MySQL に書き込む際に、ドライバの推測に基づくタイムゾーン変換が暗黙に行われます。

よくある事故は、開発環境では Java の TimeZone も MySQL の time_zone も JST だったため動いていたコードが、本番の UTC サーバーで時差 9 時間のずれを起こすケースです。これを防ぐには、JDBC の URL で connectionTimeZone を明示し、アプリケーション全体で Instant や OffsetDateTime を使う方針を徹底することです。レガシーコードの java.util.Date は内部的にローカルタイム情報を持つため、JDBC の自動変換でずれが入りやすく、java.time API への移行が根本治療になります。

DEFAULT CURRENT_TIMESTAMP の挙動

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 を変更する作業は、過去データの解釈に影響するためほぼ不可逆な決定であり、チーム全体での合意と十分なテストが不可欠です。

ベストプラクティス - UTC で統一する設計

新規プロジェクトでの推奨は、サーバーの time_zone を UTC に固定し、TIMESTAMP 型を使い、アプリケーション側で Instant や OffsetDateTime を扱う構成です。表示時のタイムゾーン変換はアプリケーション層に集約され、データベースには常に UTC の絶対時刻が保存されます。マルチリージョン展開時にもデータの解釈に揺らぎが生じず、レプリケーションでの不整合も起きません。

既存システムが DATETIME 型と JST サーバーの組み合わせで動いている場合、すぐの全面移行は困難です。最低限の対策として、新規カラムは TIMESTAMP に統一し、JDBC の connectionTimeZone を明示し、アプリケーションログには必ず UTC オフセットを残す形に整えていきます。長期的には DATETIME を TIMESTAMP に置き換える移行計画を立て、各カラムの意味を「この値はどの時刻か」のドキュメントとともに整備することが、タイムゾーンバグを生まない組織への第一歩です。

XB!LINE

この記事は役に立ちましたか?

関連記事