0Pricing
Coding Interview Prep · บทเรียน

ค้นหาและแก้ไขคำสั่งค้นหาที่ช้า

รายการตรวจสอบเพื่อวินิจฉัยปัญหาสำหรับคำถามสัมภาษณ์ว่า “คำสั่งค้นหานี้ช้า จะแก้อย่างไร”

ค้นหาและแก้ไขคำสั่งค้นหาที่ช้า เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

โจทย์ “คำสั่งสอบถามนี้ช้า แก้ไขให้เร็วขึ้น”

นี่คือโจทย์สัมภาษณ์บทสรุป: ผู้สัมภาษณ์ยื่นคำสั่งสอบถามที่ช้าพร้อมแผน EXPLAIN ANALYZE ให้คุณ แล้วขอให้วินิจฉัยปัญหา สิ่งที่กำลังทดสอบคือวิธีการ ไม่ใช่เทคนิคที่ท่องจำ

คำตอบที่ดีจะทำตามรายการตรวจสอบและอธิบายออกมาดัง ๆ: วัดผล อ่านแผน ค้นหาต้นทุนหลัก ตั้งสมมติฐาน เสนอวิธีแก้ และตรวจสอบผล บทเรียนนี้จะสร้างรายการตรวจสอบดังกล่าวทีละขั้นตอน

ทำงานอย่างเป็นระบบและบรรยายเหตุผลของคุณ นั่นคือสิ่งที่ทำให้ได้รับการประเมินระดับอาวุโส

ขั้นที่ 1: วัดผลด้วย EXPLAIN ANALYZE

อย่าเดาจากคำสั่งฐานข้อมูลเพียงอย่างเดียว ให้ดึงแผนจริงด้วย EXPLAIN (ANALYZE, BUFFERS)

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

เรียกใช้สองสามครั้ง เพราะการเรียกใช้ครั้งแรกอาจเสียเวลาเนื่องจากแคชยังว่าง ทำให้เวลาที่วัดได้คลาดเคลื่อน

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.created_at >= '2026-01-01';

ขั้นที่ 2: ค้นหาโหนดที่เป็นต้นทุนหลัก

อย่าอ่านจากบนลงล่างโดยค้นหาแบบสุ่ม ให้หาโหนดที่ใช้เวลามากที่สุดจริง ๆ

คำนวณเวลาที่โหนดใช้เอง: เวลา actual time รวมของโหนดลบด้วยเวลาของโหนดย่อย แล้วคูณด้วย loops โหนดที่มีสัดส่วนมากที่สุดคือเป้าหมายของคุณ ส่วนที่เหลือเป็นเพียงสัญญาณรบกวน

ในการสัมภาษณ์ ให้พูดว่า: 80 เปอร์เซ็นต์ของเวลาทำงานอยู่ที่ Seq Scan โหนดนี้ ดังนั้นผมจะมุ่งแก้ตรงนี้ การปรับปรุงส่วนอื่นจะเป็นการเสียแรงโดยเปล่าประโยชน์

ขั้นที่ 3: ตรวจสอบค่าประมาณเทียบกับค่าจริง

ที่โหนดซึ่งเป็นต้นทุนหลัก ให้เปรียบเทียบจำนวนแถวที่ประเมินไว้กับจำนวนแถวจริง หากมีช่องว่างมาก แสดงว่าตัววางแผนกำลังทำงานโดยขาดข้อมูลและมีแนวโน้มเลือกแผนที่ไม่ดี (อัลกอริทึมการเชื่อมตารางผิด หรือวิธีเข้าถึงข้อมูลผิด)

ตัวอย่างแสดงการประเมินต่ำเกินไปถึง 1000 เท่า ก่อนออกแบบสิ่งใดใหม่ ให้ปรับปรุงสถิติ คำสั่งเดียวนี้มักแก้แผนได้โดยไม่ต้องเสียค่าใช้จ่ายเพิ่มเติม

ANALYZE จะคำนวณสถิติคอลัมน์ใหม่ ส่วน VACUUM ANALYZE จะล้างทูเพิลที่ตายแล้วและอัปเดตแผนที่การมองเห็นด้วย

-- estimate rows=100, actual rows=120000  -> stale stats
ANALYZE orders;
-- or, for bloated tables:
VACUUM ANALYZE orders;

สาเหตุทั่วไป: ใช้ฟังก์ชันกับคอลัมน์ที่มีดัชนี

ข้อบกพร่องที่แก้ไขได้และพบได้บ่อยที่สุดคือ มีฟังก์ชันหรือการแปลงชนิดข้อมูลครอบคอลัมน์ใน WHERE ทำให้ใช้ดัชนีไม่ได้ และกลไกฐานข้อมูลต้องสแกนตามลำดับ

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

แนวคิดเดียวกันใช้กับ WHERE lower(email)=...: ให้จัดเก็บข้อมูลที่ปรับรูปแบบแล้ว สอบถามคอลัมน์โดยตรง หรือสร้างดัชนีจากนิพจน์

-- Not sargable: index unusable
WHERE DATE(created_at) = '2026-01-01'

-- Sargable: range over the bare column
WHERE created_at >= '2026-01-01'
  AND created_at <  '2026-01-02'

สาเหตุทั่วไป: ไม่มีดัชนี

หากโหนดหลักเป็น Seq Scan ที่มีตัวกรองเลือกข้อมูลได้เฉพาะเจาะจงมาก หรือเป็นการวนซ้อนที่มีค่า loops สูงมากบนคีย์ด้านในที่ไม่มีดัชนี วิธีแก้มักเป็นการสร้างดัชนี

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

ตรวจสอบด้วยการเรียกใช้ EXPLAIN ANALYZE อีกครั้ง อย่าคิดเอาเองว่าดัชนีช่วยได้

CREATE INDEX idx_orders_customer
  ON orders (customer_id);

สาเหตุทั่วไป: ใช้ SELECT * และแถวกว้าง

SELECT * ดึงทุกคอลัมน์ออกจากดิสก์และส่งผ่านเครือข่าย อีกทั้งยังป้องกันการสแกนผ่านดัชนีเพียงอย่างเดียว เพราะดัชนีมักไม่ครอบคลุมทุกคอลัมน์

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

ผู้สัมภาษณ์ที่ใส่ SELECT * ไว้ในโจทย์ต้องการให้คุณสังเกตเห็น การลดรายการคอลัมน์มักเป็นการปรับปรุงที่ทำได้เร็วและเห็นผลจริงกับตารางที่มีแถวกว้าง

-- Before
SELECT * FROM orders WHERE customer_id = 42;

-- After: only needed columns (may enable index-only scan)
SELECT order_id, amount FROM orders WHERE customer_id = 42;

สาเหตุทั่วไป: ข้อมูลล้นไปยังดิสก์

หากโหนด Sort หรือ Hash รายงานการใช้ดิสก์ (Sort Method: external merge Disk: 25000kB หรือ Batches: > 1) แสดงว่าการดำเนินการใช้ work_mem เกินขนาดและต้องเขียนข้อมูลส่วนเกินลงดิสก์

ทางเลือกมีดังนี้: เพิ่ม work_mem สำหรับเซสชัน ลดจำนวนแถวที่เข้าสู่การเรียงลำดับหรือการแฮช (กรองให้เร็วขึ้น) หรือเพิ่มดัชนีที่จัดเตรียมลำดับการเรียงไว้แล้ว เพื่อไม่ต้องเรียงลำดับเลย

นี่คือการวินิจฉัยที่แม่นยำระดับอาวุโส ซึ่งผู้สัมภาษณ์ให้คุณค่า

Sort  (actual rows=2000000 loops=1)
  Sort Key: o.amount
  Sort Method: external merge  Disk: 25000kB

สาเหตุทั่วไป: ดึงแถวมากเกินไป

ให้สังเกต Rows Removed by Filter: 9500000 คำสั่งสอบถามอ่าน 10 ล้านแถวแล้วทิ้งไปเกือบทั้งหมด เป็นการทำงานที่สูญเปล่าอย่างชัดเจน

วิธีแก้: เพิ่มดัชนีเพื่อให้ใช้ตัวกรองระหว่างการเข้าถึงข้อมูล (ไม่ใช่หลังจากนั้น) ทำให้เพรดิเคตเลือกข้อมูลได้เฉพาะเจาะจงมากขึ้น หรือเลื่อนการกรองให้เร็วขึ้นในคำสั่งสอบถาม เพื่อให้มีแถวน้อยลงไหลขึ้นไปตามโครงสร้าง

หลักการคือ ทำงานให้น้อยที่สุด และกรองให้เร็วและประหยัดที่สุดเท่าที่ทำได้

Seq Scan on events
  Filter: (event_type = 'purchase')
  Rows Removed by Filter: 9500000

รายการตรวจสอบเพื่อวินิจฉัย

ท่องรายการนี้ในการสัมภาษณ์ แล้วคุณจะไม่หลงทาง:

  • วัดผลด้วย EXPLAIN (ANALYZE, BUFFERS)
  • ระบุตำแหน่งโหนดที่ใช้เวลามากที่สุด
  • เปรียบเทียบจำนวนแถวที่ประเมินกับจำนวนแถวจริง และแก้สถิติที่ล้าสมัยก่อน
  • ตรวจสอบการใช้ดัชนีได้ โดยนำฟังก์ชันออกจากคอลัมน์ที่ใช้กรอง
  • สร้างดัชนีให้ตัวกรองที่เลือกข้อมูลได้เฉพาะเจาะจงและคีย์การเชื่อมตาราง
  • ลดจำนวนคอลัมน์ และหลีกเลี่ยง SELECT *
  • เฝ้าดูการเขียนข้อมูลล้นไปยังดิสก์และการดึงแถวมากเกินไป
  • ตรวจสอบผลด้วยการเรียกใช้แผนอีกครั้ง

นำทุกอย่างมาประกอบกัน

ลองอธิบายตัวอย่างเต็มรูปแบบให้ฟังไปทีละขั้น แผนการทำงานแสดงการสแกนตามลำดับในตาราง orders ที่มี 50M แถว ใช้เงื่อนไขกรอง customer_id = 42 และมี Rows Removed by Filter ใกล้เคียง 50M โดยค่าประมาณใกล้เคียงกับค่าจริง

การวินิจฉัย: เงื่อนไขกรองมีความจำเพาะสูง แต่ไม่มีดัชนี ต้นทุนหลักคือการสแกน วิธีแก้: CREATE INDEX ON orders(customer_id) เรียกใช้ใหม่: แผนเปลี่ยนเป็นการสแกนด้วยดัชนี เวลาลดจากระดับวินาทีเหลือต่ำกว่าหนึ่งมิลลิวินาที

วงจรวัดผล-วินิจฉัย-แก้ไข-ตรวจสอบนี้คือรูปแบบคำตอบสำหรับคำถามเกี่ยวกับคิวรีที่ทำงานช้าทุกประเภท

CREATE INDEX idx_orders_customer ON orders (customer_id);
EXPLAIN (ANALYZE, BUFFERS)
SELECT order_id, amount FROM orders WHERE customer_id = 42;

ตรวจสอบความเข้าใจอย่างรวดเร็ว

คิวรีใช้ WHERE YEAR(order_date) = 2026 เป็นเงื่อนไขกรอง และแผนการทำงานแสดงการสแกนทั้งตารางตามลำดับ แม้ว่าจะมีดัชนีแบบบีทรีบน order_date อยู่แล้ว ควรแก้ไขสิ่งใดเป็นอันดับแรกจึงจะดีที่สุด

ทบทวน

ขณะนี้คุณมีวิธีที่ทำซ้ำได้สำหรับคำถามเกี่ยวกับคิวรีที่ทำงานช้าแล้ว:

  • ให้ วัดผล ด้วย EXPLAIN (ANALYZE, BUFFERS) เสมอ และมุ่งเน้นที่โหนดหลัก
  • แก้ไขสถิติที่ล้าสมัยก่อน เมื่อค่าประมาณกับค่าจริงแตกต่างกัน
  • ทำให้เงื่อนไขรองรับการใช้ดัชนี เพิ่มดัชนีสำหรับเงื่อนไขกรองที่มีความจำเพาะสูงและคอลัมน์สำหรับเชื่อมตาราง รวมทั้งตัด SELECT * ออก
  • จัดการการเขียนข้อมูลล้นลงดิสก์และการดึงข้อมูลเกินจำเป็น จากนั้นตรวจสอบแผนการทำงานใหม่

การอธิบายรายการตรวจสอบ เสนอการเปลี่ยนแปลงที่เป็นรูปธรรม แล้วเรียกใช้แผนการทำงานอีกครั้งเพื่อพิสูจน์ คือคำตอบแบบวิศวกรอาวุโส

คำถามที่พบบ่อย

บทเรียน “ค้นหาและแก้ไขคำสั่งค้นหาที่ช้า” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “ค้นหาและแก้ไขคำสั่งค้นหาที่ช้า” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ค้นหาและแก้ไขคำสั่งค้นหาที่ช้า”

รายการตรวจสอบเพื่อวินิจฉัยปัญหาสำหรับคำถามสัมภาษณ์ว่า “คำสั่งค้นหานี้ช้า จะแก้อย่างไร” คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “ค้นหาและแก้ไขคำสั่งค้นหาที่ช้า” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม

ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. อ่านแผน EXPLAIN
  2. การสแกนตามลำดับเทียบกับการสแกนดัชนีและการสแกนเฉพาะดัชนี
  3. อัลกอริทึมการเชื่อมตาราง: ลูปซ้อน แฮช และผสาน
  4. ค้นหาและแก้ไขคำสั่งค้นหาที่ช้า
← กลับไปที่ Coding Interview Prep