Tôi nên chọn loại dấu thời gian nào trong cơ sở dữ liệu PostgreSQL?


119

Tôi muốn xác định một phương pháp hay nhất để lưu trữ dấu thời gian trong cơ sở dữ liệu Postgres của tôi trong bối cảnh của một dự án nhiều múi giờ.

tôi có thể

  1. chọn TIMESTAMP WITHOUT TIME ZONEvà nhớ múi giờ nào đã được sử dụng tại thời điểm chèn cho trường này
  2. chọn TIMESTAMP WITHOUT TIME ZONEvà thêm một trường khác sẽ chứa tên của múi giờ đã được sử dụng tại thời điểm chèn
  3. chọn TIMESTAMP WITH TIME ZONEvà chèn dấu thời gian cho phù hợp

Tôi có một chút ưu tiên cho tùy chọn 3 (dấu thời gian với múi giờ) nhưng muốn có một ý kiến ​​giáo dục về vấn đề này.

Câu trả lời:


142

Trước hết, khả năng xử lý thời gian và số học của PostgreSQL rất tuyệt vời và Tùy chọn 3 là tốt trong trường hợp chung. Tuy nhiên, đây là một cái nhìn chưa đầy đủ về thời gian và múi giờ và có thể được bổ sung:

  1. Lưu tên múi giờ của người dùng dưới dạng tùy chọn của người dùng (ví dụ: America/Los_Angeleskhông phải -0700).
  2. Yêu cầu dữ liệu sự kiện / thời gian của người dùng được gửi cục bộ tới hệ quy chiếu của họ (rất có thể là phần bù từ UTC, chẳng hạn như -0700).
  3. Trong ứng dụng, chuyển đổi thời gian thành UTCvà được lưu trữ bằng cách sử dụng một TIMESTAMP WITH TIME ZONEcột.
  4. Trả lại yêu cầu thời gian cục bộ cho múi giờ của người dùng (tức là chuyển đổi từ UTCsang America/Los_Angeles).
  5. Đặt cơ sở dữ liệu của bạn timezonethành UTC.

Tùy chọn này không phải lúc nào cũng hoạt động vì có thể khó lấy múi giờ của người dùng và do đó, lời khuyên phòng ngừa là sử dụng TIMESTAMP WITH TIME ZONEcho các ứng dụng nhẹ. Điều đó nói rằng, hãy để tôi giải thích một số khía cạnh cơ bản của Tùy chọn 4 này chi tiết hơn.

Giống như Phương án 3, lý do WITH TIME ZONElà vì thời điểm mà điều gì đó đã xảy ra là một thời điểm tuyệt đối về thời gian. WITHOUT TIME ZONEmang lại múi giờ tương đối . Đừng bao giờ, đừng bao giờ kết hợp TIMESTAMP tuyệt đối và tương đối.

Từ quan điểm có lập trình và nhất quán, hãy đảm bảo tất cả các tính toán được thực hiện bằng cách sử dụng UTC làm múi giờ. Đây không phải là yêu cầu của PostgreSQL, nhưng nó giúp ích khi tích hợp với các ngôn ngữ hoặc môi trường lập trình khác. Đặt một CHECKtrên cột để đảm bảo ghi vào cột tem thời gian có độ lệch múi giờ 0là một vị trí phòng thủ ngăn chặn một số loại lỗi (ví dụ: một tập lệnh kết xuất dữ liệu vào một tệp và thứ gì đó khác sắp xếp dữ liệu thời gian bằng cách sử dụng sắp xếp từ vựng). Một lần nữa, PostgreSQL không cần điều này để thực hiện các phép tính ngày một cách chính xác hoặc chuyển đổi giữa các múi giờ (tức là PostgreSQL rất thành thạo trong việc chuyển đổi thời gian giữa hai múi giờ tùy ý bất kỳ). Để đảm bảo dữ liệu đi vào cơ sở dữ liệu được lưu trữ với độ lệch bằng 0:

CREATE TABLE my_tbl (
  my_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  CHECK(EXTRACT(TIMEZONE FROM my_timestamp) = '0')
);
test=> SET timezone = 'America/Los_Angeles';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
ERROR:  new row for relation "my_tbl" violates check constraint "my_tbl_my_timestamp_check"
test=> SET timezone = 'UTC';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
INSERT 0 1

Nó không hoàn hảo 100%, nhưng nó cung cấp một biện pháp chống giật chân đủ mạnh để đảm bảo dữ liệu đã được chuyển đổi sang UTC. Có rất nhiều ý kiến ​​về cách làm điều này, nhưng đây có vẻ là cách tốt nhất trong thực tế từ kinh nghiệm của tôi.

Những lời chỉ trích về việc xử lý múi giờ cơ sở dữ liệu phần lớn là có lý do (có rất nhiều cơ sở dữ liệu xử lý điều này với sự kém cỏi lớn), tuy nhiên việc xử lý dấu thời gian và múi giờ của PostgreSQL khá tuyệt vời (mặc dù có một vài "tính năng" ở đây và ở đó). Ví dụ, một trong những tính năng như vậy:

-- Make sure we're all working off of the same local time zone
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 15:47:58.138995-07
(1 row)

test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:02.235541
(1 row)

Lưu ý rằng AT TIME ZONE 'UTC'phân chia thông tin múi giờ và tạo một tương đối TIMESTAMP WITHOUT TIME ZONEbằng cách sử dụng hệ quy chiếu của mục tiêu ( UTC).

Khi chuyển đổi từ không hoàn TIMESTAMP WITHOUT TIME ZONEthành sang a TIMESTAMP WITH TIME ZONE, múi giờ bị thiếu sẽ được kế thừa từ kết nối của bạn:

test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
        -7
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
        -7
(1 row)

-- Now change to UTC    
test=> SET timezone = 'UTC';
SET
-- Create an absolute time with timezone offset:
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 22:48:40.540119+00
(1 row)

-- Creates a relative time in a given frame of reference (i.e. no offset)
test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:49.444446
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
         0
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
         0
(1 row)

Điểm mấu chốt:

  • lưu trữ múi giờ của người dùng dưới dạng nhãn được đặt tên (ví dụ America/Los_Angeles) và không phải là phần bù từ UTC (ví dụ -0700)
  • sử dụng UTC cho mọi thứ trừ khi có lý do thuyết phục để lưu trữ phần bù khác 0
  • coi tất cả thời gian UTC khác 0 là lỗi đầu vào
  • không bao giờ trộn và kết hợp các dấu thời gian tương đối và tuyệt đối
  • cũng sử dụng UTClàm timezonecơ sở dữ liệu nếu có thể

Lưu ý về ngôn ngữ lập trình ngẫu nhiên: datetimeKiểu dữ liệu của Python rất tốt trong việc duy trì sự khác biệt giữa thời gian tuyệt đối và thời gian tương đối (mặc dù lúc đầu khá khó chịu cho đến khi bạn bổ sung nó bằng một thư viện như PyTZ ).


BIÊN TẬP

Hãy để tôi giải thích sự khác biệt giữa tương đối và tuyệt đối hơn một chút.

Thời gian tuyệt đối được sử dụng để ghi lại một sự kiện. Ví dụ: "Người dùng 123 đã đăng nhập" hoặc "lễ tốt nghiệp bắt đầu lúc 2 giờ chiều theo giờ Thái Bình Dương, 2011-05-28." Bất kể múi giờ địa phương của bạn là gì, nếu bạn có thể dịch chuyển đến nơi xảy ra sự kiện, bạn có thể chứng kiến ​​sự kiện đang diễn ra. Hầu hết dữ liệu thời gian trong cơ sở dữ liệu là tuyệt đối (và do đó TIMESTAMP WITH TIME ZONE, lý tưởng là phải có độ lệch +0 và nhãn văn bản đại diện cho các quy tắc quản lý múi giờ cụ thể - không phải là độ lệch).

Một sự kiện tương đối sẽ là ghi lại hoặc lên lịch thời gian của một thứ gì đó từ góc độ múi giờ chưa được xác định. Ví dụ: "cửa hàng kinh doanh của chúng tôi mở lúc 8 giờ sáng và đóng lúc 9 giờ tối", "chúng ta hãy gặp nhau lúc 7 giờ sáng vào thứ Hai hàng tuần cho một cuộc họp ăn sáng hàng tuần" hoặc "mỗi Halloween lúc 8 giờ tối". Nói chung, thời gian tương đối được sử dụng trong một mẫu hoặc nhà máy cho các sự kiện và thời gian tuyệt đối được sử dụng cho hầu hết mọi thứ khác. Có một ngoại lệ hiếm hoi đáng chỉ ra sẽ minh họa giá trị của thời gian tương đối. Đối với các sự kiện tương lai đủ xa trong tương lai, nơi có thể không chắc chắn về thời gian tuyệt đối mà tại đó điều gì đó có thể xảy ra, hãy sử dụng dấu thời gian tương đối. Đây là một ví dụ thực tế:

Giả sử đó là năm 2004 và bạn cần lên lịch giao hàng vào lúc 1 giờ chiều ngày 31 tháng 10 năm 2008 ở Bờ Tây Hoa Kỳ (tức là America/Los_Angeles/ PST8PDT). Nếu bạn lưu trữ nó bằng cách sử dụng thời gian tuyệt đối ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE, giao hàng sẽ hiển thị lúc 2 giờ chiều vì Chính phủ Hoa Kỳ đã thông qua Đạo luật Chính sách Năng lượng năm 2005 đã thay đổi các quy tắc điều chỉnh thời gian tiết kiệm ánh sáng ban ngày. Vào năm 2004 khi giao hàng được lên lịch, ngày 10-31-2008sẽ là Giờ chuẩn Thái Bình Dương ( +8000), nhưng bắt đầu từ năm 2005+ cơ sở dữ liệu múi giờ được công nhận là giờ 10-31-2008Tiết kiệm ánh sáng ban ngày Thái Bình Dương (+0700). Lưu trữ một dấu thời gian tương đối với múi giờ sẽ dẫn đến lịch trình phân phối chính xác vì dấu thời gian tương đối miễn nhiễm với sự giả mạo thiếu thông tin của Quốc hội. Trường hợp giới hạn giữa việc sử dụng thời gian tương đối và thời gian tuyệt đối để lập lịch trình là một đường mờ, nhưng quy tắc chung của tôi là lập lịch cho bất kỳ thứ gì trong tương lai xa hơn 3-6 tháng nên sử dụng dấu thời gian tương đối (đã lên lịch = tuyệt đối so với kế hoạch = quan hệ ???).

Loại thời gian tương đối khác / cuối cùng là INTERVAL. Ví dụ: "phiên sẽ hết 20 phút sau khi người dùng đăng nhập". An INTERVALcó thể được sử dụng chính xác với dấu thời gian tuyệt đối ( TIMESTAMP WITH TIME ZONE) hoặc dấu thời gian tương đối ( TIMESTAMP WITHOUT TIME ZONE). Cũng đúng như nhau khi nói, "phiên người dùng hết hạn sau 20 phút sau khi đăng nhập thành công (login_utc + session_duration)" hoặc "cuộc họp bữa sáng buổi sáng của chúng tôi chỉ có thể kéo dài 60 phút (recring_start_time + meeting_length)".

Bit cuối cùng của sự nhầm lẫn: DATE, TIME, TIME WITHOUT TIME ZONETIME WITH TIME ZONElà tất cả các loại dữ liệu tương đối. Ví dụ: '2011-05-28'::DATEđại diện cho một ngày tương đối vì bạn không có thông tin múi giờ có thể được sử dụng để xác định nửa đêm. Tương tự, '23:23:59'::TIMElà tương đối vì bạn không biết múi giờ hoặc DATEthời gian được biểu thị. Ngay cả với '23:59:59-07'::TIME WITH TIME ZONE, bạn không biết những gì DATEsẽ là. Và cuối cùng, DATEvới múi giờ trên thực tế không phải là a DATE, nó là TIMESTAMP WITH TIME ZONE:

test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 07:00:00
(1 row)

test=> SET timezone = 'UTC';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 00:00:00
(1 row)

Đưa ngày và múi giờ vào cơ sở dữ liệu là một điều tốt, nhưng rất dễ nhận được kết quả không chính xác một cách tinh vi. Cần nỗ lực tối thiểu để lưu trữ thông tin thời gian một cách chính xác và đầy đủ, tuy nhiên điều đó không có nghĩa là luôn cần nỗ lực thêm.


2
Nếu bạn cho postgresql biết chính xác múi giờ chính xác mà dấu thời gian của người dùng đang ở, postgresql sẽ thực hiện công việc nặng nhọc đằng sau hậu trường. Tự mình chuyển đổi nó chỉ là một rắc rối.
Seth Robertson

1
@Sean - với ràng buộc kiểm tra của bạn, làm cách nào bạn có thể chèn dấu thời gian mà không có set timezone to 'UTC'? Bạn biết rằng tất cả các ngày nhận biết múi giờ đều được lưu trữ nội bộ trong UTC ?

2
Mục đích của việc kiểm tra là đảm bảo rằng dữ liệu được lưu trữ với độ lệch 0 so với UTC. Việc sắp xếp và truy xuất thông tin cũng như so sánh thời gian với các hiệu số khác 0 rất dễ xảy ra lỗi. Bằng cách thực thi chênh lệch UTC bằng 0, bạn có thể tương tác một cách nhất quán với dữ liệu từ một góc độ duy nhất theo cách gần như không có rủi ro, hoạt động có thể dự đoán được trong mọi tình huống. Nếu việc đánh dấu thời gian hỗ trợ biểu thị bằng văn bản của múi giờ là thực tế, thì suy nghĩ của tôi về chủ đề này sẽ khác. : ~]
Sean

6
@Sean: Nhưng, như Jack chỉ ra, tất cả các dấu thời gian nhận biết múi giờ về cơ bản được lưu trữ nội bộ theo UTC và được chuyển đổi sang múi giờ địa phương của bạn khi được sử dụng; một cách hiệu quả, trích xuất (múi giờ từ ...) sau đó sẽ luôn trả về bất kể múi giờ cục bộ của kết nối là gì: nó không liên quan đến cách dấu thời gian được "lưu trữ". Nói cách khác, múi giờ hoàn toàn không phải là một phần của kiểu và không thể được lưu trữ: "với múi giờ" chỉ là một thuộc tính về cách dữ liệu sẽ được chuyển đổi khi tương tác với các kiểu khác. Do đó, dữ liệu không có biểu thị múi giờ nào, ở dạng văn bản hay dạng khác.
Jay Freeman -saurik-

@ JayFreeman-saurik-: bạn hoàn toàn chính xác. '' CHECK () '' ở đó như một biện pháp chống chân để bảo vệ khỏi mã có thể bị lừa. Đảm bảo rằng dữ liệu là UTC khi ghi cung cấp một sự đảm bảo khiêm tốn rằng mã đã được xem xét kỹ lưỡng hoặc môi trường thực thi được thiết lập chính xác.
Sean

59

Câu trả lời của Sean quá phức tạp và gây hiểu lầm.

Thực tế là cả "VÙNG THỜI GIAN" và "VÙNG KHÔNG GIỜ" đều lưu trữ giá trị dưới dạng dấu thời gian UTC tuyệt đối giống unix. Sự khác biệt tất cả là ở cách hiển thị dấu thời gian. Khi "VỚI múi giờ" thì giá trị được hiển thị là giá trị được lưu trữ UTC được dịch sang vùng của người dùng. Khi "KHÔNG CÓ Múi giờ", giá trị được lưu trữ theo giờ UTC sẽ bị xoắn lại để hiển thị cùng một mặt đồng hồ cho dù người dùng đã đặt múi giờ nào ".

Tình huống duy nhất có thể sử dụng "KHÔNG CÓ múi giờ" là khi mệnh giá đồng hồ có thể áp dụng bất kể múi giờ thực tế. Ví dụ: khi dấu thời gian cho biết thời điểm các phòng bỏ phiếu có thể đóng (tức là đóng cửa lúc 20:00 bất kể múi giờ của một người là gì).

Sử dụng lựa chọn 3. Luôn sử dụng "VỚI múi giờ" trừ khi có lý do rất cụ thể để không.


10
David E. Wheeler, một chuyên gia lớn của Postgres, sẽ đồng ý với đánh giá của bạn theo bài đăng của anh ấy, Luôn sử dụng TIMESTAMP VỚI KHU VỰC THỜI GIAN .
Basil Bourque

2
Điều gì sẽ xảy ra nếu bạn yêu cầu trình duyệt chuyển đổi tem thời gian UTC sang múi giờ địa phương? Vì vậy, db sẽ không bao giờ thực hiện chuyển đổi và chỉ chứa UTC. "KHÔNG CÓ Múi giờ" có được chấp nhận không?
dman

5

Sở thích của tôi là hướng tới tùy chọn 3, vì Postgres sau đó có thể thực hiện công việc tính toán lại dấu thời gian liên quan đến múi giờ cho bạn, trong khi với hai phương án còn lại, bạn sẽ phải tự mình làm điều đó. Chi phí lưu trữ bổ sung của việc lưu trữ dấu thời gian với múi giờ thực sự không đáng kể trừ khi bạn đang nói đến hàng triệu bản ghi, trong trường hợp đó, bạn có thể đã có các yêu cầu lưu trữ khá lớn.


19
Sai. Không có chi phí… Postgres không lưu trữ múi giờ (nhân tiện, 'offset' là thuật ngữ chính xác, không phải múi giờ). Các TIMESTAMP WITH TIME ZONEtên được gây hiểu lầm. Nó thực sự có nghĩa là "chú ý đến bất kỳ khoảng chênh lệch nào được chỉ định khi chèn / cập nhật và sử dụng khoảng chênh lệch đó để điều chỉnh ngày-giờ thành UTC". Các TIMESTAMP WITHOUT TIME ZONEphương tiện tên "bỏ qua bất kỳ bù đắp có thể có mặt trong chèn / cập nhật, hãy xem xét ngày và thời gian các phần như là trong UTC mà không cần điều chỉnh". Đọc kỹ tài liệu .
Basil Bourque

1
@BasilBourque cảm ơn bạn về thông tin này. Cực kỳ hữu ích. Đối với những người khác đọc dòng này từ tài liệu cho biết, "Theo nghĩa đen đã được xác định là dấu thời gian không có múi giờ, PostgreSQL sẽ im lặng bỏ qua bất kỳ chỉ báo múi giờ nào. Nghĩa là, giá trị kết quả được lấy từ các trường ngày / giờ trong giá trị đầu vào và không được điều chỉnh theo múi giờ. "
Aidan Rosswood
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.