Timestamp hiện tại trong MySQL: NOW(), CURRENT_TIMESTAMP, SYSDATE(), UTC và các thực tiễn tốt nhất

目次

1. Câu lệnh SQL ngắn nhất để lấy ngày/giờ hiện tại trong MySQL

Nếu bạn muốn lấy ngày/giờ hiện tại trong MySQL, hai hàm đầu tiên cần nhớ là NOW()CURRENT_TIMESTAMP. Dưới đây là các ví dụ SQL ngắn nhất theo từng trường hợp sử dụng.

1.1 Lấy ngày/giờ hiện tại (cơ bản)

SELECT NOW();

hoặc

SELECT CURRENT_TIMESTAMP;
  • Cả hai đều trả về ngày/giờ hiện tại (YYYY-MM-DD HH:MM:SS).
  • Trong cùng một truy vấn, thời gian được cố định tại “thời điểm bắt đầu truy vấn”.

Các trường hợp sử dụng

  • Ghi log
  • Lấy dấu thời gian tạo bản ghi
  • Lấy thời gian tham chiếu

Nhầm lẫn thường gặp

  • Múi giờ của ứng dụng và DB khác nhau gây “dịch chuyển thời gian”. → Kiểm tra bằng SELECT @@session.time_zone;

1.2 Lấy chỉ ngày hiện tại

SELECT CURDATE();
  • Định dạng trả về: YYYY-MM-DD
  • Kiểu dữ liệu là DATE (chỉ ngày, không có thời gian)

Các trường hợp sử dụng

  • Tìm kiếm “dữ liệu hôm nay”
  • Tổng hợp hàng ngày

Lưu ý

  • Không giống như NOW(), nó không bao gồm thời gian.
  • Nếu bạn cần thời gian cho các truy vấn phạm vi, hãy dùng NOW().

1.3 Lấy chỉ thời gian hiện tại

SELECT CURTIME();
  • Định dạng trả về: HH:MM:SS
  • Kiểu dữ liệu là TIME (chỉ thời gian)

Các trường hợp sử dụng

  • Hiển thị thời gian thực thi batch
  • In ra chỉ phần thời gian trong log

Cạm bẫy thường gặp

  • Vì không có ngày, bạn không thể dùng nó cho các so sánh datetime.

1.4 Lấy thời gian hiện tại ở UTC (quan trọng)

SELECT UTC_TIMESTAMP();
  • Trả về UTC bất kể múi giờ máy chủ.
  • Được khuyến nghị cho các dịch vụ toàn cầu.

Chính sách cơ bản trong dự án thực tế

  • Lưu trữ ở UTC
  • Chuyển đổi sang giờ địa phương khi hiển thị

Lưu ý

  • Nếu ứng dụng đã tự chuyển sang UTC, hãy cẩn thận tránh chuyển đổi gấp đôi.

1.5 Lấy mili giây / micro giây

SELECT NOW(3);  -- milliseconds (3 digits after the decimal point)
SELECT NOW(6);  -- microseconds (6 digits after the decimal point)
  • Có sẵn từ MySQL 5.6.4 trở lên.
  • Dùng cho log có độ chính xác cao và kiểm toán giao dịch.

Nhận định sai lầm phổ biến

  • NOW() một mình không thể trả về phần thập phân giây.
  • Cột của bạn cũng phải chỉ định độ chính xác, ví dụ DATETIME(6).

1.6 Hàm tốt nhất theo trường hợp sử dụng (bắt đầu ở đây nếu không chắc)

Use caseRecommended function
General current datetimeNOW()
Table default valueCURRENT_TIMESTAMP
Date onlyCURDATE()
Time onlyCURTIME()
Unified UTC managementUTC_TIMESTAMP()
High-precision logsNOW(6)

Các điểm quan trọng mà người dùng thường bỏ qua

  • NOW()CURRENT_TIMESTAMP thực chất tương đương (cùng giá trị)
  • Trong cùng một truy vấn, thời gian được cố định
  • Giá trị hiển thị thay đổi tùy theo cài đặt múi giờ
  • Đối với microseconds, định nghĩa cột của bạn phải chỉ định độ chính xác

2. Sự khác nhau giữa NOW() / CURRENT_TIMESTAMP / SYSDATE()

Có nhiều hàm để lấy thời gian hiện tại, nhưng phần gây nhầm lẫn nhất là sự khác biệt giữa NOW(), CURRENT_TIMESTAMPSYSDATE().
Nếu bạn không hiểu đúng, có thể gặp hành vi bất ngờ trong log và xử lý giao dịch.

2.1 NOW()CURRENT_TIMESTAMP thực chất tương đương

SELECT NOW(), CURRENT_TIMESTAMP;
  • Cả hai đều trả về datetime hiện tại (giá trị DATETIME).
  • Trong cùng một truy vấn, thời gian được cố định tại “thời điểm bắt đầu truy vấn”.
  • CURRENT_TIMESTAMP cũng có thể được dùng với cú pháp gọi hàm:
    SELECT CURRENT_TIMESTAMP();
    

Cách chọn trong dự án thực tế

Use caseRecommended
Simple retrievalNOW()
Table DEFAULT / ON UPDATECURRENT_TIMESTAMP

Các điểm quan trọng

  • Ý nghĩa của giá trị lấy được là giống nhau.
  • Sự khác biệt chính là cú pháp / cách sử dụng (ví dụ, cho phép trong DEFAULT).
  • Không phải là sự khác biệt về kiểu dữ liệu (dễ gây hiểu lầm).

2.2 SYSDATE() trả về thời gian tại “thời điểm đánh giá”

SYSDATE() trông giống NOW(), nhưng thời điểm giá trị được cố định là khác nhau.

SELECT NOW(), SLEEP(2), NOW();

Kết quả (ví dụ):

2025-02-23 14:00:00
2025-02-23 14:00:00
SELECT SYSDATE(), SLEEP(2), SYSDATE();

Kết quả (ví dụ):

2025-02-23 14:00:00
2025-02-23 14:00:02

Sự khác biệt cốt lõi

FunctionWhen the time is fixed
NOW()Query start time
SYSDATE()Evaluation time

2.3 Ghi chú cho giao dịch và sao chép

NOW() được cố định tại thời điểm bắt đầu truy vấn, nó an toàn hơn khi bạn cần một thời gian tham chiếu nhất quán trong một giao dịch.

Ngược lại, vì SYSDATE() phụ thuộc vào thời gian thực thi, nó có thể ảnh hưởng đến khả năng tái tạo trong:

  • Sao chép
  • Công việc batch
  • Xử lý log hàng loạt

Nguyên tắc chung

  • Muốn thời gian tham chiếu cố định → NOW()
  • Cần thời điểm chính xác mỗi lần → SYSDATE() (sử dụng hạn chế)

2.4 Ghi chú: CURRENT_DATE / CURRENT_TIME / LOCALTIME

MySQL cũng hỗ trợ:

SELECT CURRENT_DATE;
SELECT CURRENT_TIME;
SELECT LOCALTIME;
  • CURRENT_DATE → tương tự CURDATE()
  • CURRENT_TIME → tương tự CURTIME()
  • LOCALTIME → tương tự NOW()

Nhầm lẫn thường gặp

  • Chúng chủ yếu là “khác biệt về hình thức”; thực chất có ít khác biệt chức năng.
  • Để an toàn, ưu tiên tính dễ đọc và chuẩn hoá trong dự án.

Những điểm chính cần nhớ từ phần này

  • NOW()CURRENT_TIMESTAMP về cơ bản có cùng ý nghĩa.
  • SYSDATE() hoạt động khác vì nó dựa trên thời gian đánh giá.
  • Đối với thời gian tham chiếu cố định, sử dụng NOW() làm mặc định.
  • Sử dụng CURRENT_TIMESTAMP cho DEFAULT và ON UPDATE.

3. Thay đổi định dạng hiển thị của ngày/giờ hiện tại

Thời gian hiện tại bạn nhận được bằng NOW() mặc định được hiển thị dưới dạng YYYY-MM-DD HH:MM:SS. Trong công việc thực tế, bạn thường cần các dạng như:

  • Chỉ hiển thị ngày
  • Sử dụng YYYY/MM/DD
  • Sử dụng định dạng địa phương (ví dụ: 23 Tháng 2, 2025)
  • Hiển thị mili giây

Để làm điều này, sử dụng DATE_FORMAT() (chuyển đổi datetime sang một chuỗi định dạng tùy ý).

3.1 Cú pháp cơ bản của DATE_FORMAT()

DATE_FORMAT(datetime, 'format_string')

Ví dụ: Chuyển đổi thời gian hiện tại sang YYYY/MM/DD HH:MM

SELECT DATE_FORMAT(NOW(), '%Y/%m/%d %H:%i');

Ví dụ đầu ra:

2025/02/23 14:35

Các ký hiệu định dạng thường dùng

SpecifierMeaningExample
%Y4-digit year2025
%m2-digit month02
%d2-digit day23
%HHour (24-hour)14
%iMinutes35
%sSeconds50
%fMicroseconds123456

Ghi chú

  • Một số ký hiệu khác nhau giữa chữ hoa và chữ thường.
  • %h là giờ 12 giờ, %H là giờ 24 giờ.

3.2 Các ví dụ định dạng thường gặp (thường trong công việc thực tế)

1. Ngăn cách bằng dấu gạch chéo

SELECT DATE_FORMAT(NOW(), '%Y/%m/%d');

2. Định dạng địa phương (Tiếng Anh)

SELECT DATE_FORMAT(NOW(), '%b %d, %Y');

3. Không có giây

SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i');

Cạm bẫy thường gặp

  • Vì nó trả về một chuỗi, bạn không thể dùng nó cho các phép tính số.
  • Đối với so sánh và tìm kiếm, bạn nên dùng kiểu DATETIME gốc.

3.3 Trích xuất chỉ phần ngày hoặc thời gian

Bạn cũng có thể lấy các phần riêng trong khi giữ nguyên kiểu dữ liệu (thay vì định dạng thành chuỗi).

SELECT DATE(NOW());   -- date only (DATE type)
SELECT TIME(NOW());   -- time only (TIME type)
SELECT YEAR(NOW());   -- year only
SELECT MONTH(NOW());  -- month only
SELECT DAY(NOW());    -- day only

Cách sử dụng đề xuất

GoalRecommended
Formatting for displayDATE_FORMAT
Calculation / comparisonDATE() / TIME()

3.4 Hiển thị với microseconds

Khi bạn cần log có độ chính xác cao:

SELECT NOW(6);

Với định dạng:

SELECT DATE_FORMAT(NOW(6), '%Y-%m-%d %H:%i:%s.%f');

Lưu ý quan trọng

  • Cột của bạn phải là DATETIME(6) hoặc TIMESTAMP(6), nếu không độ chính xác sẽ không được lưu.
  • Không khả dụng trong các phiên bản MySQL cũ hơn 5.6.4 (phụ thuộc vào môi trường).

Những lỗi thường gặp

  • Sử dụng DATE_FORMAT() trong mệnh đề WHERE sẽ ngăn các chỉ mục được sử dụng. wp:list /wp:list

    • Bad example: WHERE DATE_FORMAT(created_at, '%Y-%m-%d') = '2025-02-23'
    • Recommended: WHERE created_at >= '2025-02-23 00:00:00' AND created_at < '2025-02-24 00:00:00'
    • Lưu trực tiếp chuỗi đã định dạng hiển thị (gây giảm hiệu suất tìm kiếm)

Những điểm chính cần nhớ từ phần này

  • Sử dụng DATE_FORMAT() để thay đổi định dạng hiển thị.
  • Đối với các phép tính/so sánh, giữ nguyên kiểu dữ liệu gốc.
  • Nếu cần microseconds, chỉ định độ chính xác trong định nghĩa cột.
  • Sử dụng hàm trong mệnh đề WHERE có thể gây vấn đề hiệu năng.

4. Các phép tính datetime sử dụng thời gian hiện tại

In MySQL, thường xuyên tính toán các thứ như “N giờ sau,” “N ngày trước,” hoặc “N ngày qua” dựa trên thời gian hiện tại.
Trường hợp thực tế phổ biến nhất là “lấy dữ liệu trong 24 giờ qua.”

Nền tảng của phép tính ngày giờ là INTERVAL (cú pháp để cộng/trừ các đơn vị thời gian).

4.1 Thêm/bớt với INTERVAL

Lấy 1 giờ sau

SELECT NOW() + INTERVAL 1 HOUR;

Lấy 3 ngày trước

SELECT NOW() - INTERVAL 3 DAY;

Lấy 1 tháng sau

SELECT NOW() + INTERVAL 1 MONTH;

Các đơn vị thường dùng

UnitMeaning
SECONDSeconds
MINUTEMinutes
HOURHours
DAYDays
WEEKWeeks
MONTHMonths
YEARYears

Những lỗi thường gặp

  • DAY trong INTERVAL 1 DAY vẫn hoạt động ngay cả khi viết thường, nhưng bạn nên chuẩn hoá trong nhóm.
  • Các phép tính cuối tháng có thể khác với mong đợi vì số ngày thay đổi (ví dụ, 31/1 + 1 tháng).

4.2 Lấy dữ liệu trong 24 giờ qua (mẫu phổ biến nhất)

SELECT *
FROM users
WHERE created_at >= NOW() - INTERVAL 1 DAY;

Ý nghĩa

  • Nhắm tới “từ ngay bây giờ lùi lại 24 giờ trước.”

Lưu ý

  • Nếu bạn dùng CURDATE(), cơ sở sẽ trở thành “hôm nay lúc 00:00”, điều này thay đổi ý nghĩa.

Ví dụ (dữ liệu của hôm nay):

SELECT *
FROM users
WHERE created_at >= CURDATE();

Hiểu sự khác biệt là rất quan trọng

ExpressionMeaning
NOW() - INTERVAL 1 DAYPast 24 hours
CURDATE()Since today 00:00

4.3 Lấy sự chênh lệch ngày bằng DATEDIFF()

SELECT DATEDIFF('2025-03-01', '2025-02-23');

Kết quả:

6
  • Đơn vị là “ngày”
  • Phần thời gian bị bỏ qua

Lưu ý

  • Dấu sẽ thay đổi tùy theo thứ tự đối số.
  • Bạn không thể tính sự chênh lệch theo giờ/phút/giây.

4.4 Sử dụng TIMESTAMPDIFF() cho giờ/phút/giây

SELECT TIMESTAMPDIFF(HOUR,
                     '2025-02-23 12:00:00',
                     '2025-02-23 18:30:00');

Kết quả:

6

Phút

SELECT TIMESTAMPDIFF(MINUTE,
                     '2025-02-23 12:00:00',
                     '2025-02-23 12:30:00');

Giây

SELECT TIMESTAMPDIFF(SECOND,
                     '2025-02-23 12:00:00',
                     '2025-02-23 12:00:45');

Các trường hợp sử dụng

  • Tính thời lượng phiên làm việc
  • Kiểm tra thời hạn
  • Quyết định timeout

4.5 Lấy ngày đầu tháng / ngày cuối tháng (cạm bẫy thực tế phổ biến)

Lấy ngày đầu tiên của tháng này:

SELECT DATE_FORMAT(NOW(), '%Y-%m-01');

Ngày đầu tiên của tháng tới:

SELECT DATE_FORMAT(NOW() + INTERVAL 1 MONTH, '%Y-%m-01');

Lưu ý

  • Ngày cuối tháng không phải là một ngày cố định.
  • Đối với các truy vấn phạm vi, “>= start AND < first day of next month” là an toàn.

Tóm tắt các lỗi thường gặp

  • Sử dụng BETWEEN với giá trị chỉ ngày và quên thời gian
  • Vô hiệu hoá chỉ mục bằng cách dùng DATE(created_at) trong mệnh đề WHERE
  • Bị mắc kẹt vào vấn đề “ngày 31” trong tính toán tháng
  • Hiểu sai sự khác biệt giữa NOW()CURDATE()

Những điểm chính cần nhớ từ phần này

  • INTERVAL là nền tảng của phép tính ngày giờ.
  • 24 giờ qua = NOW() - INTERVAL 1 DAY.
  • Ngày = DATEDIFF(); các đơn vị nhỏ hơn = TIMESTAMPDIFF().
  • Cẩn thận với các điều kiện kết thúc trong tính toán dựa trên tháng.

5. Đối với truy vấn phạm vi, cách này an toàn hơn BETWEEN

Khi thực hiện tìm kiếm phạm vi ngày trong MySQL, nhiều người mới bắt đầu dùng BETWEEN. Tuy nhiên, nó thường tạo ra kết quả không mong muốn nếu bạn xử lý sai ranh giới thời gian, vì vậy một mẫu an toàn hơn được khuyến nghị trong công việc thực tế.

5.1 Cơ bản về BETWEEN và cạm bẫy

Một truy vấn phổ biến

SELECT *
FROM orders
WHERE order_date BETWEEN '2025-02-01' AND '2025-02-10';

Nó trông ổn, nhưng bên trong nó tương đương với:

WHERE order_date &gt;= '2025-02-01 00:00:00'
  AND order_date &lt;= '2025-02-10 00:00:00'

Vì vậy nó không bao gồm dữ liệu sau 00:00 ngày 10/2.


5.2 Mẫu an toàn hơn (được khuyến nghị)

Mẫu được khuyến nghị

SELECT *
FROM orders
WHERE order_date &gt;= '2025-02-01 00:00:00'
  AND order_date &lt;  '2025-02-11 00:00:00';

Các điểm chính

  • Xác định ranh giới cuối là “nhỏ hơn ngày tiếp theo vào 00:00”
  • An toàn hơn <= 23:59:59 (hỗ trợ microseconds)

5.3 Cách đúng để lấy dữ liệu của hôm nay

Ví dụ sai:

WHERE DATE(created_at) = CURDATE();

Vấn đề:

  • Áp dụng một hàm lên cột vô hiệu hoá chỉ mục
  • Có thể gây chậm đáng kể trên các bảng lớn

Đề xuất:

WHERE created_at &gt;= CURDATE()
  AND created_at &lt;  CURDATE() + INTERVAL 1 DAY;

Điều này đảm bảo:

  • Có thể sử dụng chỉ mục
  • Tìm kiếm nhanh
  • Không có vấn đề về ranh giới

5.4 Các mẫu an toàn cho 7 ngày qua / 30 ngày qua

7 ngày qua

WHERE created_at &gt;= NOW() - INTERVAL 7 DAY;

30 ngày qua

WHERE created_at &gt;= NOW() - INTERVAL 30 DAY;

Ghi chú

  • CURDATE() - INTERVAL 7 DAY dựa trên “hôm nay 00:00”
  • NOW() - INTERVAL 7 DAY dựa trên “thời gian hiện tại”
  • Chọn dựa trên yêu cầu

5.5 Quy tắc chính để giữ chỉ mục hiệu quả

Sai:

WHERE DATE(created_at) = '2025-02-23';

Đúng:

WHERE created_at &gt;= '2025-02-23 00:00:00'
  AND created_at &lt;  '2025-02-24 00:00:00';

Tại sao:

  • Chỉ mục hoạt động trên “cột tự nó”
  • Áp dụng hàm ngăn việc sử dụng chỉ mục (quét toàn bộ)

Common mistakes summary

  • Quên bao gồm thời gian ở ranh giới cuối trong BETWEEN
  • Thiếu microseconds khi dùng 23:59:59
  • Sử dụng DATE() trong mệnh đề WHERE và làm chậm truy vấn
  • Để “now” và “today 00:00” không rõ ràng

Key takeaways from this section

  • Truy vấn phạm vi an toàn nhất với >= start AND < end.
  • BETWEEN yêu cầu xử lý ranh giới cẩn thận.
  • Để giữ chỉ mục hiệu quả, không nên bao bọc cột trong hàm.
  • Rõ ràng chọn giữa logic dựa trên NOW()CURDATE().

6. DEFAULT CURRENT_TIMESTAMP và ON UPDATE (cơ bản thiết kế bảng)

Trong thiết kế cơ sở dữ liệu, thường tự động quản lý created_at và updated_at.
Trong MySQL, CURRENT_TIMESTAMP cho phép bạn đặt thời gian hiện tại tự động khi chèn và cập nhật.

6.1 Tự động đặt created_at (DEFAULT CURRENT_TIMESTAMP)

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Hành vi:

INSERT INTO users (name) VALUES ('Alice');

→ Thời gian hiện tại được tự động chèn vào created_at.

Các điểm chính

  • CURRENT_TIMESTAMP có thể được dùng trong DEFAULT.
  • NOW() không thể được dùng trực tiếp trong DEFAULT.

Lỗi thường gặp

created_at DATETIME DEFAULT NOW();  -- error

Lý do:

  • Trong MySQL, bạn thường không thể đặt một hàm làm DEFAULT (ngoại lệ duy nhất là CURRENT_TIMESTAMP).

6.2 Tự động cập nhật updated_at (ON UPDATE)

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
               ON UPDATE CURRENT_TIMESTAMP
);

Hành vi:

UPDATE users SET name = 'Bob' WHERE id = 1;

updated_at được tự động cập nhật thành thời gian hiện tại.

Các trường hợp sử dụng

  • Theo dõi lần đăng nhập cuối cùng
  • Lịch sử cập nhật
  • Nhật ký kiểm toán

6.3 DATETIME vs TIMESTAMP (quan trọng)

TypeRangeTime zone impact
DATETIMEYear 1000 to 9999No
TIMESTAMP1970 to 2038Yes

Sự khác biệt cốt lõi

  • TIMESTAMP được lưu nội bộ ở UTC và chuyển đổi sang múi giờ phiên làm việc khi hiển thị.
  • DATETIME lưu giá trị nguyên (không chuyển đổi).

Hướng dẫn

CaseRecommended type
Log managementTIMESTAMP
Future dates (after 2038)DATETIME
Fixed values not requiring TZ conversionDATETIME

6.4 CURRENT_TIMESTAMP cũng có thể dùng với DATETIME

Trong MySQL 5.6 trở lên, bạn có thể chỉ định CURRENT_TIMESTAMP làm DEFAULT và ON UPDATE cho các cột DATETIME cũng được.

created_at DATETIME DEFAULT CURRENT_TIMESTAMP

Ghi chú

  • Các phiên bản MySQL cũ hơn có hạn chế (phụ thuộc vào môi trường).
  • Nếu bạn cần độ chính xác:
    DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6)
    

6.5 Nếu bạn muốn dùng NOW(): một cách thay thế (trigger)

CREATE TRIGGER set_created_at
BEFORE INSERT ON logs
FOR EACH ROW
SET NEW.created_at = NOW();

Các trường hợp sử dụng

  • Logic giá trị ban đầu phức tạp
  • Gán dấu thời gian có điều kiện

Ghi chú

  • Triggers khó debug.
  • Tránh sử dụng chúng trừ khi cần thiết.

Các lỗi thiết kế phổ biến

  • Thêm ON UPDATE vào cả created_atupdated_at
  • Nhầm lẫn hành vi của DATETIME và TIMESTAMP
  • Hoãn quyết định múi giờ

Những điểm chính từ phần này

  • Sử dụng CURRENT_TIMESTAMP cho DEFAULT.
  • Sử dụng ON UPDATE CURRENT_TIMESTAMP để theo dõi cập nhật.
  • Chọn loại dựa trên hành vi múi giờ và vấn đề năm 2038.
  • Nếu cần độ chính xác, đừng quên (6) .

7. Thiết kế múi giờ (lưu trữ ở UTC, hiển thị theo thời gian địa phương)

Khi xử lý thời gian hiện tại, thiết kế múi giờ kém gây ra lệch thời gian và chuyển đổi kép.
Trong thực tế, mặc định an toàn nhất là “lưu trữ ở UTC, chuyển đổi sang thời gian địa phương để hiển thị.”

MySQL timezone architecture storing timestamps in UTC and converting to local time (JST)

Hình: Kiến trúc cơ bản để lưu trữ UTC trong MySQL và hiển thị thời gian địa phương

7.1 Kiểm tra múi giờ hiện tại

Đầu tiên, kiểm tra múi giờ mà MySQL đang chạy.

SELECT @@global.time_zone, @@session.time_zone;
  • @@global.time_zone → cài đặt toàn server
  • @@session.time_zone → cài đặt kết nối/phiên hiện tại
  • Nếu hiển thị SYSTEM , nó phụ thuộc vào cài đặt OS

Các vấn đề phổ biến

  • Server sản xuất và phát triển có múi giờ OS khác nhau
  • Ứng dụng chuyển đổi sang UTC, và DB chuyển đổi lại (chuyển đổi kép)

7.2 Thay đổi múi giờ theo phiên

SET time_zone = 'Asia/Tokyo';

hoặc thống nhất sang UTC:

SET time_zone = '+00:00';

Các điểm quan trọng

  • Nó sẽ trở về mặc định khi kết nối đóng
  • Nếu sử dụng connection pooling, kiểm tra cài đặt ứng dụng của bạn

7.3 Tại sao lưu trữ ở UTC (nguyên tắc thực tế)

Chính sách khuyến nghị

  1. Lưu trữ dữ liệu ở UTC
  2. Chuyển đổi sang múi giờ của người dùng khi hiển thị

Lý do:

  • Hỗ trợ quốc tế dễ dàng hơn
  • Tránh vấn đề giờ tiết kiệm ánh sáng ban ngày (DST)
  • Giảm thiểu tác động khi di chuyển server

Lấy UTC:

SELECT UTC_TIMESTAMP();

7.4 Chuyển đổi múi giờ với CONVERT_TZ()

Ví dụ: UTC → JST

SELECT CONVERT_TZ('2025-02-23 05:35:00', '+00:00', '+09:00');

Sử dụng tên múi giờ:

SELECT CONVERT_TZ('2025-02-23 05:35:00', 'UTC', 'Asia/Tokyo');

Ví dụ thực tế

SELECT CONVERT_TZ(created_at, 'UTC', 'Asia/Tokyo')
FROM users;

7.5 Tại sao CONVERT_TZ() trả về NULL

Nếu trả về NULL, các nguyên nhân phổ biến bao gồm:

  • Bảng múi giờ của MySQL chưa được tải
  • Tên múi giờ được chỉ định không tồn tại

Ví dụ tải trên Linux:

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

Ghi chú

  • Trong sản xuất, xác nhận quyền hạn và xem có cần khởi động lại không
  • Trong Docker, zoneinfo có thể không được bao gồm tùy thuộc vào image

7.6 Thay đổi múi giờ toàn server

Tệp cấu hình (my.cnf / my.ini):

[mysqld]
default_time_zone = '+00:00'

Xác nhận sau khi khởi động lại:

SELECT @@global.time_zone;

Quan trọng

  • Đối với thay đổi sản xuất, xác nhận phạm vi tác động
  • Cẩn thận với dữ liệu hiện có (đôi khi chỉ thay đổi hiển thị)

Các lỗi phổ biến

  • Lưu trữ thời gian địa phương thay vì UTC, sau đó thêm hỗ trợ quốc tế
  • Chuyển đổi kép giữa ứng dụng và DB
  • Thiết kế với DATETIME mà không xem xét múi giờ
  • Cài đặt TZ khác nhau giữa kiểm thử và sản xuất

Những điểm chính từ phần này

  • Lưu trữ ở UTC và chuyển đổi khi hiển thị.
  • Sử dụng UTC_TIMESTAMP() .
  • Khi sử dụng CONVERT_TZ() , xác nhận bảng TZ.
  • Luôn tính đến sự khác biệt môi trường về múi giờ.

8. Các ví dụ thực tế có thể sử dụng ngay

Dưới đây là các ví dụ SQL cụ thể cho thấy cách sử dụng thời gian hiện tại của MySQL trong phát triển/vận hành thực tế.
Chúng được viết để bạn có thể sao chép và dán trực tiếp.

8.1 Tự động chèn thời gian hiện tại vào logs

Tạo bảng

CREATE TABLE system_logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    level VARCHAR(50),
    message TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Chèn log

INSERT INTO system_logs (level, message)
VALUES ('ERROR', 'Failed to connect to the DB');

Các điểm chính

  • created_at được chèn tự động
  • Không cần truyền timestamp từ ứng dụng
  • Thiết kế thiết yếu cho việc kiểm toán và điều tra sự cố

Những lỗi thường gặp

  • Quản lý thời gian cả trong ứng dụng và DB
  • Không đồng nhất thời gian do múi giờ không thống nhất

8.2 Cập nhật thời gian đăng nhập cuối cùng

Thiết kế bảng

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255),
    last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP
               ON UPDATE CURRENT_TIMESTAMP
);

Cập nhật khi đăng nhập

UPDATE users
SET last_login = NOW()
WHERE id = 1;

Ghi chú

  • ON UPDATE chỉ kích hoạt khi cột thay đổi
  • Một số câu lệnh UPDATE có thể không thay đổi giá trị và do đó không cập nhật

8.3 Đặt thời gian tham chiếu cố định trong các công việc batch

Đối với các quy trình chạy lâu, an toàn hơn khi đặt một thời gian tham chiếu cố định.

SET @base_time = NOW();

SELECT *
FROM orders
WHERE created_at &gt;= @base_time - INTERVAL 1 DAY;

Tại sao

  • Thời gian không thay đổi giữa quá trình
  • Độ nhất quán được duy trì

8.4 Aggregation for today / yesterday / past 7 days

Doanh thu hôm nay

SELECT SUM(amount)
FROM orders
WHERE created_at &gt;= CURDATE()
  AND created_at &lt;  CURDATE() + INTERVAL 1 DAY;

Doanh thu hôm qua

SELECT SUM(amount)
FROM orders
WHERE created_at &gt;= CURDATE() - INTERVAL 1 DAY
  AND created_at &lt;  CURDATE();

7 ngày qua

SELECT COUNT(*)
FROM users
WHERE created_at &gt;= NOW() - INTERVAL 7 DAY;

Quan trọng

  • Không áp dụng hàm lên cột trong mệnh đề WHERE
  • Viết điều kiện sao cho chỉ mục vẫn được sử dụng

8.5 Expiration checks (sessions/tokens)

SELECT *
FROM sessions
WHERE expires_at &gt; NOW();

Xóa các hàng đã hết hạn

DELETE FROM sessions
WHERE expires_at &lt;= NOW();

Những lỗi thường gặp

  • Sử dụng CURDATE() và bỏ qua thời gian
  • Lưu UTC nhưng so sánh với NOW() địa phương

8.6 Get rows expiring within N hours

SELECT *
FROM coupons
WHERE expires_at &lt;= NOW() + INTERVAL 1 HOUR;

Các trường hợp sử dụng:

  • Cảnh báo hết hạn
  • Thông báo hết hạn

Những điều bạn luôn phải nhớ

  • Rõ ràng baseline là “now” hay “today 00:00”
  • Viết các điều kiện sao cho chỉ mục vẫn hiệu quả
  • Quyết định thiết kế múi giờ từ đầu
  • Không trộn NOW()UTC_TIMESTAMP() nếu không có chính sách rõ ràng

Những điểm chính rút ra từ phần này

  • Đối với log/kiểm toán/theo dõi cập nhật, sử dụng CURRENT_TIMESTAMP
  • Đối với tổng hợp, sử dụng mẫu an toàn >= AND <
  • Đối với quản lý phiên, so sánh với NOW()
  • Đặt thời gian baseline để giữ quá trình nhất quán

9. Frequently Asked Questions (FAQ)

Dưới đây là các câu hỏi thường gặp về thời gian hiện tại trong MySQL, đặc biệt là những bẫy thường gặp trong thực tế.

9.1 Sự khác nhau giữa NOW()CURRENT_TIMESTAMP là gì?

Kết luận: ý nghĩa của giá trị trả về là giống nhau.

SELECT NOW(), CURRENT_TIMESTAMP;

Cả hai đều trả về ngày giờ hiện tại.

Sự khác nhau chính là cách sử dụng cú pháp

  • CURRENT_TIMESTAMP có thể được dùng trong DEFAULTON UPDATE
  • NOW() không thể dùng trực tiếp trong DEFAULT

Ghi chú

  • Không phải là sự khác biệt về kiểu dữ liệu
  • Bạn có thể chỉ định độ chính xác bằng các đối số như (6)

9.2 Bạn có nên dùng SYSDATE() không?

SYSDATE() trả về thời gian tại “thời điểm đánh giá”.

SELECT SYSDATE(), SLEEP(2), SYSDATE();

Giá trị có thể thay đổi ngay cả trong cùng một truy vấn.

Khi nào nên dùng

  • Khi bạn cần ghi lại thời điểm thực tế chính xác

Khi nào nên tránh

  • Sao chép
  • Xử lý giao dịch nơi tính nhất quán quan trọng

Trong hầu hết các trường hợp, sử dụng NOW() là đủ.

9.3 Tại sao thời gian lại bị dịch?

Nguyên nhân chính:

  1. Cài đặt múi giờ máy chủ
  2. Cài đặt múi giờ phiên
  3. Chuyển đổi đôi trong ứng dụng
  4. Hành vi chuyển đổi tự động của kiểu TIMESTAMP

Kiểm tra bằng:

SELECT @@global.time_zone, @@session.time_zone;

Giảm thiểu

  • Lưu trữ ở UTC theo mặc định
  • Chỉ chuyển đổi khi hiển thị
  • Đồng nhất chính sách giữa ứng dụng và CSDL

9.4 CONVERT_TZ() returns NULL

Nguyên nhân:

  • Bảng múi giờ chưa được tải
  • Tên múi giờ không chính xác

Cách khắc phục:

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

Hãy đặc biệt cẩn thận trong môi trường Docker.

9.5 Range shifts with BETWEEN

Sai:

WHERE created_at BETWEEN '2025-02-01' AND '2025-02-10';

Đúng:

WHERE created_at &gt;= '2025-02-01 00:00:00'
  AND created_at &lt;  '2025-02-11 00:00:00';

Lý do:

  • Vấn đề thời gian ranh giới cuối
  • Rò rỉ microgiây
  • Hiệu suất chỉ mục

9.6 How do you choose between DATETIME and TIMESTAMP?

  • Hỗ trợ quốc tế / Quản lý UTC → TIMESTAMP
  • Sau năm 2038 hoặc ngày giờ cố định → DATETIME

Quyết định điều này trong giai đoạn thiết kế.

9.7 Microseconds aren’t being stored

Nguyên nhân:

  • Không chỉ định độ chính xác trong định nghĩa cột

Cách khắc phục:

created_at DATETIME(6)

Những điểm chính cần nhớ từ phần này

  • Bắt đầu với NOW()CURRENT_TIMESTAMP
  • Đối với truy vấn phạm vi, sử dụng >= AND <
  • Lưu trữ UTC là mặc định an toàn nhất
  • Đừng quên lựa chọn kiểu dữ liệu và độ chính xác

10. Summary

Bài viết này tổng hợp các cách thực tế để truy xuất, định dạng, tính toán và quản lý thời gian hiện tại trong MySQL.
Cuối cùng, đây là những kiến thức tối thiểu bạn cần biết để an toàn.

10.1 Getting the current time (basics)

  • Truy xuất chung → NOW()
  • DEFAULT bảng / theo dõi cập nhật → CURRENT_TIMESTAMP
  • Chỉ ngày → CURDATE()
  • Chỉ thời gian → CURTIME()
  • UTC → UTC_TIMESTAMP()
  • Độ chính xác cao → NOW(6)

Nguyên tắc chung

  • Nếu không chắc, việc sử dụng NOW() là ổn trong hầu hết các trường hợp.
  • Đối với thiết kế schema, sử dụng CURRENT_TIMESTAMP.

10.2 Range queries: “>= AND <” is the golden rule

Mẫu an toàn:

WHERE created_at &gt;= 'START'
  AND created_at &lt;  'END'

Tại sao:

  • Ngăn ngừa việc bỏ sót các hàng ở ranh giới cuối
  • Hoạt động với microgiây
  • Giữ chỉ mục có thể sử dụng được

Ví dụ sai

WHERE DATE(created_at) = CURDATE();

Không bao bọc cột trong hàm.

10.3 Datetime arithmetic basics

  • Cộng/trừ → INTERVAL
  • Hiệu ngày → DATEDIFF()
  • Hiệu thời gian → TIMESTAMPDIFF()

Luôn nhớ rằng cơ sở của bạn là “now” hay “today 00:00.”

10.4 Time zone design principles

  • Lưu trữ ở UTC
  • Chuyển đổi khi hiển thị
  • Đồng nhất chính sách giữa ứng dụng và CSDL

Kiểm tra bằng:

SELECT @@global.time_zone, @@session.time_zone;

10.5 Table design best practices

created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
           ON UPDATE CURRENT_TIMESTAMP
  • Thiết bị chuẩn cho việc kiểm toán/ghi log/theo dõi cập nhật
  • Nếu cần độ chính xác, chỉ định (6)

4 điểm thực tế quan trọng nhất

  1. Đừng nhầm lẫn sự khác nhau giữa NOW()CURRENT_TIMESTAMP
  2. Đối với truy vấn phạm vi, sử dụng >= AND <
  3. Lưu trữ ở UTC theo mặc định
  4. Quyết định kiểu dữ liệu (DATETIME vs TIMESTAMP) trong giai đoạn thiết kế

Xử lý thời gian hiện tại trong MySQL là nền tảng cho việc ghi log, tổng hợp doanh thu, quản lý xác thực/phiên, công việc batch, và nhiều hơn nữa.
Nếu bạn tuân theo các nguyên tắc trong bài viết này, bạn có thể tránh được nhiều vấn đề phổ biến như trôi thời gian, lỗi ranh giới và giảm hiệu năng.