38-წუთიანი ღრმა სესია, რომელიც იქიდან აგრძელებს, სადაც შესავალი დეკი შეწყდა. ვვარაუდობთ, რომ იცით join-ები, გასაღებები, ACID და რა არის ინდექსი — ეს კი ქვედა შრეა: ანალიტიკა SQL-ში, EXPLAIN ANALYZE-ის კითხვა, ინდექსების სტრატეგია, MVCC და იზოლაცია, და ცხრილის მასშტაბირება ერთი მანქანის მიღმა. თუ საფუძვლები ბუნდოვანია, ჯერ მონაცემთა ბაზები & SQL გაიარეთ.
ეს არის მონაცემთა ბაზები & SQL-ის მე-2 ნაწილი — ამიტომ GROUP BY-ის საფუძვლებს გამოვტოვებთ და პირდაპირ იმ ინსტრუმენტზე გადავდივართ, რომელიც SQL-ს ანალიტიკურ ძრავად აქცევს. ფანჯრის ფუნქცია ითვლის მნიშვნელობას სტრიქონების იმ ნაკრებზე, რომელიც მიმდინარე სტრიქონს უკავშირდება, და მაინც აბრუნებს ყველა სტრიქონს — მზარდი ჯამები, რანჟირება და სტრიქონიდან სტრიქონამდე სხვაობები self-join-ის გარეშე.
window-ზე: სტრიქონების ჩარჩოზე, რომელსაც OVER (PARTITION BY … ORDER BY …) განსაზღვრავს. GROUP BY-სგან განსხვავებით, რომელიც ბევრ სტრიქონს ერთში კეცავს, ფანჯარა სტრიქონებს ხელუხლებლად ტოვებს და მათ გვერდით გამოთვლილ სვეტს ამატებს. დანაწილება მონაცემებს დამოუკიდებელ ჯგუფებად ჭრის; ჩარჩო კი არჩევს, დანაწილების შიგნით რომელ სტრიქონებს ხედავს ფუნქცია.თითოეული მომხმარებელი საკუთარი დანაწილებაა; ჩარჩო პირველი სტრიქონიდან მიმდინარემდე იზრდება, ამიტომ ჯამი გროვდება და ყოველ მომხმარებელზე ნულდება.
ROW_NUMBER ყოველთვის უნიკალურია; RANK ტოლების შემდეგ ხარვეზებს ტოვებს (1,1,3); DENSE_RANK — არა (1,1,2). NTILE(4) კვარტილებად ყოფს.
LAG(x) კითხულობს წინა სტრიქონს, LEAD(x) — მომდევნოს; იდეალურია სხვაობებისთვის (ეს თვე გასულთან შედარებით) კორელირებული ქვეშეკითხვის გარეშე.
SUM, AVG, COUNT, MAX — ყველა იღებს OVER კონსტრუქციას: მზარდი ჯამები, მოძრავი საშუალოები, ჯგუფში წილი.
FIRST_VALUE, LAST_VALUE, NTH_VALUE ჩარჩოში კონკრეტული პოზიციიდან იღებს სტრიქონს — თვალი ადევნეთ ჩარჩოს, თორემ LAST_VALUE გაგაკვირვებთ.
WHERE, GROUP BY და HAVING გაიარა, ამიტომ იმავე შეკითხვაში მათზე ფილტრს ვერ დაადებთ — მოაქციეთ CTE-ში ან ქვეშეკითხვაში და გარედან გაფილტრეთ. მეორე: როცა ORDER BY გაქვთ, ჩარჩო კი აშკარად არ მიგითითებიათ, ნაგულისხმევია RANGE და არა ROWS — ეს კი ერთი და იმავე დალაგების გასაღების მქონე ყველა თანატოლ სტრიქონს მიმდინარე ჩარჩოში კრებს. ნამდვილი სტრიქონ-სტრიქონ მზარდი ჯამისთვის პირდაპირ დაწერეთ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.CTE (Common Table Expression — WITH კონსტრუქცია) ქვეშეკითხვას სახელს არქმევს, რომ რთული შეკითხვა ზემოდან ქვემოთ იკითხებოდეს და არა შიგნიდან გარეთ. გაღრმავებული მოგება რეკურსიაა: CTE, რომელიც საკუთარ თავს მიმართავს — ასე გაივლით ხეებსა და გრაფებს (ორგანიზაციული სქემები, კატეგორიების იერარქიები, დამოკიდებულებების DAG-ები) ერთი ინსტრუქციით.
WITH RECURSIVE t AS (anchor UNION ALL recursive-member). საწყისი წევრი იძლევა პირველ სტრიქონებს; რეკურსიული წევრი კი ხელახლა სრულდება წინა ნაბიჯის მიერ დაბრუნებულ სტრიქონებზე, სანამ ახალ სტრიქონს აღარ დაამატებს. UNION (UNION ALL-ისგან განსხვავებით) დუბლიკატებს აშორებს და იაფი დაცვაა ციკლურ მონაცემებზე უსასრულო მარყუჟისგან.ყოველი იტერაცია ცხრილს ერთი დონით ზემოთ ნაპოვნ სტრიქონებს უერთებს — შეკითხვა თითო შრეს შლის მანამ, სანამ დაქვემდებარებული აღარავინ დარჩება.
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 სინტაქსური შაქარი კი ძრავების მიხედვით განსხვავდება.
შესავალმა დეკმა Seq Scan და Index Scan გაჩვენათ. ახლა უფრო ღრმად: რომელი join-ის ალგორითმი აირჩია დამგეგმავმა, რატომ და როგორ ამოიცნოთ გეგმა, რომელიც ჩავარდნის ზღვარზეა. დამგეგმავი ღირებულების მოდელია — ის გამოიცნობს; თქვენი საქმეა, დაიჭიროთ, როცა არასწორად გამოიცნობს.
EXPLAIN ბეჭდავს გეგმას სავარაუდო სტრიქონებითა და ღირებულებით; EXPLAIN ANALYZE კი მართლა უშვებს მას და ფაქტობრივ სტრიქონებსა და დროს ბეჭდავს. დაამატეთ BUFFERS, რომ ნახოთ, გვერდი ქეშიდან წაიკითხა თუ დისკიდან (Postgres 18-ში ის ნაგულისხმევად შედის ANALYZE-თან ერთად). ხე წაიკითხეთ ქვემოდან ზემოთ, შიგნიდან გარეთ.გადის გარე სტრიქონებს და თითოეულს შიდაში ეძებს — იდეალურად ინდექსით. იგებს, როცა გარე მხარე პატარაა. კვდება, როცა ორივე მხარე დიდია (სტრიქონი × სტრიქონი).
პატარა შემავალზე აგებს ჰეშ-ცხრილს და დიდს მის გვერდით ატარებს. იგებს დიდ, დაულაგებელ ტოლობით შეერთებებზე. ხარჯავს მეხსიერებას — work_mem-ს გადაცილებისას პარტიებად დისკზე იღვრება.
ორივე შემავალს დალაგებული თანმიმდევრობით გადის და ელვასავით კრავს. იგებს, როცა შემავალი უკვე დალაგებულია (ინდექსი ან დიაპაზონი). იხდის დახარისხების ფასს, თუ არა.
ANALYZE.480 000-ჯერ მოძებნა.placed_at-ზე ინდექსი აკლია.loops, დიდი ფაქტობრივი სტრიქონების რიცხვი და Sort ან ჰეში, რომელიც დისკზე გადაიღვარა.Postgres-ის სტილში; უმეტეს ძრავას თავისი ეკვივალენტი აქვს. თითო დადებითი, თითო უარყოფითი და როდის მივმართოთ თითოეულს.
დადებითი: ჭეშმარიტება — ფაქტობრივი სტრიქონები, დრო და ბუფერები ერთი ინსტრუქციისთვის. უარყოფითი: ANALYZE მართლა უშვებს შეკითხვას (ჩაწერები მოაქციეთ ტრანზაქციაში, რომელსაც მერე rollback უკეთდება).
დადებითი: მთელ სერვერზე აჯამებს საერთო დროს, გამოძახებებსა და საშუალოს ნორმალიზებული შეკითხვის მიხედვით. უარყოფითი: გაფართოებაა, რომელიც უნდა ჩართოთ; აჩვენებს,რა არის ნელი და არა რატომ.
დადებითი: პროდაქშენში ლოგავს ზღვარზე ნელი ნებისმიერი შეკითხვის რეალურ გეგმას. უარყოფითი: ლოგების მოცულობა და მცირე დანამატი; ზღვარი ფრთხილად მოარგეთ.
დადებითი: ტექსტის კედელს ხედ აქცევს, ძვირ ნოუდს მონიშნავს, გამოსწორებას შემოგთავაზებთ. უარყოფითი: იმდენად კარგია, რამდენადაც ჩასმული გეგმა; სერვერის მასშტაბის სურათს არ იძლევა.
იცით, რა არის B-ხის ინდექსი. ოსტატობა ფორმის არჩევაშია: რომელი სვეტები, რა თანმიმდევრობით, რა დამატებებით — და იმის ცოდნაში, რომ ყოველი დამატებული ინდექსი გადასახადია ყოველ ჩაწერაზე. სამი პატერნი მოგებების უმეტესობას ფარავს.
(a, b, c)-ში მხოლოდ მარცხენა პრეფიქსი გამოდგება. ჯერ ის სვეტები დადეთ, რომლებსაც =-ით ამოწმებთ, მერე ერთი დიაპაზონის სვეტი, მერე ORDER BY-ის სვეტი. მხოლოდ b-ზე ფილტრი მას ვერ გამოიყენებს.
INCLUDE-ით დაამატეთ არა-გასაღები სვეტები, რომ ინდექსში ყველაფერი იყოს, რასაც შეკითხვა ირჩევს. დამგეგმავი index-only scan-ს აკეთებს — heap-ს საერთოდ არ ეხება (როცა visibility map ახალია).
თავად ინდექსზე დადებული WHERE მას პატარად და იაფად ინახავს. კლასიკაა ცხელი ქვესიმრავლისთვის — ღია შეკვეთები, რბილად წაშლილი სტრიქონები, ერთი ტენანტი — სადაც სტრიქონების უმეტესობა უმნიშვნელოა.
ყოველი ინდექსი დამატებითი სტრუქტურაა, რომელიც ძრავამ სინქრონში უნდა შეინარჩუნოს — ამიტომ თითოეული ანელებს ჩამატებას, განახლებასა და წაშლას.
INSERT იქცევა ერთ heap-ჩაწერად პლუს ჩაწერად ცხრილის ყოველ ინდექსში.pg_stat_user_indexes).GIN ერგება jsonb-ს, მასივებსა და სრულ ტექსტს, BRIN კი უზარმაზარ, ბუნებრივად დალაგებულ ცხრილებს, როგორიცაა დროის სერიები.EXPLAIN-ით — ინდექსი, რომელიც არ გამოიყენება, უარესია, ვიდრე მისი არარსებობა.შესავალმა დეკმა ACID მოგცათ. ახლა I-ს რთული ნაწილი: რის დანახვის უფლება აქვს რეალურად პარალელურ ტრანზაქციებს, როგორ აღწევს ამას Postgres მკითხველების დაბლოკვის გარეშე და რა ჩავარდნები — დაკარგული განახლებები, ჩაწერის დახრა, დედლოკები — გელოდებათ მასშტაბზე.
xmin/xmax); ტრანზაქცია ხედავს მხოლოდ იმ ვერსიებს, რომლებიც მისი სნეპშოტისთვის ვარგისია. შედეგი: მკითხველები არასოდეს ბლოკავენ მწერლებს და მწერლები — მკითხველებს. ფასი: მკვდარი ვერსიები გროვდება და VACUUM-მა უნდა დაიბრუნოს ისინი.მაღალი დონეები მეტ ანომალიას აღკვეთს — და მეტ შეწყვეტასა და კონკურენციაში დგომას იწვევს. აირჩიეთ ყველაზე დაბალი დონე, რომელიც ჯერ კიდევ სწორია.
SELECT … FOR UPDATE, ან ამოიცანით ოპტიმისტური ვერსიის სვეტით.FOR UPDATE SKIP LOCKED ბევრ ვორკერს აძლევს საშუალებას, სხვადასხვა სტრიქონი აიღოს ერთმანეთის დაბლოკვის გარეშე.VACUUM-ს ბლოკავს და ცხრილს ბერავს.ერთი და იმავე იდეის ორი ჭრილი: გაყავით მონაცემები. დანაწილება ერთ ცხრილს ნაწილებად ჭრის ერთ მანქანაზე; შარდინგი კი მას ბევრ მანქანაზე ანაწილებს. ისინი სხვადასხვა პრობლემას წყვეტს და ღირებულებაც ძალიან განსხვავებული აქვს.
RANGE-ს (თარიღით), LIST-ს (რეგიონით) და HASH-ს. დამგეგმავი აკეთებს დანაყოფების გასხვლას — გამოტოვებს იმ დანაყოფებს, რომლებსაც WHERE ვერ ემთხვევა.DELETE-ის გარეშე); თითო დანაყოფის ინდექსი პატარაა; არარელევანტური დანაყოფები დაგეგმვისასვე იჭრება.დანაწილება = შვილობილი ცხრილები ერთი სერვერის ქვეშ. შარდინგი = სტრიქონები, გაბნეული დამოუკიდებელ სერვერებზე შარდის გასაღების მიხედვით.
დაშბორდის შეკითხვა ტაიმაუტში ვარდება. გაიარეთ ის ციკლი, რომელსაც მთელი კარიერის განმავლობაში გამოიყენებთ: გაზომე → წაიკითხე გეგმა → შეცვალე ერთი რამ → ხელახლა გაზომე.
(user_id, placed_at DESC) ერთდროულად ემსახურება join-საც და დახარისხებასაც და დისკზე დახარისხებას კლავს.INCLUDE (amount) მას index-only scan-ად აქცევს, ამიტომ heap-ს საერთოდ აღარ სტუმრობს.active მომხმარებლები; ინდექსი ზომის მცირე ნაწილია.ANALYZE, რომ დამგეგმავმა გამოცნობა შეწყვიტოს და სწორი join აირჩიოს.EXPLAIN ANALYZE; ენდეთ ციფრებს და არა შეგრძნებას.ხუთი შეკითხვა ფანჯრებზე, გეგმებზე, ინდექსებზე, იზოლაციასა და მასშტაბზე — მყისიერი უკუკავშირი, ავტორიზაციის გარეშე.
ნავიგაცია ← → ღილაკებით ან სქროლით · უკან ბიბლიოთეკაში