Xử lý Timezone trong MySQL: Đừng để App ‘lạc trôi’ giữa các múi giờ

MySQL tutorial - IT technology blog
MySQL tutorial - IT technology blog

Lỗi lệch múi giờ – Chuyện cũ nhưng chưa bao giờ hết hot

Lỗi lệch múi giờ là một trong những nguyên nhân hàng đầu gây ra các pha “support xuyên đêm”. Kịch bản rất phổ biến: Code ở Local chạy mượt, nhưng khi deploy lên AWS (thường để mặc định giờ UTC), dữ liệu báo cáo bỗng dưng lệch 7-8 tiếng. Khách đặt hàng lúc 10h sáng nhưng hệ thống lại ghi nhận là 3h đêm.

Thiết kế sai từ đầu là “án tử” cho database khi scale-up. Việc migration hàng tỷ record sau này sẽ tốn hàng tuần thay vì vài giờ. Tôi từng chứng kiến một team phải thức trắng 48 giờ chỉ để chạy script convert 50 triệu dòng dữ liệu. Tất cả chỉ vì họ lưu giờ Local thay vì chuẩn hóa UTC.

Quick Start: Kiểm tra và cấu hình trong 1 nốt nhạc

Đừng đoán mò. Hãy kiểm tra ngay database của bạn đang chạy múi giờ nào bằng lệnh sau:

-- Xem múi giờ Global, Session và thời gian hiện tại
SELECT @@global.time_zone, @@session.time_zone, NOW();

Nếu thấy kết quả là SYSTEM, MySQL đang dùng chung giờ với hệ điều hành. Đây là thiết lập mặc định nhưng cực kỳ rủi ro khi bạn di chuyển server giữa các region (ví dụ từ Singapore sang Mỹ).

Để ép session hiện tại về chuẩn UTC, hãy dùng:

SET time_zone = '+00:00';
SELECT NOW(); -- Giờ sẽ lập tức khớp với giờ quốc tế

Muốn cấu hình vĩnh viễn? Hãy thêm dòng này vào file my.cnf (Linux) hoặc my.ini (Windows) dưới thẻ [mysqld]:

[mysqld]
default-time-zone = '+00:00'

TIMESTAMP hay DATETIME? Lựa chọn quyết định vận mệnh

Dân chuyên nghiệp thường tranh luận nảy lửa về hai kiểu này. Mỗi loại đều có “cái giá” riêng của nó:

1. Kiểu TIMESTAMP (4 Bytes)

  • Cơ chế: MySQL tự convert từ múi giờ hiện tại sang UTC khi lưu và convert ngược lại khi lấy ra.
  • Giới hạn: Sẽ “ngỏm” vào ngày 19/01/2038 (sự cố Y2K38).
  • Điểm cộng: Tự động xoay theo timezone của client kết nối.

2. Kiểu DATETIME (5-8 Bytes)

  • Cơ chế: Lưu nguyên bản (What you see is what you get). Không biến đổi, không convert.
  • Giới hạn: Lưu được đến tận năm 9999.
  • Điểm trừ: Nếu server đổi vùng mà bạn không xử lý logic ở tầng App, dữ liệu sẽ bị sai lệch ngữ cảnh.

Lời khuyên thực chiến: Với các hệ thống hiện đại, hãy dùng DATETIME kết hợp với ép Server chạy UTC. Cách này giúp dữ liệu minh bạch, dễ debug và không lo giới hạn năm 2038.

Nâng cao: Sử dụng Named Time Zones cho chuyên nghiệp

Thay vì nhớ con số +07:00 khô khan, bạn có thể dùng 'Asia/Ho_Chi_Minh'. Tuy nhiên, MySQL mặc định không có sẵn bảng tên này. Trên Linux, bạn cần nạp dữ liệu từ OS vào DB bằng lệnh:

mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql

Sau khi nạp, việc set timezone sẽ trở nên cực kỳ trực quan. Ưu điểm lớn nhất là MySQL sẽ tự động xử lý Daylight Saving Time (DST) – giờ mùa hè cho các thị trường Âu Mỹ mà bạn không cần sửa code.

Chiến lược “3 lớp” cho ứng dụng toàn cầu

Để hệ thống không bao giờ sai giờ, hãy áp dụng công thức chuẩn sau:

  1. Database Layer: Luôn fix cứng time_zone = '+00:00'. Chỉ dùng DATETIME để lưu trữ.
  2. Application Layer: Backend luôn parse mọi input về UTC trước khi Insert. Ví dụ: Node.js dùng moment.utc() hoặc dayjs.utc().
  3. Client Layer: Frontend nhận string UTC từ API và dùng trình duyệt để hiển thị theo giờ địa phương của người dùng.

Cảnh báo: Đừng để Query bị chậm vì Timezone

Sai lầm phổ biến nhất là dùng hàm convert ngay trong mệnh đề WHERE. Với bảng 10 triệu record, câu query dưới đây sẽ gây Full Table Scan, khiến CPU server nhảy vọt lên 100%:

-- THẢM HỌA: Index bị vô hiệu hóa
SELECT * FROM orders 
WHERE CONVERT_TZ(created_at, '+00:00', '+07:00') > '2023-10-01 00:00:00';

Giải pháp: Hãy tính toán giá trị so sánh ở phía App trước, sau đó truyền vào query. Hãy để cột created_at ở trạng thái “nguyên bản” để MySQL tận dụng được Index.

-- CHUẨN: Tốc độ query tính bằng miligiây
SET @search_time = '2023-09-30 17:00:00'; 
SELECT * FROM orders WHERE created_at > @search_time;

Chốt lại những điều cần nhớ

  • Nói không với SYSTEM: Hãy chỉ định rõ timezone trong file config để tránh phụ thuộc vào OS.
  • UTC là chân á: Lưu trữ mọi thứ ở UTC. Chỉ convert sang giờ địa phương ở lớp hiển thị (Frontend).
  • Lưu ý Docker: Container MySQL thường chạy UTC, nhưng máy dev có thể là GMT+7. Hãy dùng biến môi trường TZ=Asia/Ho_Chi_Minh khi chạy container để đồng bộ.
  • Kiểm tra Driver: Một số thư viện như mysql2 (Node.js) hoặc Eloquent (Laravel) có thể tự ý convert giờ. Hãy đọc kỹ document để tắt tính năng này nếu bạn muốn tự kiểm soát.

Xử lý múi giờ không khó về kỹ thuật, nhưng đòi hỏi sự kỷ luật của cả team. Hy vọng những chia sẻ này giúp bạn tránh được những pha “mất tiền oan” vì lỗi thời gian.

Share: