30 คะแนน โดย GN⁺ 2026-01-26 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ดัชนีของ PostgreSQL เป็นโครงสร้างสำคัญที่ช่วยเพิ่มความเร็วในการเข้าถึงข้อมูล โดยลดปริมาณข้อมูลที่ต้องอ่านจากดิสก์เพื่อ ปรับปรุงประสิทธิภาพของคิวรี
  • ดัชนีมีให้เลือกหลายรูปแบบ เช่น Btree, Hash, BRIN, GIN, GiST, SP-GiST ซึ่งแต่ละแบบถูกปรับให้เหมาะกับลักษณะข้อมูลและรูปแบบคิวรีที่ต่างกัน
  • ดัชนีมีต้นทุนหลายด้าน เช่น พื้นที่ดิสก์, ประสิทธิภาพการเขียน, ความซับซ้อนของ query planner, การใช้หน่วยความจำ
  • ฟีเจอร์ขั้นสูงอย่าง partial index, multicolumn index, covering index, expression index สามารถช่วยเพิ่มประสิทธิภาพสูงสุดในบางสถานการณ์ได้
  • การเลือกและจัดการดัชนีอย่างเหมาะสมถูกเน้นว่าเป็น องค์ประกอบสำคัญของการปรับแต่งประสิทธิภาพ PostgreSQL

แนวคิดพื้นฐานของดัชนี

  • ดัชนีคือโครงสร้างที่ช่วยให้ฐานข้อมูล ลดปริมาณข้อมูลที่ต้องอ่านจากดิสก์ เพื่อให้คิวรีทำงานได้เร็วขึ้น
    • Primary key, unique key, exclusion constraint ก็ถูกทำให้ทำงานผ่านดัชนีเช่นกัน
    • ดัชนีมีประสิทธิภาพเมื่อผลลัพธ์ของคิวรีมีน้อยกว่า 15~20% ของทั้งตาราง และหากมากกว่านั้น sequential scan อาจมีประสิทธิภาพดีกว่า
  • PostgreSQL มี ประเภทดัชนี 6 แบบ มาให้โดยค่าเริ่มต้น และสามารถใช้ประเภทเพิ่มเติมได้ผ่านส่วนขยาย
    • ดัชนีแต่ละตัวจะเชื่อมค่าคีย์เข้ากับตำแหน่งข้อมูลที่เกี่ยวข้อง (TID)

โครงสร้างข้อมูลที่จัดเก็บบนดิสก์

  • ตารางของ PostgreSQL ถูกเก็บเป็นไฟล์ heap และประกอบด้วยหน้า (page) ขนาด 8KB
  • แต่ละแถว (tuple) ถูกเก็บโดยไม่มีลำดับตายตัว และมีที่อยู่ภายในระบุด้วย ctid (current tuple id)
    • ตัวอย่าง: (0,1) หมายถึงทูเพิลตัวแรกของหน้า 0
  • ดัชนีจะเชื่อมตำแหน่งเหล่านี้ของ heap (ctid) ด้วยโครงสร้างต้นไม้เพื่อรองรับการค้นหาอย่างรวดเร็ว

วิธีที่ดัชนีช่วยเร่งการเข้าถึงข้อมูล

  • หากไม่มีดัชนี PostgreSQL จะทำ sequential scan โดยอ่านทุกหน้า
    • ในตัวอย่างคิวรีเมื่อต้องค้นหา name='Ronaldo' จะต้องอ่าน 6272 หน้าและใช้เวลา 265ms
  • เมื่อเพิ่มดัชนี จะเปลี่ยนเป็น Index Scan อ่านเพียง 4 หน้าและเสร็จใน 0.077ms
    • ดัชนีจะจับคู่ค่ากับ ctid เพื่อค้นหาเฉพาะแถวที่ต้องการได้อย่างรวดเร็ว
  • ขนาดไฟล์ดัชนีอาจใกล้เคียงกับขนาดตารางได้ (เช่น ตาราง 30MB → ดัชนี 30MB)

ต้นทุนของดัชนี

  • นอกจากช่วยเพิ่มประสิทธิภาพแล้ว ดัชนียังมาพร้อม ภาระต้นทุน หลายด้าน

พื้นที่ดิสก์

  • ดัชนีใช้พื้นที่จัดเก็บแยกต่างหาก และ อาจมีขนาดใหญ่กว่าตาราง
    • ทำให้มีต้นทุนเพิ่มเติมในการสำรองข้อมูล การทำสำเนา และการกู้คืนจากความเสียหาย
    • สามารถปรับปรุงประสิทธิภาพด้านพื้นที่ได้ด้วย partial index, multicolumn index, BRIN เป็นต้น

งานเขียนข้อมูล

  • เมื่อมี UPDATE, INSERT, DELETE แล้วคอลัมน์ที่อยู่ในดัชนีถูกเปลี่ยน จะเกิด overhead จากการอัปเดตดัชนี

Query planner

  • ยิ่งมีดัชนีมากขึ้น ตัว planner ก็ต้องพิจารณาทางเลือกมากขึ้น ทำให้ใช้เวลาสร้างแผนคิวรีนานขึ้น

การใช้หน่วยความจำ

  • หน้าของดัชนีจะถูกโหลดไว้ใน shared buffer เพื่อแคช และยิ่งมีดัชนีมากก็ยิ่งเพิ่มภาระหน่วยความจำ
  • เนื่องจากมี ข้อจำกัดขนาดโหนดของ btree คอลัมน์ที่มีขนาดใหญ่จะทำให้ต้นไม้ลึกขึ้น
  • ในการ sort, multicolumn scan, vacuum, reindex ก็ยังมีการใช้ work memory เพิ่มเติมด้วย

ประเภทหลักของดัชนี

Btree

  • เป็น โครงสร้างดัชนีเริ่มต้น ของ PostgreSQL และเป็นดัชนีอเนกประสงค์ที่ใช้ใน DBMS ส่วนใหญ่
    • รองรับการค้นหาอย่างรวดเร็วด้วยความซับซ้อนเวลา O(log n)
    • เป็น โครงสร้างต้นไม้สมดุล ที่ทุก leaf node มีความลึกเท่ากัน
    • เหมาะกับการทำงานของ ORDER BY, JOIN และใช้กับข้อกำหนด primary key และ unique key
  • โหนดภายในเก็บตัวชี้ไปยังโหนดย่อย ส่วน leaf node จะเก็บคีย์และ heap pointer
  • สามารถค้นหาได้สองทิศทางผ่าน left/right node pointer
การใช้หลายดัชนีร่วมกัน
  • PostgreSQL สามารถรวมหลายดัชนีด้วย bitmap AND/OR เพื่อจัดการเงื่อนไขแบบผสม
    • ตัวอย่าง: เมื่อมีเงื่อนไข age=30 AND login_count=100 จะรวมบิตแมปจากดัชนีสองตัวเข้าด้วยกัน
ดัชนีหลายคอลัมน์
  • สามารถรวมหลายคอลัมน์ไว้ในดัชนีเดียวเพื่อ ประหยัดพื้นที่และเพิ่มความเร็ว
    • อย่างไรก็ตาม ลำดับของคอลัมน์มีความสำคัญ และจะใช้ดัชนีได้เฉพาะเงื่อนไขที่ตรงจากด้านซ้ายก่อนเท่านั้น
Partial index
  • ใช้เงื่อนไขเพื่อทำดัชนีเฉพาะบางแถว
    • ลดขนาดดัชนี ช่วยให้เหมาะกับ RAM มากขึ้น และเพิ่มความเร็วในการค้นหา
    • ตัวอย่าง: create index on rules(status) where status='enabled';
    • มีประโยชน์เมื่อการกระจายค่ามีความไม่สมดุล (เช่น status <> 'TODO')
Covering index
  • หากคอลัมน์ทั้งหมดที่คิวรีต้องใช้ถูกรวมอยู่ในดัชนี ก็จะ คืนผลลัพธ์ได้โดยไม่ต้องเข้าถึง heap (index-only scan)
    • create index abc_cov_idx on bar(a, b) including c;
    • ใช้พื้นที่ได้มีประสิทธิภาพกว่า multicolumn index
Expression index
  • เป็นการทำดัชนีจาก ผลลัพธ์ของฟังก์ชันหรือ expression แทนค่าคอลัมน์โดยตรง
    • ตัวอย่าง: CREATE INDEX idx_lower_name ON customers (lower(name));
    • มีประโยชน์เมื่อค้นหาด้วยค่าที่แปลงแล้วอย่าง LOWER(name)
    • ใช้ได้เฉพาะฟังก์ชันแบบ immutable เท่านั้น

Hash

  • เป็นดัชนีที่อิงกับ โครงสร้าง hash map และ ประหยัดพื้นที่ สำหรับข้อมูลอย่างสตริงยาวหรือ UUID
    • เก็บ hash code ขนาด 32 บิตเพื่อลดขนาด
    • รองรับเฉพาะการเปรียบเทียบแบบ เท่ากัน (=) เท่านั้น และไม่รองรับการ sort หรือดัชนีหลายคอลัมน์
    • หากมีการกระจาย hash อย่างสม่ำเสมอ อาจให้ ประสิทธิภาพการอ่านที่เร็วกว่า Btree ได้
  • ตามเอกสารทางการ hash index สามารถ เข้าถึง bucket page ได้โดยตรง จึงช่วยลด I/O ในตารางขนาดใหญ่

BRIN (Block Range Index)

  • เป็นดัชนีที่เก็บเพียง ค่าต่ำสุดและค่าสูงสุดของแต่ละช่วงบล็อก
    • กะทัดรัดมากและเป็นมิตรกับแคช
    • เหมาะกับ ข้อมูลขนาดใหญ่, append-only, time-series
  • หากมีการอัปเดตแถวบ่อย ประสิทธิภาพจะลดลงจาก การเก็บข้อมูลซ้ำเพราะ MVCC
  • สามารถปรับสมดุลระหว่าง ความแม่นยำกับขนาด ได้ด้วยการตั้งค่า pages_per_range

GIN (Generalized Inverted Index)

  • เป็นดัชนีที่เหมาะกับ การค้นหาในข้อมูลเชิงประกอบ
    • รองรับการค้นหาองค์ประกอบเฉพาะในข้อความ, อาร์เรย์, JSONB เป็นต้น
    • ใช้ กลยุทธ์เฉพาะ (opclass) ตามชนิดข้อมูล
    • สำหรับ JSON แนะนำให้ใช้กับ คอลัมน์ JSONB ส่วนข้อความแนะนำให้ใช้ร่วมกับ tsvector หรือส่วนขยาย pg_trgm

GiST & SP-GiST

  • Generalized Search Tree (GiST) และ Space-Partitioned GiST (SP-GiST) เป็นเฟรมเวิร์กสำหรับสร้างดัชนีของข้อมูลบางประเภท
    • GiST รองรับ ต้นไม้สมดุล ส่วน SP-GiST รองรับ โครงสร้างไม่สมดุล
    • ใช้กับ ข้อมูลภูมิสารสนเทศ, inet, range, text vector เป็นต้น
    • GIN เด่นด้านการค้นหาที่รวดเร็ว ขณะที่ GiST มีต้นทุนในการสร้างและดูแลต่ำกว่า
    • สำหรับ full-text search ควรเลือกให้เหมาะกับความต้องการระหว่างสองแบบนี้

บทสรุป

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

1 ความคิดเห็น

 
GN⁺ 2026-01-26
ความคิดเห็นจาก Hacker News
  • เอกสารทางการของ PostgreSQL เขียนได้ดีมากและอ่านสนุกด้วย เลยอยากแชร์
    เอกสารแนะนำ PostgreSQL Indexes

  • ส่วนของดัชนีหลายคอลัมน์แทบจะเหมือนกับที่ฉันเคยเรียนมา
    แต่ก็สงสัยว่าใน PostgreSQL เวอร์ชันล่าสุดยังเป็นแบบนั้นอยู่หรือไม่
    ก่อนหน้านี้เคยเห็นมีการใช้ bitmap index scan กับคิวรีที่คล้ายกับตัวอย่างที่สาม เลยทำให้เริ่มกลับมาทบทวน “ความเข้าใจแบบเดิม” อีกครั้ง
    สำหรับเรื่องดัชนี ฉันคิดว่าเว็บไซต์และหนังสือ Use The Index, Luke เป็น งานคลาสสิก ที่ทั้งทีมควรอ่าน

    • ใน PostgreSQL 18 มีการเพิ่ม index skip scan ทำให้ตอนนี้สามารถค้นหาได้อย่างมีประสิทธิภาพแม้ใช้แค่คอลัมน์รองของดัชนีหลายคอลัมน์
      ในเวอร์ชันก่อนหน้าก็ทำได้เหมือนกัน แต่ตอนนั้นต้องสแกนทั้งดัชนีจึงไม่มีประสิทธิภาพ
      วิดีโอที่เกี่ยวข้อง: ลิงก์ YouTube
    • bitmap index scan ช่วยจำกัดหน้าที่มีโอกาสพบข้อมูล แต่ยังต้องตรวจสอบเงื่อนไขจริงซ้ำอีกครั้ง จึงช้ากว่าการสแกนดัชนีแบบปกติ
  • คิดว่าน่าจะดีถ้า PostgreSQL รองรับ incremental view maintenance เป็นความสามารถพื้นฐาน
    แนวคิดนี้จะอัปเดตอัตโนมัติเมื่อข้อมูลต้นทางเปลี่ยน เหมือนดัชนี แต่ไม่ได้จำกัดอยู่แค่วิวบางประเภทและสามารถใช้กับวิวใดก็ได้

    • เรื่องนี้ค่อนข้างยากเพราะเกี่ยวข้องกับ การประมวลผลทรานแซกชัน
      มีหลายโปรเจกต์ที่เกี่ยวข้อง เช่น Noria, Materialize, Apache Flink, GCP Continuous Queries, Spark Streaming Tables, Delta Tables, ClickHouse streaming tables, TimescaleDB, ksqlDB, StreamSQL
      ฝั่ง PostgreSQL ช่วงหลังมีส่วนขยายชื่อ pg_ivm ที่เริ่มเข้ามาจัดการปัญหานี้
    • ถ้าเป็นข้อมูลอนุกรมเวลา ฟีเจอร์ continuous aggregates ของ TimescaleDB ก็ทำหน้าที่ลักษณะนี้อยู่แล้ว
  • ประเด็น B-tree vs Hash อินเด็กซ์ น่าสนใจ
    หลายคนคิดว่าคอลัมน์ ID เหมาะกับ hash มากกว่า แต่ในความเป็นจริง B-tree แบบปกติมักมีประสิทธิภาพดีกว่า
    โดยเฉพาะการแทรกค่าที่เกือบเป็นลำดับต่อเนื่อง โครงสร้างแบบต้นไม้จะได้เปรียบกว่า
    อย่างไรก็ตาม บล็อกโพสต์ที่อ้างถึงครั้งนี้กลับบอกว่า hash ชนะในการ benchmark

  • จังหวะของบทความนี้ดีมาก
    กฎ leading column ของดัชนีหลายคอลัมน์เป็นเรื่องที่สับสนอยู่เสมอ แต่ด้วย bitmap index scan ทำให้มันไม่ร้ายแรงเหมือนเมื่อก่อน
    ฟีเจอร์ skip scan ใน PostgreSQL 18 เปลี่ยนความเข้าใจเดิมไปมาก ดังนั้นถ้าเรียนมาจากเวอร์ชันเก่า ก็คงต้อง อัปเดต mental model กันใหม่

  • คิดว่าเป็นบทความที่ยอดเยี่ยมมากสำหรับ PostgreSQL
    เรื่อง B-tree index เองก็อ้างอิง Use The Index, Luke มานานแล้วบ่อยครั้ง

  • คิดว่าเป็นงานที่ควรอ่าน
    ไม่ได้เป็นแค่บทความเกริ่นนำแบบผิวเผิน แต่ลึกพอสมควรและยังอ่านได้ไม่ยาก ตราบใดที่ยังไม่ลงไปถึงโครงสร้างภายใน

  • ชอบ สไตล์การเขียนที่เรียบง่ายและถ่อมตัว แบบนี้
    เป็นวิธีถ่ายทอดความรู้ที่ตรงไปตรงมาดี