ບັນຫາ N+1 Query

ບັນຫາ N+1 Query

ບັນຫາ N+1 Query ແມ່ນຮູບແບບຕ້ານປະສິດທິພາບ (anti-pattern) ດ້ານປະສິດທິພາບ ທີ່ເກີດຂຶ້ນເມື່ອດຶງຂໍ້ມູນລາຍການດ້ວຍ query ຄັ້ງດຽວ ແລ້ວຕາມດ້ວຍການດຶງຂໍ້ມູນທີ່ກ່ຽວຂ້ອງຂອງແຕ່ລະ record ທີລະລາຍການແຍກກັນ ສົ່ງຜົນໃຫ້ມີການເຂົ້າເຖິງຖານຂໍ້ມູນທັງໝົດ N+1 ຄັ້ງ.

"N" ແລະ "1" ໃນ N+1

ເມື່ອໃຊ້ ORM (Object-Relational Mapping) ຫຼື query builder, ມັກຈະເກີດບັນຫານີ້ໂດຍບໍ່ຮູ້ຕົວ. ຮູບແບບຂອງມັນເປັນດັ່ງນີ້:

  1. ດຶງລາຍການຈາກ parent table — ນີ້ຄືການ query ທີ່ເອີ້ນວ່າ "1"
  2. ສຳລັບແຕ່ລະລາຍການທີ່ດຶງໄດ້ N ລາຍການ, ສອບຖາມຂໍ້ມູນທີ່ກ່ຽວຂ້ອງຈາກ child table — ນີ້ຄື query ທີ່ເຮັດ "N" ຄັ້ງ

ຍົກຕົວຢ່າງ, ລອງຄິດເຖິງກໍລະນີທີ່ສະແດງໜ້າລາຍການຄຳສັບ. ທຳອິດ SELECT 50 ລາຍການຈາກ table glossaries, ຈາກນັ້ນດຶງຂໍ້ມູນການແປຂອງແຕ່ລະຄຳສັບຈາກ table translations ທີລະລາຍການ, ລວມແລ້ວຈະມີ query ທັງໝົດ 51 ຄັ້ງ. ຖ້າຈຳນວນລາຍການເພີ່ມເປັນ 500, ກໍຈະເຮັດ 501 ຄັ້ງ.

ເປັນຫຍັງຈຶ່ງຮ້າຍແຮງ

ເຖິງແມ່ນວ່າແຕ່ລະ query ຈະຕອບສະໜອງພາຍໃນໄລຍະສອງສາມ millisecond, ແຕ່ເນື່ອງຈາກຈຳນວນຄັ້ງເພີ່ມຂຶ້ນແບບ linear, response time ຈຶ່ງຊຸດໂຊມລົງຕາມສັດສ່ວນຂອງປະລິມານຂໍ້ມູນ. table ທີ່ໃນ local development ມີພຽງສອງສາມສິບລາຍການ ອາດຂະຫຍາຍເປັນຫຼາຍພັນລາຍການໃນ production ແລ້ວຄ່ອຍສະແດງໃຫ້ເຫັນຄວາມຊ້າ — ອຸບັດຕິເຫດແບບນີ້ບໍ່ແມ່ນເລື່ອງແປກ.

ຍິ່ງໄປກວ່ານັ້ນ, ໃນສະພາບແວດລ້ອມທີ່ DB server ແລະ application server ຖືກແຍກອອກຈາກກັນຜ່ານ network, overhead ຂອງ round trip ຈະສະສົມຕາມຈຳນວນຄັ້ງ. ເຖິງແມ່ນ query ເອງຈະເບົາ, ແຕ່ network latency ກໍຈະກາຍເປັນຕົວຊີ້ຂາດ.

ວິທີການແກ້ໄຂ

ວິທີແກ້ໄຂທີ່ເປັນຕົວແທນສາມາດແບ່ງອອກເປັນ 2 ປະເພດຫຼັກ ຄື eager loading (ການໂຫຼດລ່ວງໜ້າ) ແລະ JOIN.

ວິທີການຕົວຢ່າງ frameworkສະຫຼຸບ
eager loadingincludes ຂອງ Rails / select_related ຂອງ Django / include ຂອງ PrismaORM ສ້າງ query ສຳລັບດຶງຂໍ້ມູນທີ່ກ່ຽວຂ້ອງໂດຍອັດຕະໂນມັດ
explicit JOINraw SQL / .select("*, children(*)") ຂອງ Supabaseສົ່ງຄືນ parent ແລະ child ໂດຍການ join ໃນ query ດຽວ
DataLoader patternDataLoader ຂອງ GraphQL / cache() ຂອງ Next.jsລວບລວມ key ພາຍໃນ request ແລ້ວປ່ຽນເປັນ batch query

ໃນກໍລະນີທີ່ໃຊ້ Supabase, ການໃຊ້ embedded query ຂອງ PostgREST (select("*, translations(*)")) ຊ່ວຍໃຫ້ດຶງຂໍ້ມູນທີ່ກ່ຽວຂ້ອງໄດ້ໃນ 1 request ໂດຍບໍ່ຕ້ອງຂຽນ loop ຢູ່ຝ່າຍ application.

ການກວດຫາ ແລະ ການປ້ອງກັນ

ເພື່ອກວດຫາ N+1 ໃນຂັ້ນຕອນການພັດທະນາ, ການຕິດຕາມ query log ແມ່ນມີປະສິດທິພາບ. ສຳລັບ PostgreSQL, ສາມາດຕັ້ງ log_min_duration_statement ເປັນ 0 ເພື່ອ log query ທັງໝົດ, ແລ້ວກວດສອບວ່າມີ query ທີ່ມີຮູບແບບດຽວກັນຕໍ່ເນື່ອງກັນຫຼືບໍ່. ສຳລັບ Rails ມີ bullet gem, ສຳລັບ Django ມີ django-debug-toolbar ເປັນເຄື່ອງມືກວດຫາ.

ພຽງແຕ່ໃສ່ໃຈໃນຂັ້ນຕອນ code review ວ່າ "ມີ await ຢູ່ພາຍໃນ loop ຫຼືບໍ່", ກໍສາມາດປ້ອງກັນ N+1 ໄດ້ຫຼາຍກໍລະນີ.