การต่อสู้กับปัญหา Encoding ของ PostgreSQL บน Windows + Docker
บทนำ
เมื่อรัน PostgreSQL ด้วย Docker บน Windows บางครั้งมีปัญหาในการจัดการข้อมูลภาษาญี่ปุ่น
“อักขระเพี้ยน”, “encoding errors”, “Illegal multibyte sequence”
ผมเจอ error เหล่านี้และใช้เวลาหลายชั่วโมงก่อนจะแก้ได้
บทความนี้คือบันทึกจุดเจ็บปวดเหล่านั้นและวิธีแก้ไข
ปัญหาที่เจอ
ปัญหาที่ 1: อักขระเพี้ยนเมื่อ Import CSV
ระหว่างงาน Japanese localization สำหรับ dvdrental ผมสร้าง CSV ที่แปลงชื่อภาพยนตร์เป็นภาษาญี่ปุ่นและพยายาม import เข้า PostgreSQL
COPY film(title) FROM '/tmp/titles_ja.csv' WITH (FORMAT CSV, HEADER TRUE, ENCODING 'UTF8');
Error:
ERROR: invalid byte sequence for encoding "UTF8": 0x83
สาเหตุคือ CSV ที่บันทึกด้วย Excel บน Windows เป็น “Shift-JIS (CP932)“
ปัญหาที่ 2: เครื่องหมายคำถามเมื่อ Execute SQL จาก PowerShell
$sql = "UPDATE film SET title = '進撃の巨人' WHERE film_id = 1;"
docker exec -i postgres psql -U postgres -d dvdrental -c $sql
ผลลัพธ์: title กลายเป็น ???
Default encoding ของ PowerShell ไม่ใช่ UTF-8 และการแปลง encoding เกิดขึ้นเมื่อส่งผ่าน pipe ไปยัง PostgreSQL
วิธีแก้ไข
วิธีแก้ไขที่ 1: บันทึก CSV เป็น UTF-8 (ไม่มี BOM)
แม้จะบันทึกจาก Excel เป็น “CSV UTF-8” บน Windows อาจกลายเป็น UTF-8 ที่มี BOM (UTF-8 with BOM)
COPY command ของ PostgreSQL อาจไม่จัดการ BOM อย่างถูกต้อง
วิธี A: เปิด CSV ใน VS Code และบันทึกเป็น “UTF-8” (ไม่มี BOM)
- File → Save As → เลือก encoding “UTF-8”
วิธี B: แปลงด้วย PowerShell
# แปลงจาก BOM มีไปเป็น BOM ไม่มี
$content = Get-Content -Path "titles_ja_bom.csv" -Encoding UTF8
$content | Set-Content -Path "titles_ja_nobom.csv" -Encoding UTF8NoBOM
วิธีแก้ไขที่ 2: คัดลอกไปยัง PostgreSQL Container แล้ว Import
# คัดลอกไฟล์ไปยัง container
docker cp .\sql\titles_ja.csv postgres:/tmp/titles_ja.csv
# Execute COPY ภายใน container (มีแนวโน้มเกิดปัญหา encoding น้อยกว่า)
docker exec postgres psql -U postgres -d dvdrental -c `
"COPY film(title) FROM '/tmp/titles_ja.csv' WITH (FORMAT CSV, HEADER TRUE, ENCODING 'UTF8')"
เนื่องจากไม่ผ่าน local filesystem จึงไม่มีการแปลง encoding ของ OS
วิธีแก้ไขที่ 3: การตั้งค่า Encoding ของ PowerShell
# เพิ่มที่ต้น script
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8NoBOM'
# หรือตั้งค่าเป็น environment variable
$env:PYTHONUTF8 = "1" # เมื่อใช้ Python scripts
PowerShell 7.x (pwsh) default เป็น UTF-8 แต่ Windows PowerShell 5.1 default เป็น Shift-JIS
วิธีตรวจสอบ:
[System.Text.Encoding]::Default
ถ้าแสดง Shift-JIS หรือ Code page 932 จำเป็นต้องเปลี่ยนการตั้งค่า
วิธีแก้ไขที่ 4: ตั้งค่า Locale อย่างชัดเจนใน Docker Compose
# compose.yml
services:
postgres:
image: postgres:16
environment:
POSTGRES_DB: dvdrental
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
LANG: ja_JP.UTF-8
LC_ALL: ja_JP.UTF-8
volumes:
- pgdata:/var/lib/postgresql/data
Default locale เมื่อสร้าง PostgreSQL database จะเป็น UTF-8
การตรวจสอบ Encoding ของ PostgreSQL
-- ตรวจสอบ encoding ของ DB ที่เชื่อมต่ออยู่
SELECT pg_encoding_to_char(encoding), datcollate FROM pg_database WHERE datname = current_database();
-- ตรวจสอบ encoding ทั้ง server
SHOW server_encoding;
SHOW client_encoding;
ยืนยันว่า server_encoding เป็น UTF8
วิธีตรวจสอบ Encoding ของไฟล์ CSV
# ตรวจสอบ bytes แรกของไฟล์ด้วย PowerShell
$bytes = [System.IO.File]::ReadAllBytes(".\titles_ja.csv") | Select-Object -First 4
$bytes | ForEach-Object { "0x{0:X2}" -f $_ }
# UTF-8 ที่มี BOM: 0xEF 0xBB 0xBF ...
# UTF-8 ไม่มี BOM: Data bytes มาตรงจากต้น
การแก้ไขข้อมูลที่เพี้ยนแล้ว
สำหรับการแก้ไขข้อมูลที่เพี้ยนไปแล้ว:
-- ตรวจสอบข้อมูลที่เพี้ยน
SELECT film_id, title FROM film WHERE title ~ '[^\x00-\x7F]' IS FALSE;
-- เขียนทับด้วยข้อมูลญี่ปุ่นที่ถูกต้อง (re-import จาก CSV)
TRUNCATE TABLE film_title_ja; -- ถ้าใช้ working table
-- หรือแก้ไขตรงๆ ด้วย UPDATE
UPDATE film SET title = 'ชื่อภาษาญี่ปุ่นที่ถูกต้อง' WHERE film_id = 1;
สรุป
| ปัญหา | สาเหตุ | วิธีแก้ไข |
|---|---|---|
| Import CSV ล้มเหลว | Shift-JIS หรือ UTF-8 ที่มี BOM | บันทึกใหม่เป็น UTF-8 (ไม่มี BOM) |
| PowerShell อักขระเพี้ยน | Console encoding เป็น CP932 | [Console]::OutputEncoding = UTF8 |
| ส่งตรงไปยัง container ไม่ได้ | PowerShell pipe แปลง encoding | ใช้ docker cp คัดลอกไฟล์ก่อน |
| DB default ไม่ใช่ UTF-8 | ไม่ได้ตั้ง LANG ใน Docker Compose | ตั้งค่า LANG: ja_JP.UTF-8 อย่างชัดเจน |
ในสภาพแวดล้อม development บน Windows มีหลายสถานการณ์ที่สมมติฐาน “UTF-8 เป็นเรื่องปกติ” ใช้ไม่ได้
การพัฒนานิสัยในการตรวจสอบ encoding ทุกครั้งที่จัดการ CSV ช่วยลดเวลาที่เสียไปได้อย่างมาก
แผนที่บทความของ Series นี้
→ การสร้างแอพ DVD Rental สำหรับผู้ใช้ควบคู่กับหน้าจัดการ — ภาพรวมโครงสร้าง Vue 3 + Spring Boot