วิธี Search MySQL ด้วย Fulltext Order By Query เร็วและแรง
ถ้าใครเคยใช้ Fulltext จะรู้ว่า Fulltext นั้นค้นหาข้อมูลเร็วมาก แต่ก็ต้องมาเจ็บปวดเมื่อต้องใช้ “ORDER BY” เมื่อ Record ยิ่งเยอะยิ่งช้ามากๆ เมื่อคืนผมเจอปัญหาก็คิดไม่ตก คิดไปคิดมาไปอ่านการทำงานของ Order By Optimization ซึ่งอ่านไปแล้วก็ลองทำตามก็ไม่ได้ผล แม้จะอัพเรื่องจำนวน Memory ของ sort_buffer_size , read_rnd_buffer_size ก็ไม่หายตอนนั้นก็เกือบตี 4 แล้วถอดใจ กะว่าได้ใช้ Inverse Index แน่ๆแต่พอนอนลงแล้วนึกทบทวนเกี่ยวกับที่ได้อ่าน Order By Optimization จำประโยคได้ว่า
“Read all rows according to key or by table scanning. Rows that do not match the WHERE clause are skipped.”
คือถ้ามีการใช้ Where มันจะ Skip ข้อมูลตรงส่วนที่เหลือทิ้งไป งั้นเราก็มาดูตัวอย่างกันครับว่า Query ของผมเป็นยังไง
SELECT id,title,created_at,updated_at FROM pages WHERE root_page_id IN (1,2,3,4,5,6,7) AND MATCH (title,content) AGAINST (‘”ais” “dtac” “true”‘ IN BOOLEAN MODE) AND id NOT IN (1000,1001,1002,1003) ORDER BY created_at DESC LIMIT 0,20
โดยทั้งหมดนี้ผมทำ index แบบธรรมดาคือไม่ได้ composition ทั้งหมดแต่ก็มี index ทุก field ใน query นี้แต่ผลที่ได้ของเดิมก็คือ 20 sec ต่อมาก็เลยทดสอบว่าถ้า scope created_at ไว้น่าจะเร็วขึ้น ตามที่เขาเขียนข้างบนเลยกลายเป็น
SELECT id,title,created_at,updated_at FROM pages WHERE created_at >= ’2011-04-05 23:59:59′ AND created_at <= ’2011-05-05 23:59:59′ root_page_id IN (1,2,3,4,5,6,7) AND MATCH (title,content) AGAINST (‘”ais” “dtac” “true”‘ IN BOOLEAN MODE) AND id NOT IN (1000,1001,1002,1003) ORDER BY created_at DESC LIMIT 0,20
แล้วก็ลองทดสอบดูสรุปก็ยังเป็น 20 วินาทีอยู่ดีช้าอยู่ดี ก็ส่งสัยว่าทำไมๆๆ ก็เลยนึกถึงความเป็นจริงลองคิดว่า MySQL เป็นคน “created_at” ต่อให้มี index เราก็ต้องไปดู index ทั้งหมดอยู่ดีว่า id ไหนบ้างนะที่อยู่ใน range นี้โดยลองคิดดูว่ามีสัก > 500K เสร็จแล้วยังไม่วายโดน order_by อีกก็เลยยังช้าเหมือนเดิม แล้ววิธีคิดแบบไหนละที่จะทำให้เร็วขึ้น
ลองคิดดูว่าถ้าคุณมีตระกร้าของแต่ละคำที่ต้องการค้นหา (ในที่นี้ก็คือ fulltext และ index) เสร็จแล้วเอาตระกร้าเหล่านั้นออกมาก่อน แล้วค่อยมาเรียงมันจะเร็วกว่าไหม ? (เพราะแบบอันแรกเรายังไม่เทตระกร้าแล้วค่อยออกมาเรียง scope แต่เป็นการหา “ตะกร้าที่เกี่ยวข้อง” แล้ว “เรียงทั้งหมดใน table” เพราะยังไม่เท่ตะกร้าเนี้ยและ) คงส่งสัยใช่ไหมว่า MySQL มันจะโง่ยังงั้นเลยหรอ อันนี้ผมก็ไม่แน่ใจ แต่ผมใช้แบบ Query นี้
SELECT id,title,created_at,updated_at FROM pages WHERE id IN (SELECT id FROM pages WHERE root_page_id IN (1,2,3,4,5,6,7) AND MATCH (title,content) AGAINST (‘”ais” “dtac” “true”‘ IN BOOLEAN MODE) AND id NOT IN (1000,1001,1002,1003) ) ORDER BY created_at DESC LIMIT 0,20
เชื่อไหมว่า Query ผมจาก 20 วินาทีเหลือเพียง 0.3-1 วินาทีเท่านั้น !! นั้นหมายความว่าเร็วขึ้น 20x – 70x เท่าเลยทีเดียวนะครับ โดยไม่ต้องไปใช้ Inverse Index แต่ใช้วิธีนี้ แค่นี้ผมก็ยังไม่ต้องรีบเปลี่ยน Software Architecture และไม่ต้องไปเปลื้อง RAM จำนวนมากเพื่อให้ตอบโจทย์นี้เพราะมันจะต้องกิน RAM ประมาณ 6GB ในข้อมูลขนาด 650,000 rows ของผมซึ่งผมไม่มี ฝากบทความนี้กับคนที่กำลังจะหาวิธีใช้ Fulltext กับ Order By นะครับ
โดย Query เนี้ยผมทำกับระบบชื่อ OB-VOC ครับลองอ่านดูที่ Thumbsup ครับ
