Postgres SQL Optimization

First W. May
ntbx
Published in
2 min readJun 28, 2020

ทำไม query ช้า, รัน query แล้ว timeout บ่อย ที่นี่มีคำตอบ คลิก!!

COUNT(*) vs COUNT(1)

COUNT(1) หรือ COUNT({column_name}) จะเร็วกว่า COUNT(*) เสมอ (ถ้าในตารางไม่ได้มีคอลัมน์เดียว)

  • (*) คำสั่งนี้จะเรียกข้อมูลทุกคอลัมน์ในตารางนั้นขึ้นมารอก่อน แล้วค่อยนับว่ามีผลลัพธ์กี่ record
  • (1) หรือ ({column_name}) จะเรียกข้อมูลมานับเฉพาะคอลัมน์แรก หรือคอลัมน์ที่เรากำหนด

UNION vs UNOIN ALL …more

  • UNION ใช้สำหรับรวมสองตารางเข้าด้วยกันโดยกัน (ผลลัพธ์เป็นค่าที่ไม่ซ้ำกัน)
  • UNION ALL คือการนำสองตารางมาต่อกัน (ผลลัพธ์มีค่าที่ซ้ำกันได้)

TIP: ถ้าผลลัพธ์ที่ต้องการมี record ที่ซ้ำกันได้ หรือรู้อยู่แล้วว่าไม่มีข้อมูลซ้ำในการ union ครั้งนี้ หการใช้ UNION ALL จะเร็วกว่า เพราะระบบไม่ต้องวนลูปเช็คข้อมูลซ้ำเหมือนคำสั่ง UNION

คิดดีๆก่อนใช้ “ WITH” statement

ข้อมูลที่ทำการ query จาก with statement จะ ไม่มี index, เพราะฉนั้น การใช้ with เพื่อมา query ต่อจึงทำให้ช้ากว่าการใช้ Sub Query ใน SQL หลักตรงๆไปเลย ( ถ้าคอลัมน์ที่ใช้ JOIN มี INDEX)

sub query ภายใน with statement ไม่ควรมีผลลัพธืที่เป้น table ใหญ่ๆ เช่นการ ‘select * from table’ แบบไม่มีฟิลเตอร์อะไรเลย เพราะจะทำให้ query ช้า และใช้ memory ของระบบเป็นจำนวนมาก

เมื่อไหร่ควรใช้ WITH ?

  • มีผลลัพธ์ปลายทางชัดเจน ( มีการ where ใน subquery ผลลัพธ์ไม่ใหญ่มาก)
  • INDEX ไม่มีผลต่อการ JOIN กับตารางหลัก (optional)

***ถ้าใช้ with statement ใน SQL บ่อยๆ หมายความว่าควรสร้างตารางใหม่สำหรับเรื่องนั้นๆแล้ว

ใช้ ‘NOT EXISTS’ แทนการ ‘LEFT JOIN ON null id

Slow Query (A):

SELECT developper.name
FROM developper
LEFT JOIN talent ON developper.id = talent.foreign_id
WHERE talent.id IS NULL
Execution time: 1.585 ms

Optimized Query (B):

SELECT developper.name
FROM developper
WHERE NOT EXISTS (SELECT 1 talent.id WHERE developper.id = talent.foreign_id)
Execution time: 1.050 ms

จาก SQL ด้านบน ทั้งสองแบบมีผลลัพธ์แบบเดียวกัน แต่ performance ของ B จะดีกว่าเกือบเท่าตัว

Distinct On vs Partition

เมื่อต้องการเลือกข้อมูล record ล่าสุด หรือ record แรกตามกลุ่มที่แบ่งไว้ การใช้คำสั่ง Distinct on จะเร็วกว่าการทำ Partitions ( window function )

Slow Query (A):

SELECT employee_id, branch_id FROM (
SELECT
ROW_NUMBER() OVER( partition by employee_id ORDER BY employee_branch.created_at) as row_num,
employee_id,
branch_id
FROM employee_branch
) AS twr
WHERE row_num = 1

Optimized Query (B):

SELECT 
DISTINCT ON ( employee_id)
employee_id,
branch_id
FROM employee_branch
ORDER BY employee_branch.created_at

จาก SQL ด้านบน ทั้งสองแบบมีผลลัพธ์แบบเดียวกัน แต่ performance ของ B จะดีกว่า

  • *ใช้ได้แค่กรณีที่ต้องการเลือก record แรก หรือสุดท้ายเท่านั้น

มี correlated subqueries ให้น้อยที่สุด

Slow Query (A):

SELECT employee_number, name
FROM employees emp
WHERE salary > (
SELECT AVG(salary)
FROM employees
WHERE department = emp.department);

จาก SQL ข้างต้น ใน sub query จะมีการเรียก emp.department ซึ่ง emp นั้นเป็น table ที่อยู่นอก sub query เพราะฉนั้น sub query จะถูกคำนวณใหม่ทุกครั้ง ตามจำนวน record ของ query หลัก ทำให้ performance แย่มากๆ

Optimized Query (B):

SELECT employee_number, name
FROM employees emp
JOIN (
SELECT
AVG(salary) as salary
employee_number
FROM employees
GROUP BY employee_number
) as emp_avg_salary
ON emp_avg_salary.employee_number = emp.employee_number
WHERE emp.salary > emp_avg_salary.salary

TIP: เปลี่ยนไปใช้ JOIN แทนสิ!

EXISTS statement

การใช้ EXISTS statement เพื่อหาว่ามีข้อมูลนั้นอยู่จริงรึเปล่า จะเร็วกว่าการใช้ JOIN ในการหา เนื่องจาก JOIN จะเช็คข้อมูลทั้งหมดว่ามีข้อมูลซ้ำเท่าไหร่ แต่ EXISTS จะเช็คแค่ว่ามีข้อมูลนั้นรึเปล่า และจะหยุดหาต่อเมื่อเจอข้อมูลที่กำลังหาอยู่

//เช่น

SELECT EXISTS(SELECT 1 FROM employee where gender = 'male');

จาก SQL ข้างต้น ระบบจะหยุดหาเมื่อเจอ gender = ‘male’ record แรก

TIP: ใช้เมื่อต้องการหาว่ามีข้อมูลที่ต้องการอยู่ในตารางนั้นๆหรือไม่ โดยไม่สนว่าจะมีเท่าไหร่

--

--