PHP & mySQL: ปี 2038 Bug: มันคืออะไร? ต้องแก้ยังไง?


116

ฉันกำลังคิดที่จะใช้ TIMESTAMP เพื่อเก็บวันที่ + เวลา แต่ฉันอ่านพบว่ามีข้อ จำกัด ของปี 2038 อยู่ แทนที่จะถามคำถามจำนวนมากฉันต้องการแยกคำถามออกเป็นส่วนเล็ก ๆ เพื่อให้ผู้ใช้มือใหม่เข้าใจได้ง่ายเช่นกัน ดังนั้นคำถามของฉัน:

  1. ปัญหาปี 2581 คืออะไร?
  2. เหตุใดจึงเกิดขึ้นและจะเกิดอะไรขึ้นเมื่อเกิดขึ้น?
  3. เราจะแก้อย่างไร
  4. มีทางเลือกอื่นที่เป็นไปได้ในการใช้งานซึ่งไม่ก่อให้เกิดปัญหาในลักษณะเดียวกันหรือไม่?
  5. เราจะทำอย่างไรกับแอปพลิเคชันที่มีอยู่ซึ่งใช้ TIMESTAMP เพื่อหลีกเลี่ยงปัญหาที่เรียกว่าเมื่อมันเกิดขึ้นจริงๆ

ขอบคุณล่วงหน้า.


1
นั่นคือ 28 ปีที่จะไป คุณยังใช้เทคโนโลยีที่เกี่ยวข้องกับคอมพิวเตอร์ตั้งแต่ปี 1982 อยู่หรือไม่? ไม่แน่ ดังนั้นไม่ต้องกังวลเพราะภายในปี 2581 จะไม่มีปัญหาอีกต่อไป
Gordon

24
กอร์ดอนฉันสร้างแอปพลิเคชันที่ใช้ในการพยากรณ์ ฉันประหยัดเงินได้เท่าไหร่ในแต่ละเดือนและสามารถประมาณได้ว่าเมื่อไหร่ฉันจะเป็นเศรษฐี ในกรณีของฉัน 28 ปีนั้นไม่มากและฉันแน่ใจว่าฉันไม่ใช่คนเดียวที่มีปัญหานี้ในตอนนี้ (ฉันแก้ไขได้โดยใช้ตัวเลข 64 บิตเพื่อแสดงเวลาประทับ)
Emil Vikström

2
@Emil ด้วยการใช้จำนวนเต็ม 64 บิตในตอนนี้คุณพบว่าตัวเองเป็นวิธีง่ายๆในการแก้ปัญหาที่เป็นรูปธรรม ไม่สามารถใช้ได้กับทุกคน (หรือจำเป็นสำหรับ) แต่ทำงานให้กับผู้ใช้ของคุณ ประเด็นคือถ้า OP ไม่มีปัญหาที่เป็นรูปธรรมเช่นการคาดการณ์นี่อาจเป็นหัวข้อที่น่าสนใจ แต่เขาไม่ควรกังวลอะไรเพราะการแก้ปัญหานี้ในระดับทั่วไปไม่ใช่ปัญหา PHP (คำนึงถึงแท็ก) แค่ 2c ของฉัน
Gordon

1
ภายในปี 2038 การแยกวิเคราะห์สตริง "YYYY-MM-DD HH: MM: SS: mmmm ... " จะเป็นการดำเนินการที่ถูกที่สุดเท่าที่คุณจะฝันถึง ภายในปี 2038 32 บิตจะล้าสมัย ฉันสงสัยว่าการประทับเวลา Unix อย่างที่เรารู้ว่าจะมีอยู่จริงและถ้าเป็นเช่นนั้นระบบ 256 บิตของเราจะจัดการวันที่ที่อยู่ไกลเกินกว่าอายุที่ระบบ 4096 บิตจะถูกแจกในมื้ออาหารที่มีความสุข
Super Cat

8
กอร์ดอน 9 ปีต่อมาตอนนี้ TIMESTAMPS ยังคงใช้อยู่ เรายังคงใช้เทคโนโลยีจาก 28 ปีที่แล้ว เรียกว่าเว็บทั่วโลก
sfscs

คำตอบ:


149

ฉันได้ทำเครื่องหมายนี้เป็นวิกิชุมชนดังนั้นอย่าลังเลที่จะแก้ไขได้ตามอัธยาศัย

ปัญหาปี 2581 คืออะไร?

"ปัญหาปี 2038 (หรือที่เรียกว่า Unix Millennium Bug, Y2K38 โดยการเปรียบเทียบกับปัญหา Y2K) อาจทำให้ซอฟต์แวร์คอมพิวเตอร์บางตัวล้มเหลวก่อนหรือในปี 2038 ปัญหาดังกล่าวส่งผลกระทบต่อซอฟต์แวร์และระบบทั้งหมดที่จัดเก็บเวลาของระบบเป็นเซ็น 32 - จำนวนเต็มบิตและตีความตัวเลขนี้เป็นจำนวนวินาทีตั้งแต่ 00:00:00 UTC ของวันที่ 1 มกราคม 1970 "


เหตุใดจึงเกิดขึ้นและจะเกิดอะไรขึ้นเมื่อเกิดขึ้น?

ครั้งเกิน 03:14:07 UTC ของวันอังคารที่ 19 มกราคม 2038จะ 'ล้อมรอบ' และถูกจัดเก็บไว้ภายในเป็นจำนวนลบซึ่งระบบเหล่านี้จะตีความเป็นเวลาในวันที่ 13 ธันวาคม 1901 แทนที่จะเป็นปี 2038 เนื่องจาก ความจริงที่ว่าจำนวนวินาทีนับตั้งแต่ยุค UNIX (1 มกราคม 1970 00:00:00 GMT) จะเกินค่าสูงสุดของคอมพิวเตอร์สำหรับจำนวนเต็มที่มีลายเซ็น 32 บิต


เราจะแก้อย่างไร

  • ใช้ชนิดข้อมูลแบบยาว (64 บิตก็เพียงพอแล้ว)
  • สำหรับ MySQL (หรือ MariaDB) หากคุณไม่ต้องการข้อมูลเวลาให้พิจารณาใช้DATEประเภทคอลัมน์ หากคุณต้องการความแม่นยำสูงใช้มากกว่าDATETIME TIMESTAMPระวังว่าDATETIMEคอลัมน์จะไม่เก็บข้อมูลเกี่ยวกับเขตเวลาดังนั้นแอปพลิเคชันของคุณจะต้องทราบว่ามีการใช้เขตเวลาใด
  • วิธีแก้ปัญหาที่เป็นไปได้อื่น ๆ ที่อธิบายไว้ใน Wikipedia
  • รอให้ MySQL devs แก้ไขข้อบกพร่องนี้ที่รายงานเมื่อทศวรรษที่แล้ว

มีทางเลือกอื่นที่เป็นไปได้ในการใช้งานซึ่งไม่ก่อให้เกิดปัญหาในลักษณะเดียวกันหรือไม่?

ลองใช้ทุกที่ที่เป็นไปได้เพื่อใช้ประเภทขนาดใหญ่สำหรับการจัดเก็บวันที่ในฐานข้อมูล: 64 บิตเพียงพอแล้ว - ประเภทยาวแบบยาวใน GNU C และ POSIX / SuS หรือsprintf('%u'...)ใน PHP หรือส่วนขยาย BCmath


มีกรณีใดบ้างที่อาจทำลายการใช้งานแม้ว่าเราจะยังไม่ถึงปี 2581

ดังนั้น MySQL DATETIMEจึงมีช่วง 1,000-9999 แต่ TIMESTAMP มีช่วง 1970-2038 เท่านั้น หากระบบของคุณจัดเก็บวันเกิดวันที่ส่งต่อในอนาคต (เช่นการจำนอง 30 ปี) หรือที่ใกล้เคียงกันแสดงว่าคุณกำลังประสบปัญหานี้อยู่แล้ว อีกครั้งอย่าใช้ TIMESTAMP ถ้าจะเป็นปัญหา


เราจะทำอย่างไรกับแอปพลิเคชันที่มีอยู่ซึ่งใช้ TIMESTAMP เพื่อหลีกเลี่ยงปัญหาที่เรียกว่าเมื่อมันเกิดขึ้นจริงๆ

แอปพลิเคชั่น PHP ไม่กี่ตัวที่จะยังคงใช้งานได้ในปี 2038 แม้ว่าจะยากที่จะมองเห็นได้เนื่องจากเว็บยังแทบจะไม่เป็นแพลตฟอร์มเดิม

นี่คือกระบวนการในการเปลี่ยนแปลงคอลัมน์ตารางฐานข้อมูลในการแปลงไปTIMESTAMP DATETIMEเริ่มต้นด้วยการสร้างคอลัมน์ชั่วคราว:

# rename the old TIMESTAMP field
ALTER TABLE `myTable` CHANGE `myTimestamp` `temp_myTimestamp` int(11) NOT NULL;

# create a new DATETIME column of the same name as your old column
ALTER TABLE `myTable` ADD `myTimestamp` DATETIME NOT NULL;

# update all rows by populating your new DATETIME field
UPDATE `myTable` SET `myTimestamp` = FROM_UNIXTIME(temp_myTimestamp);

# remove the temporary column
ALTER TABLE `myTable` DROP `temp_myTimestamp`

ทรัพยากร


2
น่ากลัว สำหรับฉันคำตอบของคุณนั้นสมบูรณ์ที่สุด ขอบคุณมาก.
Devner

7
ใน MySQL หากเวอร์ชันในอนาคตเปลี่ยนประเภทข้อมูลการจัดเก็บข้อมูลพื้นฐานของ TIMESTAMP เป็น 64 บิตก็ไม่จำเป็นต้องเปลี่ยนรหัสของคุณเป็น DATETIME ใช่ไหม ฉันไม่คิดว่าการกีดกันใครจากการใช้ TIMESTAMP เป็นสิ่งที่ถูกต้องเพราะประเภทข้อมูลนั้นมีจุดประสงค์
pixelfreak

1
ฟิลด์BIGINT ที่ลงนามของ MySQL ดูเหมือนว่าจะใช้งานได้ดีสำหรับการจัดเก็บการประทับเวลาและฉันได้ทำการทดสอบในท้องถิ่นบน MySQL 5.5 ซึ่งยืนยันว่าใช้งานได้ เห็นได้ชัดว่าการใช้ลายเซ็นจะดีกว่าไม่ได้ลงนามเนื่องจากคุณสามารถแสดงวันที่ในอดีตได้เช่นกัน มีเหตุผลใดที่จะไม่ใช้ BIGINT ในการประทับเวลา?
zuallauz

สิ่งหนึ่งที่ควรทราบ MySQL มีเพียงการตั้งค่าอัตโนมัติเป็นวันที่ / เวลาปัจจุบัน (CURRENT_TIMESTAMP) สำหรับช่องประทับเวลา หวังว่าฟังก์ชันนี้จะพอร์ตไปยังประเภทวันที่ทั้งหมดในที่สุด
Ray

5
เป็นเรื่องไร้สาระอย่างยิ่งที่ MySQL (และ MariaDB) ไม่ใช้จำนวนเต็ม 64 บิตในการจัดเก็บการประทับเวลาบนระบบ 64 บิต การใช้ DATETIME ไม่ใช่วิธีแก้ปัญหาที่เพียงพอเนื่องจากเราไม่รู้เกี่ยวกับเขตเวลา มีรายงานย้อนกลับไปในปี 2548แต่ยังไม่มีการแก้ไข
Mike

14

เมื่อใช้ UNIX Timestamps เพื่อจัดเก็บวันที่คุณกำลังใช้จำนวนเต็ม 32 บิตซึ่งจะนับจำนวนวินาทีตั้งแต่ปี 1970-01-01 ดูเวลา Unix

ตัวเลข 32 บิตนั้นจะล้นในปี 2038 นั่นคือปัญหาปี 2038


ในการแก้ปัญหานั้นคุณต้องไม่ใช้การประทับเวลา UNIX 32 บิตในการจัดเก็บวันที่ของคุณ - ซึ่งหมายความว่าเมื่อใช้ MySQL คุณไม่ควรใช้TIMESTAMPแต่DATETIME(ดู10.3.1 ประเภท DATETIME, DATE และ TIMESTAMP ):

DATETIMEชนิดถูกนำมาใช้เมื่อคุณต้องการค่าที่มีวันที่และเวลาทั้งข้อมูล ช่วงที่รองรับคือ'1000-01-01 00:00:00'ถึง '9999-12-31 23:59:59'ในการ

TIMESTAMPชนิดข้อมูลมีช่วงของ'1970-01-01 00:00:01'เวลา UTC เพื่อ '2038-01-19 03:14:07'UTC


(อาจ)สิ่งที่ดีที่สุดที่คุณสามารถทำได้เพื่อการประยุกต์ใช้ของคุณเพื่อหลีกเลี่ยง / แก้ไขว่าปัญหาคือการไม่ใช้TIMESTAMPแต่DATETIMEสำหรับคอลัมน์ที่ต้องมีวันที่ไม่ได้อยู่ระหว่าง 1970 และ 2038

อย่างไรก็ตามข้อสังเกตเล็ก ๆ อย่างหนึ่ง: มีความเป็นไปได้สูงมาก(ในทางสถิติ)ที่ใบสมัครของคุณจะถูกเขียนซ้ำสองสามครั้งก่อนปี 2038 ^^ ดังนั้นถ้าคุณไม่จำเป็นต้องจัดการกับวันที่ในอนาคต คุณจะไม่ต้องดูแลปัญหานั้นด้วยแอปพลิเคชันเวอร์ชันปัจจุบันของคุณ ...


1
+1 สำหรับข้อมูล ความพยายามในที่นี้คือการปรับแนวทางปฏิบัติที่ดีที่สุดตั้งแต่เริ่มต้นเพื่อที่จะได้ไม่ต้องกังวลกับปัญหาในภายหลัง ดังนั้นแม้ว่าตอนนี้ในปี 2038 อาจฟังดูไม่เป็นปัญหา แต่ฉันแค่อยากทำตามแนวทางปฏิบัติที่ดีที่สุดและปฏิบัติตามสำหรับแต่ละแอปพลิเคชันที่ฉันสร้างขึ้นตั้งแต่เริ่มต้น หวังว่าจะสมเหตุสมผล
Devner

ใช่มันสมเหตุสมผลฉันเห็นประเด็นของคุณ แต่ "แนวทางปฏิบัติที่ดีที่สุด" อาจหมายถึง "สิ่งที่ตอบโจทย์ความต้องการ" ได้เช่นกัน - หากคุณทราบว่าการประทับเวลาเพียงพอก็ไม่จำเป็นต้องใช้วันที่และเวลา (เช่นผู้ที่ต้องการหน่วยความจำเพิ่มขึ้นในการจัดเก็บเป็นต้นและอาจมีความสำคัญหาก คุณมีบันทึกเป็นล้าน) ;; ฉันมีแอปพลิเคชันบางตัวที่ฉันใช้การประทับเวลาสำหรับบางคอลัมน์ (คอลัมน์ที่มีวันที่ปัจจุบันเป็นต้น) และวันที่และเวลาสำหรับบางคอลัมน์ (เช่นคอลัมน์ที่มีวันที่ในอดีต / ในอนาคตเป็นต้น)
Pascal MARTIN

ฉันเห็นว่าขั้นตอนของคุณเหมาะสมและใช้ได้ดีโดยเฉพาะอย่างยิ่งเมื่อเกี่ยวข้องกับพื้นที่จัดเก็บข้อมูล ถ้าเป็นอย่างนั้นฉันขอถามคุณได้ไหมว่าโดยทั่วไปแล้วคุณใช้โครงสร้างและความยาวอะไรสำหรับคอลัมน์ TIMESTAMP ในแอปพลิเคชันของคุณ ขอบคุณ
Devner

เมื่อฉันต้องการใช้ TIMESTAMP ใช้สำหรับ / เนื่องจากข้อมูล "วันที่ / เวลา" ของฉันอยู่ระหว่างปี 1970 ถึง 2038 ดังนั้นฉันจึงใช้ประเภทข้อมูล MySQL TIMESTAMP
Pascal MARTIN

ขอบคุณสำหรับข้อมูล. จากคำตอบของคุณฉันเข้าใจว่าเพียงพอแล้วที่จะประกาศคอลัมน์เป็น TIMESTAMP และเราไม่จำเป็นต้องระบุความยาวใด ๆ (ไม่เหมือนกับและ int หรือ var ที่เราระบุความยาว) ฉันถูกไหม?
Devner

7

การค้นหาอย่างรวดเร็วบน Google จะช่วยแก้ปัญหาในปี 2038

  1. ปัญหาปี 2038 (หรือที่เรียกว่า Unix Millennium Bug, Y2K38 โดยเปรียบเทียบกับปัญหา Y2K) อาจทำให้ซอฟต์แวร์คอมพิวเตอร์บางตัวล้มเหลวก่อนหรือในปี 2038
  2. ปัญหานี้ส่งผลกระทบต่อซอฟต์แวร์และระบบทั้งหมดที่จัดเก็บเวลาของระบบเป็นจำนวนเต็ม 32 บิตที่ลงชื่อและตีความตัวเลขนี้เป็นจำนวนวินาทีนับตั้งแต่ 00:00:00 UTC ของวันที่ 1 มกราคม 1970 เวลาล่าสุดที่สามารถแสดงด้วยวิธีนี้ คือ 03:14:07 UTC ของวันอังคารที่ 19 มกราคม 2038 เวลาที่อยู่นอกเหนือจากช่วงเวลานี้จะ "พันรอบ" และถูกจัดเก็บไว้ภายในเป็นจำนวนลบซึ่งระบบเหล่านี้จะตีความเป็นวันที่ในปี 1901 แทนที่จะเป็นปี 2038
  3. ไม่มีวิธีแก้ไขที่ง่ายสำหรับปัญหานี้สำหรับชุด CPU / OS ที่มีอยู่ระบบไฟล์ที่มีอยู่หรือรูปแบบข้อมูลไบนารีที่มีอยู่

2

http://en.wikipedia.org/wiki/Year_2038_problemมีรายละเอียดส่วนใหญ่

สรุป:

1) + 2) ปัญหาคือระบบจำนวนมากจัดเก็บข้อมูลวันที่เป็น int ที่ลงชื่อ 32 บิตเท่ากับจำนวนวินาทีตั้งแต่ 1/1/1970 วันที่ล่าสุดที่สามารถจัดเก็บได้ในลักษณะนี้คือ 03:14:07 UTC ของวันอังคารที่ 19 มกราคม 2038 เมื่อเกิดเหตุการณ์นี้ int จะ "ล้อมรอบ" และเก็บไว้เป็นจำนวนลบซึ่งจะตีความเป็นวันที่ในปี 1901 สิ่งที่จะเกิดขึ้นนั้นแตกต่างกันไปในแต่ละระบบ แต่เพียงพอที่จะบอกว่ามันอาจจะไม่ดีสำหรับพวกเขา!

สำหรับระบบที่จัดเก็บเฉพาะวันที่ในอดีตฉันเดาว่าคุณไม่จำเป็นต้องกังวลไปสักพัก! ปัญหาหลักคือระบบที่ทำงานกับวันที่ในอนาคต หากระบบของคุณต้องทำงานร่วมกับวันที่ 28 ปีในอนาคตคุณควรเริ่มกังวลตอนนี้!

3) ใช้รูปแบบวันที่อื่นที่มีให้ใช้งานหรือย้ายไปใช้ระบบ 64 บิตและใช้ 64 บิต หรือสำหรับฐานข้อมูลให้ใช้รูปแบบการประทับเวลาอื่น (เช่นสำหรับ MySQL ใช้ DATETIME)

4) ดู 3!

5) ดู 4 !!! ;)


คะแนนของคุณ 4 และ 5 ทำให้ฉันนึกถึง 'Call by Reference' นั่นทำให้รอยยิ้มบนใบหน้าของฉัน ขอบคุณและ +1 สำหรับสิ่งเดียวกัน
Devner

1

Bros หากคุณต้องการใช้ PHP เพื่อแสดงการประทับเวลานี่เป็นโซลูชัน PHP ที่ดีที่สุดโดยไม่ต้องเปลี่ยนจากรูปแบบ UNIX_TIMESTAMP

ใช้ฟังก์ชัน custom_date () ข้างในใช้ DateTime นี่คือโซลูชัน DateTimeนี่คือทางออกที่

ตราบเท่าที่คุณยังไม่ได้ลงนาม BIGINT (8) เป็นการประทับเวลาของคุณในฐานข้อมูล ตราบใดที่คุณมี PHP 5.2.0 ++


-1

เนื่องจากฉันไม่ต้องการอัพเกรดอะไรเลยฉันจึงขอให้แบ็กเอนด์ (MSSQL) ทำงานนี้แทน PHP!

$qry = "select DATEADD(month, 1, :date) next_date ";
$rs_tmp = $pdo->prepare($qry);
$rs_tmp->bindValue(":date", '2038/01/15');
$rs_tmp->execute();
$row_tmp = $rs_tmp->fetch(PDO::FETCH_ASSOC);

echo $row_tmp['next_date'];

อาจไม่ใช่วิธีที่มีประสิทธิภาพ แต่ใช้ได้ผล

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.