این داستان: وقتی خودمون با دست خودمون N+1 میسازیم!
یکی از مهمترین موضوعات زمانی که داریم اطلاعات رو از دیتابیس میخونیم، هزینههای پنهانی هستش که خودمون در هنگام نوشتن کوئری ایجاد میکنیم و حواسمون نیست. به کوئری زیر دقت و فرض کنید اپلیکیشنی مشابه اینستاگرام داریم و میخوایم خیلی ساده در یک کوئری پستها و تعداد لایکها رو از دیتابیس فراخوانی کنیم.
SELECT
posts.id,
posts.caption,
posts.content_url,
COALESCE(
(
SELECT COUNT(*)
FROM post_reactions
WHERE post_reactions.post_id = posts.id
),
0
) AS reactions_count
FROM
posts LEFT JOIN post_reactions ON post_reactions.post_id = posts.id
اگر با بعضی واژهها در کوئری بالا آشنا نیستید مهم نیست، هدف ما بررسی سابکوئری:
SELECT COUNT(*)
FROM post_reactions
WHERE post_reactions.post_id = posts.id
سابکوئری بالا رو میشه با روشهای دیگهای هم نوشت و تقریبا همه ما عادت کردیم در بسیاری از موارد وقتی میخوایم دیتایی مرتبط با نتایج مورد نظرمون از جدولهای دیگه فراخوانی کنیم سریعا سراغ سابکوئریها بریم.
اما باید دقت کنیم که این سابکوئریها در دل خودشون یک N+1 ایجاد میکنن و به ازای تکتک ریفهایی که فراخوانی کردیم، تکرار میشن و بهراحتی ما رو در رکوردهای بالا دچار مشکل میکنن!
راهحل چنین موضوعاتی استفاده از pre aggregating و CTE هستش.
بهتره در چنین مواقعی با استفاده از WITH در PostgreSQL یا MySQL یا MariaDB از تکرار کوئریها بهصورت پنهان در Joinها جلوگیری کنیم. نسخه اصلاح شده کوئری:
WITH
reaction_counts AS (
SELECT post_id, COUNT(*) AS reactions_count
FROM post_reactions
GROUP BY
post_id
),
SELECT
posts.id,
posts.caption,
posts.content_url,
reaction_counts.reactions_count
FROM
LEFT JOIN reaction_counts ON reaction_counts.post_id = posts.id
کوئری بالا هم خوانایی بیشتری داره و هم پرفورمنس بهتری! کاری که هیچ ORM درکی از انجامش نداره!
#episode_0#story#tip