แอปเก่าพังหลังย้ายเซิร์ฟเวอร์ — error 1364/1366 กับ MariaDB strict mode
โค้ดเดิม ข้อมูลเดิม แค่ย้ายเครื่องก็พัง — เพราะสิ่งที่เปลี่ยนไม่ใช่โค้ด แต่เป็น default ของ MariaDB ที่เข้มขึ้นทุกรุ่น บทความนี้ไล่ตั้งแต่อาการ วิธีพิสูจน์ จนถึงทางแก้ที่เลือกได้ตามหน้างาน
สรุปใน 1 นาที
- อาการ/ปัญหา
- ย้ายเว็บมาเครื่องใหม่แล้วฟอร์มสมัครสมาชิกพัง ขึ้น error 1364/1366 ทั้งที่โค้ดไม่เปลี่ยน
- ใครควรอ่าน
- คนย้ายแอป PHP เก่าไป MariaDB รุ่นใหม่
- ทางแก้แบบสั้น
- MariaDB รุ่นใหม่เปิด STRICT_TRANS_TABLES ใน sql_mode แก้ได้ 3 ระดับ ตั้งแต่ conf.d ทั้งเครื่อง จนถึง SET SESSION เฉพาะแอปที่ยังเก่าอยู่
พอดีหลังย้ายเว็บจากเซิร์ฟเวอร์เก่า (CentOS + MariaDB รุ่นเก่า) มาเครื่องใหม่ (Debian + MariaDB รุ่นปัจจุบัน) มีระบบสมาชิกตัวหนึ่งอาการแปลกครับ: หน้าเว็บปกติทุกอย่าง แต่พอกดสมัครสมาชิกขึ้น "temporary error" เฉยๆ ไม่มีรายละเอียดอะไรเลย
เคสแบบนี้ถ้าไม่รู้จัก strict mode มาก่อน อาจนั่งไล่โค้ด PHP ทั้งวันก็ไม่เจอ เพราะโค้ดไม่ผิด — สภาพแวดล้อมมันเปลี่ยน เลยเอาวิธีไล่มาเล่าไว้
ไล่หาต้นเหตุ
โชคดีระบบนี้มีตาราง error log ของตัวเอง เข้าไปดูเจอตัวจริง:
SQLSTATE[HY000]: General error: 1364
Field 'middlename' doesn't have a default value
บางระบบจะเจออีกหมายเลขในตระกูลเดียวกัน:
SQLSTATE[22007]: Invalid datetime format: 1366
Incorrect integer value: '' for column 'status' at row 1
ทั้งคู่คืออาการเดียวกัน: โค้ด INSERT โดยไม่ส่งค่าครบทุก column หรือส่ง string ว่างใส่ column ตัวเลข ซึ่งแอปยุค PHP 5 ทำกันเป็นเรื่องปกติ เพราะ MySQL สมัยนั้นแค่ warning แล้วใส่ค่า default ให้เงียบๆ
ที่พังเพราะ MariaDB ตั้งแต่ 10.2 เปลี่ยน default ของ sql_mode ให้มี STRICT_TRANS_TABLES — จาก warning กลายเป็น error ทันที เว็บเดิมโค้ดเดิมข้อมูลเดิม แค่ย้ายเครื่องก็พัง
เช็คว่าเครื่องเราตั้งอะไรอยู่:
mysql -e 'SELECT @@GLOBAL.sql_mode;'
# STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
ทางแก้
ทางที่ถูกต้องที่สุดคือแก้โค้ดให้ INSERT ครบ cast ค่าให้ถูก แต่กับแอปเก่าที่ไม่มีคนดูแลแล้ว (หรือเข้ารหัส ionCube แก้ไม่ได้) วิธีที่ workable จริงคือถอด strict mode ออก — ทำเป็นไฟล์ conf แยกต่างหาก อย่าไปแก้ไฟล์หลักของ distro:
cat > /etc/mysql/conf.d/98-legacy-sqlmode.cnf <<'EOF'
# แอปเก่า (pre-strict era) พังกับ STRICT_TRANS_TABLES หลังย้ายเซิร์ฟเวอร์
# error 1364 (no default) / 1366 (incorrect value) ตอน INSERT
[mysqld]
sql_mode = ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
EOF
systemctl restart mariadb
mysql -e 'SELECT @@GLOBAL.sql_mode;'
เหตุผลที่ทำเป็นไฟล์ 98-*.cnf แยก: อัปเกรด MariaDB รอบหน้าไฟล์ config หลักโดนทับ ไฟล์ conf.d ของเรารอด และเปิดมาอ่านก็รู้ทันทีว่าใส่ไว้ทำไม — comment ในไฟล์คือจดหมายถึงตัวเองในอนาคตครับ
ถ้าไม่อยากถอดทั้งเซิร์ฟเวอร์
strict mode มีประโยชน์กับแอปใหม่ๆ จริง (กันข้อมูลขยะเข้า DB) ถ้าเครื่องมีทั้งแอปเก่าแอปใหม่ปนกัน มีทางเลือกละเอียดกว่า
แก้โค้ดจุดต่อ DB ได้สักบรรทัด
หลัง new PDO(...) หรือ mysqli_connect(...) ใส่บรรทัดเดียว — แอปอื่นบนเครื่องยังได้ strict mode เต็มๆ
แตะโค้ดไม่ได้เลย
SET GLOBAL init_connect ให้รันตอนทุก connection เปิด — ระวัง: ไม่ apply กับ user ที่มีสิทธิ์ SUPER
// แบบที่ 1: หลัง new PDO(...) หรือ mysqli_connect(...)
$pdo->exec("SET SESSION sql_mode = 'NO_ENGINE_SUBSTITUTION'");
-- แบบที่ 2: ระดับ server (ไม่ apply กับ user สิทธิ์ SUPER)
SET GLOBAL init_connect = "SET sql_mode='NO_ENGINE_SUBSTITUTION'";
ส่วนตัวผมเลือกถอดทั้งเครื่องเพราะเครื่องนั้นแอปเก่าเป็นส่วนใหญ่ — เลือกตามหน้างานครับ
เช็คลิสต์เวลาย้ายเซิร์ฟเวอร์แล้ว "ฟอร์มพัง แต่หน้าเว็บปกติ"
- หา error log ของแอปก่อน — หลายระบบเก็บลง DB ตัวเอง ไม่ออก PHP error log
- เจอ 1364/1366 = strict mode ชัวร์
SELECT @@GLOBAL.sql_modeเทียบกับเครื่องเก่า- ตัดสินใจ: แก้โค้ด / SET SESSION / ถอด global
- ทำเป็น conf.d ไฟล์แยก + comment เหตุผลกำกับ
เคสนี้ผมใช้เวลาไล่จริงๆ ไม่ถึงครึ่งชั่วโมง เพราะเคยเจอมาก่อน — ใครกำลังวางแผนย้ายเซิร์ฟเวอร์ เก็บ @@GLOBAL.sql_mode ของเครื่องเก่าไว้เทียบก่อนย้ายเลยครับ กันงงทีหลัง ตรงไหนผมอธิบายตกหล่นหรือพิมพ์ผิด เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ
อยากให้เราทำให้ไหม?
ให้เราช่วยติดตั้ง ตั้งค่า ย้ายระบบ หรือแก้ปัญหาเซิร์ฟเวอร์ เพื่อให้คุณมีเวลาไปทำงานที่สำคัญกว่า
เคยเจองานแบบนี้ไหม? ติดตรงไหน?
มาแลกวิธีกันต่อในบอร์ด LinuxThai



ความคิดเห็น 0
ยังไม่มีความคิดเห็น — เป็นคนแรกเลย