ბიბლიოთეკა
00/07 · ~38 წთ
GUIDEDECK · PART 2 · როცა შეკითხვა სწრაფი უნდა იყოს

გაღრმავებული SQL
ფანჯრის ფუნქციები, გეგმები
და ძრავა, რომელიც ქვემოთ დგას.

38-წუთიანი ღრმა სესია, რომელიც იქიდან აგრძელებს, სადაც შესავალი დეკი შეწყდა. ვვარაუდობთ, რომ იცით join-ები, გასაღებები, ACID და რა არის ინდექსი — ეს კი ქვედა შრეა: ანალიტიკა SQL-ში, EXPLAIN ANALYZE-ის კითხვა, ინდექსების სტრატეგია, MVCC და იზოლაცია, და ცხრილის მასშტაბირება ერთი მანქანის მიღმა. თუ საფუძვლები ბუნდოვანია, ჯერ მონაცემთა ბაზები & SQL გაიარეთ.

~38 წთსაშუალო → გაღრმავებულიPostgres-ის სტილში
გადაახვიეთ
01 · ფანჯრის ფუნქციები 6 წთ

აგრეგირება სტრიქონების
შეკუმშვის გარეშე.

ეს არის მონაცემთა ბაზები & SQL-ის მე-2 ნაწილი — ამიტომ GROUP BY-ის საფუძვლებს გამოვტოვებთ და პირდაპირ იმ ინსტრუმენტზე გადავდივართ, რომელიც SQL-ს ანალიტიკურ ძრავად აქცევს. ფანჯრის ფუნქცია ითვლის მნიშვნელობას სტრიქონების იმ ნაკრებზე, რომელიც მიმდინარე სტრიქონს უკავშირდება, და მაინც აბრუნებს ყველა სტრიქონს — მზარდი ჯამები, რანჟირება და სტრიქონიდან სტრიქონამდე სხვაობები self-join-ის გარეშე.

ფანჯრის ფუნქცია — აგრეგატი ან რანჟირება, გამოთვლილი window-ზე: სტრიქონების ჩარჩოზე, რომელსაც OVER (PARTITION BY … ORDER BY …) განსაზღვრავს. GROUP BY-სგან განსხვავებით, რომელიც ბევრ სტრიქონს ერთში კეცავს, ფანჯარა სტრიქონებს ხელუხლებლად ტოვებს და მათ გვერდით გამოთვლილ სვეტს ამატებს. დანაწილება მონაცემებს დამოუკიდებელ ჯგუფებად ჭრის; ჩარჩო კი არჩევს, დანაწილების შიგნით რომელ სტრიქონებს ხედავს ფუნქცია.
SELECT user_id, placed_at, amount, SUM(amount) OVER ( PARTITION BY user_id -- one running total per user ORDER BY placed_at -- ordered within the user ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -- the frame ) AS running_spend, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY placed_at) AS nth_order FROM orders;
PARTITION user=42 10 → 10 25 → 35 8 → 43 ◂ current 15 (not yet) frame = start … current PARTITION user=99 resets → 0

თითოეული მომხმარებელი საკუთარი დანაწილებაა; ჩარჩო პირველი სტრიქონიდან მიმდინარემდე იზრდება, ამიტომ ჯამი გროვდება და ყოველ მომხმარებელზე ნულდება.

ფუნქციები, რომლებიც უნდა დაიმახსოვროთ

რანჟირება

პოზიცია ჯგუფში

ROW_NUMBER ყოველთვის უნიკალურია; RANK ტოლების შემდეგ ხარვეზებს ტოვებს (1,1,3); DENSE_RANK — არა (1,1,2). NTILE(4) კვარტილებად ყოფს.

ოფსეტი

შეხედეთ მეზობელს

LAG(x) კითხულობს წინა სტრიქონს, LEAD(x) — მომდევნოს; იდეალურია სხვაობებისთვის (ეს თვე გასულთან შედარებით) კორელირებული ქვეშეკითხვის გარეშე.

აგრეგატი

ნებისმიერი აგრეგატი ფანჯარაში

SUM, AVG, COUNT, MAX — ყველა იღებს OVER კონსტრუქციას: მზარდი ჯამები, მოძრავი საშუალოები, ჯგუფში წილი.

value

ჩარჩოს კიდეები

FIRST_VALUE, LAST_VALUE, NTH_VALUE ჩარჩოში კონკრეტული პოზიციიდან იღებს სტრიქონს — თვალი ადევნეთ ჩარჩოს, თორემ LAST_VALUE გაგაკვირვებთ.

ორი ხაფანგი, რომელიც შესავალ დეკში არ იყო. პირველი: ფანჯრის ფუნქციები მას შემდეგ სრულდება, რაც WHERE, GROUP BY და HAVING გაიარა, ამიტომ იმავე შეკითხვაში მათზე ფილტრს ვერ დაადებთ — მოაქციეთ CTE-ში ან ქვეშეკითხვაში და გარედან გაფილტრეთ. მეორე: როცა ORDER BY გაქვთ, ჩარჩო კი აშკარად არ მიგითითებიათ, ნაგულისხმევია RANGE და არა ROWS — ეს კი ერთი და იმავე დალაგების გასაღების მქონე ყველა თანატოლ სტრიქონს მიმდინარე ჩარჩოში კრებს. ნამდვილი სტრიქონ-სტრიქონ მზარდი ჯამისთვის პირდაპირ დაწერეთ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
02 · CTE და რეკურსია 5 წთ

დაასახელეთ ქვეშეკითხვა — და
მიეცით საშუალება, თავს დაუძახოს.

CTE (Common Table Expression — WITH კონსტრუქცია) ქვეშეკითხვას სახელს არქმევს, რომ რთული შეკითხვა ზემოდან ქვემოთ იკითხებოდეს და არა შიგნიდან გარეთ. გაღრმავებული მოგება რეკურსიაა: CTE, რომელიც საკუთარ თავს მიმართავს — ასე გაივლით ხეებსა და გრაფებს (ორგანიზაციული სქემები, კატეგორიების იერარქიები, დამოკიდებულებების DAG-ები) ერთი ინსტრუქციით.

რეკურსიული CTE — WITH RECURSIVE t AS (anchor UNION ALL recursive-member). საწყისი წევრი იძლევა პირველ სტრიქონებს; რეკურსიული წევრი კი ხელახლა სრულდება წინა ნაბიჯის მიერ დაბრუნებულ სტრიქონებზე, სანამ ახალ სტრიქონს აღარ დაამატებს. UNION (UNION ALL-ისგან განსხვავებით) დუბლიკატებს აშორებს და იაფი დაცვაა ციკლურ მონაცემებზე უსასრულო მარყუჟისგან.
WITH RECURSIVE reports AS ( -- anchor: the boss, depth 0 SELECT id, name, manager_id, 0 AS depth FROM employees WHERE manager_id IS NULL UNION ALL -- recursive: everyone reporting to a known row SELECT e.id, e.name, e.manager_id, r.depth + 1 FROM employees e JOIN reports r ON e.manager_id = r.id ) SELECT * FROM reports ORDER BY depth;

ყოველი იტერაცია ცხრილს ერთი დონით ზემოთ ნაპოვნ სტრიქონებს უერთებს — შეკითხვა თითო შრეს შლის მანამ, სანამ დაქვემდებარებული აღარავინ დარჩება.

წაკითხვადობა

ღობე გაქრა

Postgres 12-მდე CTE ოპტიმიზაციის ღობე იყო — ყოველთვის მატერიალიზდებოდა. თანამედროვე Postgres მარტივ CTE-ს მთავარ გეგმაში ჩასვამს; ორივე მხარეს აიძულებთ AS MATERIALIZED / AS NOT MATERIALIZED-ით.

ციკლები

დაიცავით მარყუჟებისგან

გრაფებს ციკლები შეიძლება ჰქონდეს. გამეორებების მოსაშორებლად გამოიყენეთ UNION, ან SQL-ის სტანდარტული CYCLE … SET … USING კონსტრუქცია (Postgres 14+), რომ ხელახლა მონახულებული ნოუდი აღმოაჩინოთ და გაჩერდეთ.

ძრავები

თითქმის ყველგან მუშაობს

WITH RECURSIVE სტანდარტული SQL-ია — Postgres, SQL Server, Oracle, თანამედროვე MySQL 8+ და SQLite ყველა უჭერს მხარს. CYCLE/SEARCH სინტაქსური შაქარი კი ძრავების მიხედვით განსხვავდება.

03 · გეგმების კითხვა 6 წთ

EXPLAIN ANALYZE ერთადერთი
აზრია, რომელსაც მნიშვნელობა აქვს.

შესავალმა დეკმა Seq Scan და Index Scan გაჩვენათ. ახლა უფრო ღრმად: რომელი join-ის ალგორითმი აირჩია დამგეგმავმა, რატომ და როგორ ამოიცნოთ გეგმა, რომელიც ჩავარდნის ზღვარზეა. დამგეგმავი ღირებულების მოდელია — ის გამოიცნობს; თქვენი საქმეა, დაიჭიროთ, როცა არასწორად გამოიცნობს.

შეკითხვის გეგმა — ფიზიკური ოპერატორების ხე, რომელსაც დამგეგმავი ირჩევს თქვენი SQL-ის შესასრულებლად. EXPLAIN ბეჭდავს გეგმას სავარაუდო სტრიქონებითა და ღირებულებით; EXPLAIN ANALYZE კი მართლა უშვებს მას და ფაქტობრივ სტრიქონებსა და დროს ბეჭდავს. დაამატეთ BUFFERS, რომ ნახოთ, გვერდი ქეშიდან წაიკითხა თუ დისკიდან (Postgres 18-ში ის ნაგულისხმევად შედის ANALYZE-თან ერთად). ხე წაიკითხეთ ქვემოდან ზემოთ, შიგნიდან გარეთ.

ორი ცხრილის შეერთების სამი გზა

nested loop

ყოველი გარე სტრიქონისთვის — ძებნა შიგნით

გადის გარე სტრიქონებს და თითოეულს შიდაში ეძებს — იდეალურად ინდექსით. იგებს, როცა გარე მხარე პატარაა. კვდება, როცა ორივე მხარე დიდია (სტრიქონი × სტრიქონი).

hash join

ააგე ჰეში, მერე მოძებნე

პატარა შემავალზე აგებს ჰეშ-ცხრილს და დიდს მის გვერდით ატარებს. იგებს დიდ, დაულაგებელ ტოლობით შეერთებებზე. ხარჯავს მეხსიერებას — work_mem-ს გადაცილებისას პარტიებად დისკზე იღვრება.

merge join

ორი დალაგებული ნაკადის შეკვრა

ორივე შემავალს დალაგებული თანმიმდევრობით გადის და ელვასავით კრავს. იგებს, როცა შემავალი უკვე დალაგებულია (ინდექსი ან დიაპაზონი). იხდის დახარისხების ფასს, თუ არა.

EXPLAIN (ANALYZE, BUFFERS) SELECT ... FROM orders o JOIN users u ON u.id = o.user_id WHERE o.placed_at > now() - '30 days'; Nested Loop (cost=.. rows=12) -- შეფასება: 12 -> Seq Scan on orders -- ინდექსი არ გამოიყენა rows=480000 actual rows=480000 -- შეფასება მკვეთრად ცდება -> Index Scan on users (loops=480000) Planning 0.3 ms · Execution 3 200 ms

როგორ მივხვდეთ, რომ გეგმა ცუდია

  • შეფასება და ფაქტი — დამგეგმავი 12 სტრიქონს ელოდა, მიიღო 480,000. სტატისტიკა მოძველებულია; გასაახლებლად გაუშვით ANALYZE.
  • არასწორი join — რაკი "12 სტრიქონი" დაუჯერა, nested loop აირჩია და შიდა ინდექსში 480 000-ჯერ მოძებნა.
  • Seq Scan გაფილტრულ თარიღზე — placed_at-ზე ინდექსი აკლია.
  • ნიშანი: მაღალი loops, დიდი ფაქტობრივი სტრიქონების რიცხვი და Sort ან ჰეში, რომელიც დისკზე გადაიღვარა.

ინსტრუმენტების ლანდშაფტი

Postgres-ის სტილში; უმეტეს ძრავას თავისი ეკვივალენტი აქვს. თითო დადებითი, თითო უარყოფითი და როდის მივმართოთ თითოეულს.

EXPLAIN / EXPLAIN ANALYZE

ერთი შეკითხვა, რომელიც წინაშე გაქვთ

დადებითი: ჭეშმარიტება — ფაქტობრივი სტრიქონები, დრო და ბუფერები ერთი ინსტრუქციისთვის. უარყოფითი: ANALYZE მართლა უშვებს შეკითხვას (ჩაწერები მოაქციეთ ტრანზაქციაში, რომელსაც მერე rollback უკეთდება).

  • მიმართეთ  როცა უკვე იცით, რომელი შეკითხვაა ნელი.
pg_stat_statements

რომელი შეკითხვაა ნელი

დადებითი: მთელ სერვერზე აჯამებს საერთო დროს, გამოძახებებსა და საშუალოს ნორმალიზებული შეკითხვის მიხედვით. უარყოფითი: გაფართოებაა, რომელიც უნდა ჩართოთ; აჩვენებს,რა არის ნელი და არა რატომ.

  • მიმართეთ  რომ მთავარი დამნაშავეები იპოვოთ, სანამ რამეს მოარგებთ.
auto_explain

გეგმები, რომლებიც ხელით არ გაგიშვიათ

დადებითი: პროდაქშენში ლოგავს ზღვარზე ნელი ნებისმიერი შეკითხვის რეალურ გეგმას. უარყოფითი: ლოგების მოცულობა და მცირე დანამატი; ზღვარი ფრთხილად მოარგეთ.

  • მიმართეთ  რომ დაიჭიროთ დროგამოშვებით ნელი გეგმები, რომელთა გამეორებაც არ გამოგდით.
pgMustard · explain.dalibo

გეგმის ვიზუალიზაცია & შეფასება

დადებითი: ტექსტის კედელს ხედ აქცევს, ძვირ ნოუდს მონიშნავს, გამოსწორებას შემოგთავაზებთ. უარყოფითი: იმდენად კარგია, რამდენადაც ჩასმული გეგმა; სერვერის მასშტაბის სურათს არ იძლევა.

  • მიმართეთ  როცა გეგმა იმდენად დიდია, რომ თავში ვერ იკითხავთ.
04 · ინდექსის სტრატეგია 6 წთ

სწორ ინდექსს იგივე ფორმა
აქვს, რაც თქვენს შეკითხვას.

იცით, რა არის B-ხის ინდექსი. ოსტატობა ფორმის არჩევაშია: რომელი სვეტები, რა თანმიმდევრობით, რა დამატებებით — და იმის ცოდნაში, რომ ყოველი დამატებული ინდექსი გადასახადია ყოველ ჩაწერაზე. სამი პატერნი მოგებების უმეტესობას ფარავს.

კომპოზიტური

თანმიმდევრობა: ტოლობა → დიაპაზონი → დახარისხება

(a, b, c)-ში მხოლოდ მარცხენა პრეფიქსი გამოდგება. ჯერ ის სვეტები დადეთ, რომლებსაც =-ით ამოწმებთ, მერე ერთი დიაპაზონის სვეტი, მერე ORDER BY-ის სვეტი. მხოლოდ b-ზე ფილტრი მას ვერ გამოიყენებს.

CREATE INDEX ON orders (user_id, status, placed_at); -- serves WHERE user_id=? AND status=? -- ORDER BY placed_at
მფარავი

პასუხი მხოლოდ ინდექსიდან

INCLUDE-ით დაამატეთ არა-გასაღები სვეტები, რომ ინდექსში ყველაფერი იყოს, რასაც შეკითხვა ირჩევს. დამგეგმავი index-only scan-ს აკეთებს — heap-ს საერთოდ არ ეხება (როცა visibility map ახალია).

CREATE INDEX ON orders (user_id) INCLUDE (amount, status); -- SELECT amount,status WHERE user_id=? -- → Index Only Scan, no heap fetch
ნაწილობრივი

დააინდექსეთ მხოლოდ ის სტრიქონები, რომლებსაც ეკითხებით

თავად ინდექსზე დადებული WHERE მას პატარად და იაფად ინახავს. კლასიკაა ცხელი ქვესიმრავლისთვის — ღია შეკვეთები, რბილად წაშლილი სტრიქონები, ერთი ტენანტი — სადაც სტრიქონების უმეტესობა უმნიშვნელოა.

CREATE INDEX ON orders (placed_at) WHERE status = 'open'; -- tiny: indexes ~2% of the table

ყოველი ინდექსი დამატებითი სტრუქტურაა, რომელიც ძრავამ სინქრონში უნდა შეინარჩუნოს — ამიტომ თითოეული ანელებს ჩამატებას, განახლებასა და წაშლას.

როდის ვნებს ინდექსი

  • ჩაწერის გამრავლება — ერთი INSERT იქცევა ერთ heap-ჩაწერად პლუს ჩაწერად ცხრილის ყოველ ინდექსში.
  • HOT განახლებებს შლის — Postgres იაფ heap-only-tuple განახლებას მხოლოდ მაშინ აკეთებს, თუ არცერთი დაინდექსებული სვეტი არ შეცვლილა. დააინდექსეთ ხშირად განახლებადი სვეტი და ინდექსის არევასა და გაბერვას იძულებით იღებთ.
  • მკვდარი წონა — ინდექსი, რომელსაც არავინ ეკითხება, მაინც ხარჯავს დისკს, ქეშსა და ჩაწერის დროს. წაშალეთ გამოუყენებელი (შეამოწმეთ pg_stat_user_indexes).
  • სწორი ტიპი მონაცემებისთვის — B-ხე ნაგულისხმევია, მაგრამ GIN ერგება jsonb-ს, მასივებსა და სრულ ტექსტს, BRIN კი უზარმაზარ, ბუნებრივად დალაგებულ ცხრილებს, როგორიცაა დროის სერიები.
  • ჯერ ტოლობის სვეტები (დამგეგმავს პირდაპირ მათზე გადახტომა შეუძლია), მერე ერთი დიაპაზონის/უტოლობის სვეტი, მერე დახარისხების სვეტი.
  • დიაპაზონის სვეტი ინდექსს "ხარჯავს" — მის შემდეგ მოსულ სვეტებზე ძებნა აღარ ხდება, მხოლოდ ფილტრაცია.
  • ნუ გაამრავლებთ ერთსა და იმავე სვეტებს ბევრ გადამფარავ ინდექსში; ერთი კარგად დალაგებული კომპოზიტური ჩვეულებრივ სამ ერთსვეტიანს სჯობს.
  • ყოველთვის დაადასტურეთ EXPLAIN-ით — ინდექსი, რომელიც არ გამოიყენება, უარესია, ვიდრე მისი არარსებობა.
05 · ტრანზაქციები და კონკურენტულობა 6 წთ

იზოლაცია სახელურია
სისწორესა და კონკურენციას შორის.

შესავალმა დეკმა ACID მოგცათ. ახლა I-ს რთული ნაწილი: რის დანახვის უფლება აქვს რეალურად პარალელურ ტრანზაქციებს, როგორ აღწევს ამას Postgres მკითხველების დაბლოკვის გარეშე და რა ჩავარდნები — დაკარგული განახლებები, ჩაწერის დახრა, დედლოკები — გელოდებათ მასშტაბზე.

MVCC — Multi-Version Concurrency Control: ყოველი ჩაწერა ქმნის სტრიქონის ახალ ვერსიას და არ გადააწერს ადგილზე. თითოეულ ვერსიას თან ახლავს ტრანზაქციების ID-ები, რომლებმაც ის შექმნეს და წაშალეს (xmin/xmax); ტრანზაქცია ხედავს მხოლოდ იმ ვერსიებს, რომლებიც მისი სნეპშოტისთვის ვარგისია. შედეგი: მკითხველები არასოდეს ბლოკავენ მწერლებს და მწერლები — მკითხველებს. ფასი: მკვდარი ვერსიები გროვდება და VACUUM-მა უნდა დაიბრუნოს ისინი.

იზოლაციის ოთხი დონე

  • Read Committed (Postgres-ის ნაგულისხმევი) — ყოველი ინსტრუქცია ხედავს ბოლო დაკომიტებულ მონაცემებს. აჩერებს ბინძურ წაკითხვებს; მაინც უშვებს არაგამეორებად და ფანტომურ წაკითხვებს.
  • Repeatable Read — პირველ ინსტრუქციაზე გაყინული სნეპშოტი. Postgres-ში ეს ნამდვილი სნეპშოტ-იზოლაციაა: ფანტომები არაა, მაგრამ ჩაწერის დახრა (write skew) გაუსხლტება.
  • Serializable — იქცევა ისე, თითქოს ტრანზაქციები სათითაოდ სრულდებოდა. Postgres იყენებს SSI-ს და შეიძლება ტრანზაქცია სერიალიზაციის შეცდომით შეწყვიტოს, რომელიც უნდა გაიმეოროთ.
  • Read Uncommitted — სტანდარტში ბინძურ წაკითხვებს უშვებს; Postgres-ს ნამდვილი ბინძური წაკითხვა არ აქვს, ამიტომ ის Read Committed-ივით იქცევა.
Read Committed
ბინძური კითხვა ✓
Repeatable Read
+ არაგამეორებადი, ფანტომი ✓
Serializable
+ ჩაწერის დახრა ✓
აღკვეთს →
ჩაწერის დახრა Repeatable Read-ს უსხლტება —
მას მხოლოდ Serializable იჭერს

მაღალი დონეები მეტ ანომალიას აღკვეთს — და მეტ შეწყვეტასა და კონკურენციაში დგომას იწვევს. აირჩიეთ ყველაზე დაბალი დონე, რომელიც ჯერ კიდევ სწორია.

-- Txn A -- Txn B UPDATE acct SET ... UPDATE acct SET ... WHERE id=1; WHERE id=2; -- A-ს ახლა id=2 უნდა -- B-ს ახლა id=1 უნდა UPDATE ... WHERE id=2; UPDATE ... WHERE id=1; -- თითოეული იმას იკავებს, რაც მეორეს სჭირდება → დედლოკი -- Postgres ამას აღმოაჩენს და ერთ ტრანზაქციას წყვეტს

ლოკები, დედლოკები & რიგები

  • დედლოკი — ორი ტრანზაქცია ლოკებს საპირისპირო თანმიმდევრობით იღებს. წამალი დისციპლინაა: სტრიქონები ერთი და იმავე თანმიმდევრობით აიღეთ (მაგალითად, id-ის ზრდადობით). Postgres ციკლს ერთ-ერთის შეწყვეტით არღვევს.
  • დაკარგული განახლება — წაკითხვა-შეცვლა-ჩაწერა ლოკის გარეშე. სტრიქონის ლოკისთვის გამოიყენეთ SELECT … FOR UPDATE, ან ამოიცანით ოპტიმისტური ვერსიის სვეტით.
  • სამუშაოების რიგები — FOR UPDATE SKIP LOCKED ბევრ ვორკერს აძლევს საშუალებას, სხვადასხვა სტრიქონი აიღოს ერთმანეთის დაბლოკვის გარეშე.
  • ტრანზაქციები მოკლე შეინახეთ — გრძელი ტრანზაქციები ლოკებს იკავებს, VACUUM-ს ბლოკავს და ცხრილს ბერავს.
06 · დანაწილება და შარდინგი 5 წთ

ერთი დიდი ცხრილი კარგია —
სანამ აღარაა.

ერთი და იმავე იდეის ორი ჭრილი: გაყავით მონაცემები. დანაწილება ერთ ცხრილს ნაწილებად ჭრის ერთ მანქანაზე; შარდინგი კი მას ბევრ მანქანაზე ანაწილებს. ისინი სხვადასხვა პრობლემას წყვეტს და ღირებულებაც ძალიან განსხვავებული აქვს.

დანაწილება — ერთი ლოგიკური ცხრილი, ფიზიკურად რამდენიმე შვილობილ ცხრილად შენახული და გასაღებით გაყოფილი. დეკლარაციული დანაწილება (Postgres 10+) უჭერს მხარს RANGE-ს (თარიღით), LIST-ს (რეგიონით) და HASH-ს. დამგეგმავი აკეთებს დანაყოფების გასხვლას — გამოტოვებს იმ დანაყოფებს, რომლებსაც WHERE ვერ ემთხვევა.
  • რატომ: ძველი მონაცემები დანაყოფის წაშლით ქრება (მყისიერად, DELETE-ის გარეშე); თითო დანაყოფის ინდექსი პატარაა; არარელევანტური დანაყოფები დაგეგმვისასვე იჭრება.
  • ყურადღება: დანაწილების გასაღები შეკითხვების უმეტესობაში უნდა იყოს, თორემ ყოველ დანაყოფს დაასკანირებთ. ეს მაინც ერთი სერვერია.
შარდინგი — ჰორიზონტალური დანაწილება დამოუკიდებელ მონაცემთა ბაზის სერვერებზე, სადაც თითოეული სტრიქონების ერთ ქვესიმრავლეს ინახავს. შარდის გასაღები წყვეტს, რომელ ნოუდს ეკუთვნის სტრიქონი. სწორედ ასე მასშტაბირდება ჩაწერა და საცავი ერთი მანქანის მიღმა.
  • რატომ: ვერცერთი ცალკეული მანქანა ვერ იტევს მონაცემებს ან ჩაწერის მოცულობას; ჰორიზონტალური მასშტაბი გჭირდებათ.
  • ღირებულება: შარდებს შორის join-ები და ტრანზაქციები რთულია, გადანაწილება მტკივნეულია, ცუდი შარდის გასაღები კი ცხელ წერტილებს ქმნის. ნუ შარდავთ, სანამ არ მოგიწევთ.
orders (ლოგიკური)
ნოუდი A
id % 3 = 0
ნოუდი B
id % 3 = 1
ნოუდი C
id % 3 = 2
2024
2025
2026
დანაწილება · ერთი სერვერი
შარდინგი · ბევრი სერვერი

დანაწილება = შვილობილი ცხრილები ერთი სერვერის ქვეშ. შარდინგი = სტრიქონები, გაბნეული დამოუკიდებელ სერვერებზე შარდის გასაღების მიხედვით.

ძრავების ლანდშაფტი (2026)

  • Postgres — მშობლიური დეკლარაციული დანაწილება; შარდინგი Citus გაფართოებით ან აპლიკაციის დონეზე მარშრუტიზაციით.
  • MySQL — ჩაშენებული დანაწილება; Vitess შარდინგის სტანდარტული შრეა (მასზე დგას ძალიან დიდი MySQL ფლოტები).
  • ნაგულისხმევად განაწილებული — CockroachDB, YugabyteDB და Spanner თავად შარდავს და არეპლიცირებს SQL ინტერფეისის მიღმა და ცოტა შეყოვნებას ცვლის ავტომატურ ჰორიზონტალურ მასშტაბში.
07 · ოპტიმიზაციის მაგალითი + შეჯამება 4 წთ

შევკრათ ერთად:
ერთი ნელი შეკითხვა, გასწორებული.

დაშბორდის შეკითხვა ტაიმაუტში ვარდება. გაიარეთ ის ციკლი, რომელსაც მთელი კარიერის განმავლობაში გამოიყენებთ: გაზომე → წაიკითხე გეგმა → შეცვალე ერთი რამ → ხელახლა გაზომე.

მანამდე — 3.2 s
-- "latest order per active user, last 30d" SELECT u.id, o.amount, o.placed_at FROM users u JOIN orders o ON o.user_id = u.id WHERE u.status = 'active' AND o.placed_at > now() - '30 days' ORDER BY o.placed_at DESC; Seq Scan on orders · Sort spills to disk
შემდეგ — 18 ms
-- 1. partial composite index, sorted CREATE INDEX ON orders (user_id, placed_at DESC) INCLUDE (amount); -- 2. partial index for the active subset CREATE INDEX ON users (id) WHERE status = 'active'; -- 3. ANALYZE so estimates are fresh ANALYZE orders, users; -- → Index Only Scan, no sort, ~180x faster

ხუთი ნაბიჯი

  • დააინდექსეთ წვდომის პატერნზე — კომპოზიტური (user_id, placed_at DESC) ერთდროულად ემსახურება join-საც და დახარისხებასაც და დისკზე დახარისხებას კლავს.
  • დაფარეთ შეკითხვა — INCLUDE (amount) მას index-only scan-ად აქცევს, ამიტომ heap-ს საერთოდ აღარ სტუმრობს.
  • დაავიწროვეთ ნაწილობრივი ინდექსით — დააინდექსეთ მხოლოდ active მომხმარებლები; ინდექსი ზომის მცირე ნაწილია.
  • განაახლეთ სტატისტიკა — ANALYZE, რომ დამგეგმავმა გამოცნობა შეწყვიტოს და სწორი join აირჩიოს.
  • ხელახლა გაზომეთ — კვლავ EXPLAIN ANALYZE; ენდეთ ციფრებს და არა შეგრძნებას.
ცოდნის შემოწმება

დაგამახსოვრდათ?

ხუთი შეკითხვა ფანჯრებზე, გეგმებზე, ინდექსებზე, იზოლაციასა და მასშტაბზე — მყისიერი უკუკავშირი, ავტორიზაციის გარეშე.

შეაფასეთ ეს დასტა
იყავით პირველი

ნავიგაცია ← → ღილაკებით ან სქროლით · უკან ბიბლიოთეკაში