RE:NODE

ბაზები13 წუთის საკითხავი

MySQL-ის index-ები და EXPLAIN: გეგმების კითხვა, query-ების გასწორება

როგორ მუშაობს MySQL InnoDB-ის index-ები, როგორ დაალაგო composite index-ის სვეტები და როგორ წაიკითხო EXPLAIN და EXPLAIN ANALYZE, რომ ნელი query სწრაფად აქციო.

0 მკითხველი

MySQL-ის ნელი query-ების უმეტესობა ერთი მიზეზით არის ნელი: სერვერი გაცილებით მეტ ხაზს კითხულობს, ვიდრე აბრუნებს, რადგან არცერთი index არ აძლევს საშუალებას, პირდაპირ საჭირო ხაზებზე მივიდეს. გამოსავალი არის index, რომლის სვეტებიც იმას შეესაბამება, როგორ ფილტრავს და ალაგებს query - ჯერ ტოლობის სვეტები, შემდეგ დიაპაზონის ან დალაგების სვეტი - ხოლო ხელსაწყო, რომელიც გეუბნება, სწორად გააკეთე თუ არა, არის EXPLAIN. შეხედე type სვეტს (ALL ნიშნავს ცხრილის სრულ სკანირებას), key სვეტს (რომელი index აირჩია, თუ აირჩია), rows-ს (რამდენი ხაზის შემოწმებას ელის MySQL) და Extra-ს (Using filesort და Using temporary ძვირებია). შემდეგ გაუშვი EXPLAIN ANALYZE, რომ ნახო, რა მოხდა სინამდვილეში, და არა ის, რაც optimizer-მა გამოიცნო.

ეს გზამკვლევი განმარტავს, როგორ ინახავს InnoDB ცხრილებსა და index-ებს, რატომ წყვეტს composite index-ში სვეტების რიგი, გამოიყენება თუ არა ის, რა შაბლონები აიძულებს MySQL-ს, უგულებელყოს არსებული index, და როგორ წაიკითხო EXPLAIN-ის ორივე ფორმა, განხილული მაგალითით ბოლოს. ის MySQL 8.0-სა და 8.4 LTS-ს ეხება.

როგორ ინახავს InnoDB ცხრილს#

InnoDB ცხრილი primary key-ით დალაგებული B+tree-ა. ამ ხის ფოთლოვანი გვერდები სრულ ხაზებს ინახავს. ეს clustered index-ია და ის არჩევითი არ არის: თუ primary key-ს არ გამოაცხადებ, InnoDB პირველ UNIQUE NOT NULL index-ს იყენებს, ხოლო თუ ასეთი არ არის, იგონებს დამალულ 6-ბაიტიან row ID-ს, სახელად GEN_CLUST_INDEX, რომლის გამოყენებაც query-ებში არ შეგიძლია.

ყოველი სხვა index მეორადი index-ია: ცალკე B+tree, დალაგებული index-ირებული სვეტებით, რომლის ფოთლებიც ამ სვეტებს პლუს primary key-ის მნიშვნელობას ინახავს. ხაზის პოვნა მეორადი index-ით ამიტომ ორი ძებნაა - ერთი მეორად index-ში primary key-ის საპოვნელად, შემდეგ მეორე clustered index-ში ხაზის მოსატანად.

აქედან სამი პრაქტიკული შედეგი გამომდინარეობს:

  • primary key პატარა შეინარჩუნე. ის ყოველ მეორად index-ში კოპირდება. BIGINT 8 ბაიტია; CHAR(36)-ად შენახული UUID ყოველ index-ის ჩანაწერში სულ მცირე 36 ბაიტია, და index utf8mb4-ში თითო სიმბოლოზე ოთხ ბაიტს იტოვებს.
  • თანმიმდევრული primary key-ები იაფად ემატება. AUTO_INCREMENT key ხის ბოლოს ემატება. შემთხვევითი UUID-ები მთელ ხეზე იფანტება, გვერდებს ყოფს და ქეშს აფრაგმენტებს. თუ UUID-ები გჭირდება, შეინახე ისინი BINARY(16)-ად UUID_TO_BIN(UUID(), 1)-ით, რომელიც დროის კომპონენტს ისე ალაგებს, რომ მნიშვნელობები დაახლოებით თანმიმდევრული იყოს.
  • query-ს, რომელსაც მხოლოდ index-ირებული სვეტები სჭირდება, მეორე ძებნის გამოტოვება შეუძლია. ეს covering index-ია, EXPLAIN-ში ნაჩვენები როგორც Using index, და ხშირად ყველაზე დიდი ცალკეული მოგებაა, რაც ხელმისაწვდომია.

ცხრილებს ყოველთვის ცხადი primary key მიეცი. MySQL 8-ს შეუძლია ეს sql_require_primary_key=ON-ით აიძულოს, ხოლო რეპლიკაცია და ბევრი ხელსაწყო მის გარეშე ცუდად იქცევა.

Composite index-ები და მარცხენა პრეფიქსი#

composite index (a, b, c)-ზე დალაგებულია a-ით, შემდეგ b-ით ყოველი a-ის შიგნით, შემდეგ c-ით. სატელეფონო წიგნის მსგავსად, რომელიც გვარით, შემდეგ სახელით არის დალაგებული, ის სასარგებლოა ნებისმიერი ძებნისთვის, რომელიც მარცხნიდან იწყება.

Query-ის პირობაიყენებს (a, b, c)-ს?როგორ
WHERE a = 1კიპრეფიქსი a
WHERE a = 1 AND b = 2კიპრეფიქსი a, b
WHERE a = 1 AND b = 2 AND c > 5კისამივე; c დიაპაზონად
WHERE a = 1 AND c = 3ნაწილობრივმხოლოდ a, შემდეგ c-ს ფილტრავს (index condition pushdown)
WHERE b = 2ჩვეულებრივ არაპირველი სვეტი არ არის; skip scan ზოგჯერ ეხმარება
WHERE a > 1 AND b = 2ნაწილობრივდიაპაზონი a-ზე b-ს ძებნისთვის გამოყენებას აჩერებს
WHERE a = 1 ORDER BY bკიხაზები უკვე b-ით დალაგებული გამოდის

აქედან გამომდინარე წესი: ჯერ ტოლობის სვეტები, შემდეგ ერთი დიაპაზონის ან დალაგების სვეტი, ბოლოს. როგორც კი index დიაპაზონის პირობას წააწყდება, მის შემდეგ მდგარი სვეტები ძებნის შესავიწროებლად ვეღარ გამოიყენება, მხოლოდ ფილტრაციისთვის. რამდენიმე ტოლობის სვეტს შორის რიგს იმაზე ნაკლები მნიშვნელობა აქვს, ვიდრე ხალხს ჰგონია; ის, რომელიც ყველაზე მეტ query-ს აქვს საერთო, პირველი დააყენე, რომ index ყველას ემსახუროს.

MySQL 8.0.13-მა დაამატა skip scan, რომელსაც შეუძლია (a, b) გამოიყენოს WHERE b = 2-ისთვის, როცა a-ს ძალიან ცოტა განსხვავებული მნიშვნელობა აქვს, თითო a-ზე ერთხელ ძებნით. ის ჩანს როგორც Using index for skip scan. ეს გადარჩენაა და არა დიზაინი.

პირველი სვეტისთვის სელექციურობას იმაზე ნაკლები მნიშვნელობა აქვს, ვიდრე ძველი რჩევები ვარაუდობს. მნიშვნელოვანია, რომ index query-ის ფორმას შეესაბამებოდეს. index მხოლოდ დაბალი კარდინალობის სვეტზე (status სამი მნიშვნელობით) იშვიათად არის სასარგებლო; იგივე სვეტი (status, created_at)-ის პირველ ნაწილად „ბოლო გადახდილი შეკვეთებისთვის“ შესანიშნავია.

როცა MySQL შენს index-ს უგულებელყოფს#

index შექმენი და EXPLAIN მაინც ამბობს type: ALL. ჩვეული მიზეზები:

sql
-- A function on the column: the index holds email, not LOWER(email)SELECT * FROM users WHERE LOWER(email) = 'ana@example.com';-- Implicit conversion: phone is VARCHAR, the literal is a numberSELECT * FROM users WHERE phone = 5551234;-- Leading wildcard: the index is sorted from the first characterSELECT * FROM products WHERE name LIKE '%phone%';-- Arithmetic on the columnSELECT * FROM orders WHERE created_at + INTERVAL 1 DAY > NOW();-- Date functions instead of a rangeSELECT * FROM orders WHERE YEAR(created_at) = 2026;

გამოსავლები, რიგით: გადაწერე პირობა ისე, რომ სვეტი მარტო იდგეს (created_at > NOW() - INTERVAL 1 DAY, created_at >= '2026-01-01' AND created_at < '2027-01-01'), სტრიქონის ლიტერალები ბრჭყალებში ჩასვი, რომ სვეტის ტიპს დაემთხვეს, ხოლო ტექსტის შიგნით ძებნისთვის LIKE '%...%'-ის ნაცვლად FULLTEXT index გამოიყენე. როცა ფუნქცია გარდაუვალია, MySQL 8.0.13 და უფრო ახალი ფუნქციურ index-ებს უჭერს მხარს - შენიშნე ორმაგი ფრჩხილები:

sql
CREATE INDEX idx_users_email_lower ON users ((LOWER(email)));

ორი ნაკლებად აშკარა მიზეზი. სხვადასხვა სიმბოლოების ნაკრებისა ან collation-ის მქონე სვეტების join კონვერტირებულ მხარეს index-ის გამოყენებას უშლის, რაც ნახევრად დასრულებული utf8mb4 მიგრაციის ხშირი შედეგია. და optimizer-მა შეიძლება სწორად გადაწყვიტოს, რომ სრული სკანირება იაფია: თუ პირობა ცხრილის 40%-ს ემთხვევა, ცხრილის თანმიმდევრული წაკითხვა ათიათასობით index-ით ძებნას ჯობნის. ასეთ შემთხვევაში index-ს ძალით ნუ მოახვევ; query ისე გაასწორე, რომ ნაკლები ხაზი დასჭირდეს.

EXPLAIN, სვეტ-სვეტ#

sql
EXPLAIN SELECT id, total FROM ordersWHERE customer_id = 4821 AND status = 'paid'ORDER BY created_at DESC LIMIT 20\G
code
*************************** 1. row ***************************           id: 1  select_type: SIMPLE        table: orders   partitions: NULL         type: refpossible_keys: idx_customer          key: idx_customer      key_len: 8          ref: const         rows: 2310     filtered: 10.00        Extra: Using where; Using filesort

mysql კლიენტში ბრძანების ;-ის ნაცვლად \G-ით დასრულება ყოველ ხაზს ვერტიკალურად ბეჭდავს, რაც EXPLAIN-ისთვის გაცილებით ადვილად იკითხება.

სვეტირას გეუბნება
typeროგორ მოიძებნება ხაზები. საუკეთესოდან უარესამდე: const, eq_ref, ref, range, index, ALL
possible_keysindex-ები, რომლებიც optimizer-მა განიხილა
keyindex, რომელიც აირჩია. NULL ნიშნავს არცერთს
key_lenგამოყენებული index-ის ბაიტები - აჩვენებს, composite index-ის რამდენი სვეტი გამოიყენა
refრას ედრება index: const, სვეტი სხვა ცხრილიდან
rowsშესამოწმებელი ხაზების შეფასება, თითო ციკლზე
filteredდარჩენილი პირობების შემდეგ დარჩენილი პროცენტის შეფასება
Extraდანარჩენი ყველაფერი, და აქ ცხოვრობს გაფრთხილებები

type-ის მნიშვნელობები უბრალო ენით: const ერთი ხაზია primary key-ით ან unique index-ით; eq_ref ერთი ხაზია join-ში წინა ცხრილის თითო ხაზზე; ref რამდენიმე ხაზია, რომლებიც არაუნიკალურ index-ზე ტოლობას ემთხვევა; range index-ის დიაპაზონია (BETWEEN, >, IN); index მთელ index-ს კითხულობს; ALL მთელ ცხრილს კითხულობს. index ისეთი კარგი ამბავი არ არის, როგორიც ჟღერს - ეს ცხრილის ნაცვლად index-ის სრული სკანირებაა.

Extra-ს შენიშვნები, რომლებიც ღირს იცნობდე:

  • Using index - covering: პასუხი მხოლოდ index-იდან. კარგია.
  • Using index condition - index condition pushdown, ფილტრაცია index-ის შიგნით ხაზების მოტანამდე. კარგია.
  • Using where - ხაზები წაკითხვის შემდეგ იფილტრება. ნორმალურია, მაგრამ მაღალი rows-ით ფლანგვას ნიშნავს.
  • Using filesort - შედეგები წაკითხვის შემდეგ ლაგდება, მეხსიერებაში ან დისკზე. ბევრ ხაზზე ძვირია.
  • Using temporary - დროებითი ცხრილი აიგო, ჩვეულებრივ GROUP BY-ისთვის ან DISTINCT-ისთვის.
  • Using join buffer (hash join) - hash join, რომელიც 8.0.18-დან გამოიყენება, როცა join-ს გამოსაყენებელი index არ აქვს. ნიშანია, რომ join-ის სვეტს index სჭირდება.

მაგალითში გეგმამ idx_customer-ით მომხმარებლის 2310 შეკვეთა იპოვა, შემდეგ სტატუსით გაფილტრა (შეფასებით 10% გადარჩება) და ყველა დაალაგა, რომ 20 დაებრუნებინა. ეს მუშაობს, მაგრამ ასჯერ მეტ ხაზს კითხულობს, ვიდრე აბრუნებს.

key_len წყნარი დეტალია, რომელიც პასუხობს კითხვას „სრულად გამოიყენება ჩემი composite index?“. INT 4 ბაიტია, BIGINT 8, nullable სვეტი 1-ს ამატებს, VARCHAR(n) utf8mb4-ში ითვლება როგორც 4 × n + 2. თუ key_len სამსვეტიანი index-ის მხოლოდ პირველ სვეტს ფარავს, დანარჩენი ორი ძებნისთვის არ გამოიყენება.

EXPLAIN ANALYZE და tree ფორმატი#

EXPLAIN შეფასებებს აჩვენებს. EXPLAIN ANALYZE, 8.0.18-ში დამატებული, query-ს რეალურად უშვებს და ყოველ ნაბიჯზე მომხდარს tree ფორმატში გიყვება:

sql
EXPLAIN ANALYZE SELECT id, total FROM ordersWHERE customer_id = 4821 AND status = 'paid'ORDER BY created_at DESC LIMIT 20;
code
-> Limit: 20 row(s)  (cost=520 rows=20) (actual time=9.84..9.85 rows=20 loops=1)    -> Sort: orders.created_at DESC, limit input to 20 row(s) per chunk       (cost=520 rows=2310) (actual time=9.84..9.84 rows=20 loops=1)        -> Filter: (orders.`status` = 'paid')  (cost=520 rows=231)           (actual time=0.11..9.52 rows=1874 loops=1)            -> Index lookup on orders using idx_customer (customer_id=4821)               (cost=520 rows=2310) (actual time=0.10..9.21 rows=2310 loops=1)

წაიკითხე ყველაზე შიდა ხაზიდან გარეთკენ. ყოველი ნაბიჯი აჩვენებს შეფასებას (cost, rows) და რეალობას (actual time როგორც პირველი-ხაზი..ყველა-ხაზი მილიწამებში, rows, loops). ორ რამეს უნდა დაუწყო ძებნა:

  • შეფასებები, რომლებიც რეალობას შორდება. აქ ფილტრს 231 ხაზი უნდა დაეტოვებინა და 1874 დატოვა. დიდი სხვაობები ნიშნავს, რომ optimizer ცუდი სტატისტიკით გეგმავს და სხვა მნიშვნელობებისთვის შეიძლება არასწორი გეგმა აირჩიოს. ANALYZE TABLE orders; index-ის სტატისტიკას აახლებს; index-ის გარეშე სვეტებისთვის დახრილი მნიშვნელობებით histogram გეხმარება: ANALYZE TABLE orders UPDATE HISTOGRAM ON status WITH 16 BUCKETS;.
  • სად მიდის დრო. ჩადგმული ნაბიჯებისთვის დრო loops-ზე გაამრავლე. ნაბიჯი, რომლის რეალური დროც ნახტომს აკეთებს, ისაა, სადაც სამუშაოა.

EXPLAIN ANALYZE query-ს ასრულებს, მის მიერ გამოძახებული ფუნქციების ნებისმიერი გვერდითი ეფექტის ჩათვლით, და იმდენ ხანს გრძელდება, რამდენსაც query. UPDATE-ისა და DELETE-ისთვის გამოიყენე ჩვეულებრივი EXPLAIN ან გაუშვი ეკვივალენტური SELECT. EXPLAIN FORMAT=TREE იმავე ხეს მხოლოდ შეფასებებით აჩვენებს, არაფრის გაშვების გარეშე; EXPLAIN FORMAT=JSON ღირებულების დეტალებს ამატებს. MySQL Workbench JSON ფორმას დიაგრამად ხატავს, რაც ზოგს დიდ join-ებზე უფრო ადვილად ესმის.

Index-ები join-ებისთვის, GROUP BY-სთვის და პრეფიქსებისთვის#

join-ები იმავე ლოგიკას მიჰყვება, ერთ ცხრილზე ერთდროულად. MySQL გეგმაში პირველ ცხრილს კითხულობს, შემდეგ ყოველი ხაზისთვის შესაბამის ხაზებს მომდევნოში ეძებს. ამ ძებნას მეორე ცხრილის join-ის სვეტზე index სჭირდება - ჩვეულებრივ foreign key-ის მხარეს. InnoDB index-ს ავტომატურად ქმნის, როცა FOREIGN KEY-ს აცხადებ, და ეს ერთ-ერთი მიზეზია, რის გამოც გამოცხადებული foreign key-ები იშვიათად იწვევს ნელ join-ებს, გამოუცხადებელი „ლოგიკური“ კი ხშირად. EXPLAIN-ში კარგად index-ირებული join შიდა ცხრილზე eq_ref-ს ან ref-ს აჩვენებს; ALL Using join buffer (hash join)-თან ერთად ნიშნავს, რომ ერთი ცხრილის ყოველი ხაზი მეორის hash-ს ედრება.

GROUP BY-სა და DISTINCT-ზე პასუხი დროებითი ცხრილის აგების ნაცვლად index-ის რიგით გავლით შეიძლება, როცა დაჯგუფებული სვეტები index-ის მარცხენა პრეფიქსია და ნებისმიერი ტოლობის პირობა მათ წინ დგას. GROUP BY customer_id index-ზე, რომელიც customer_id-ით იწყება, ნაკადად მიდის; იგივე query index-ის გარეშე სვეტზე Using temporary-ს აჩვენებს. სვეტით დაჯგუფებული COUNT(*)-ისთვის ამ სვეტზე ვიწრო index ასევე covering-ია, ასე რომ თავად ცხრილს არასოდეს ეხება.

გრძელი სტრიქონის სვეტების index-ირება პრეფიქსით შეიძლება: INDEX (url(100)) პირველ 100 სიმბოლოს index-ირებს. InnoDB-ის ლიმიტი index-ის გასაღებზე ნაგულისხმევი DYNAMIC row ფორმატით 3072 ბაიტია, რაც utf8mb4-ის 768 სიმბოლოა, ასე რომ VARCHAR(1000)-ის მთლიანად index-ირება შეუძლებელია. პრეფიქსის index ვერ იქნება covering და ვერ მოემსახურება ORDER BY-ს სრულ მნიშვნელობაზე; გრძელ მნიშვნელობებზე ზუსტი ძებნისთვის, როგორიცაა URL-ები, generated სვეტში შენახული hash-ის index-ირება ხშირად უკეთესია - JSON და generated სვეტები ამ ტექნიკას აჩვენებს.

განხილული მაგალითი#

ზემოთ მოცემული query, კიდევ ერთხელ: მომხმარებლის ბოლო გადახდილი შეკვეთები. ცხრილს index მხოლოდ customer_id-ზე აქვს. გამოსავალი არის index, რომელიც ორივე ტოლობასა და დალაგებას შეესაბამება:

sql
ALTER TABLE orders  ADD INDEX idx_customer_status_created (customer_id, status, created_at),  ALGORITHM=INPLACE, LOCK=NONE;

ALGORITHM=INPLACE, LOCK=NONE online აგებას ითხოვს: წაკითხვა და ჩაწერა გრძელდება, სანამ index იქმნება, და თუ ეს შეუძლებელია, ბრძანება ვარდება და ცხრილს ჩუმად არ ბლოკავს. დიდ ცხრილზე ის მაინც იყენებს I/O-სა და დროებით სივრცეს, ამიტომ წყნარ დროს ააგე.

code
-> Limit: 20 row(s)  (cost=12.4 rows=20) (actual time=0.06..0.09 rows=20 loops=1)    -> Index lookup on orders using idx_customer_status_created       (customer_id=4821, status='paid') (reverse)       (cost=12.4 rows=1874) (actual time=0.06..0.08 rows=20 loops=1)

ოცი წაკითხული ხაზი, დალაგების გარეშე, ფილტრის გარეშე: index ხაზებს created_at-ის რიგით აწვდის, DESC-ისთვის უკუღმა წაკითხულს, და LIMIT ოცის შემდეგ ჩერდება. ათი მილიწამიდან ერთზე ნაკლებამდე, და რაც უფრო მნიშვნელოვანია, ფასი აღარ იზრდება მომხმარებლის შეკვეთების რაოდენობასთან ერთად. total-ის მეოთხე სვეტად დამატება index-ს covering-ს გახდიდა და clustered index-ში ძებნასაც გამოტოვებდა - ეს ღირს query-სთვის, რომელიც ყოველ გვერდის ნახვაზე ეშვება, და არა ისეთისთვის, რომელიც საათში ერთხელ.

ახლა წაშალე index, რომელსაც ეს ანაცვლებს. (customer_id) ახალი index-ის პრეფიქსია, ამიტომ ზედმეტია და მხოლოდ ჩაწერებსა და მეხსიერებას ჯდება.

Index-ების კონტროლი#

index-ები უფასო არ არის. თითოეული ახლდება ყოველ INSERT-ზე, DELETE-ზე და ნებისმიერ UPDATE-ზე, რომელიც მის სვეტებს ეხება, და თითოეული buffer pool-ში ადგილს იკავებს, რომელიც სხვა შემთხვევაში მონაცემებს დააქეშებდა. sys სქემა პოულობს მათ, ვინც თავის ადგილს ვერ ამართლებს:

sql
-- Indexes not used since the server startedSELECT * FROM sys.schema_unused_indexes WHERE object_schema = 'appdb';-- Indexes made redundant by another (a prefix of a longer one)SELECT table_name, redundant_index_name, dominant_index_nameFROM sys.schema_redundant_indexes WHERE table_schema = 'appdb';

„გაშვების შემდეგ გამოუყენებელს“ აზრი მხოლოდ მაშინ აქვს, თუ სერვერი სრული ბიზნეს-ციკლის განმავლობაში მუშაობდა - index, რომელსაც მხოლოდ თვის ბოლოს ანგარიში იყენებს, 29 დღის განმავლობაში გამოუყენებლად გამოიყურება. წაშლამდე ის უხილავი გახადე, რაც მას განახლებულს ინახავს, მაგრამ optimizer-ს უმალავს:

sql
ALTER TABLE orders ALTER INDEX idx_customer INVISIBLE;-- watch for a week; if nothing slows down:ALTER TABLE orders DROP INDEX idx_customer;-- or, if something did:ALTER TABLE orders ALTER INDEX idx_customer VISIBLE;

უხილავი index-ები 8.0-ში გამოჩნდა და წაშლის გამოცდის ყველაზე უსაფრთხო გზაა. MySQL 8 ნამდვილად კლებად index-ებსაც უჭერს მხარს (created_at DESC განსაზღვრებაში), რაც სასარგებლოა, როცა query ერთი სვეტით ზრდადად და მეორით კლებადად ალაგებს, რასაც ზრდადი index ვერ მოემსახურება.

იმის პოვნა, თავდაპირველად რომელ query-ებს სჭირდება ყურადღება, slow query log-ის საქმეა. იგივე თემისთვის PostgreSQL-ზე იხილე PostgreSQL-ის index-ები განმარტებით და EXPLAIN ANALYZE ნელი query-ებისთვის; იდეები გადაიტანება, გამოტანის ფორმატი - არა.

FAQ#

რამდენი index უნდა ჰქონდეს ცხრილს?

იმდენი, რამდენიც query-ებს სჭირდება, და არა მეტი. ცხრილების უმეტესობას კარგად ემსახურება primary key პლუს ორიდან ხუთამდე მეორადი index. ჩაწერით დატვირთული ცხრილი ათი index-ით თითო insert-ზე ათ განახლებას იხდის; შეამოწმე sys.schema_unused_indexes და წაშალე ის, რასაც არაფერი იყენებს.

აქვს მნიშვნელობა სვეტების რიგს WHERE-ში?

არა. optimizer პირობებს თავისუფლად ალაგებს; WHERE b = 2 AND a = 1 index-ს (a, b)-ზე ზუსტად ისევე იყენებს, როგორც WHERE a = 1 AND b = 2. მნიშვნელობა აქვს სვეტების რიგს index-ის განსაზღვრებაში.

რატომ აჩვენებს EXPLAIN index-ს possible_keys-ში, მაგრამ key არის NULL?

optimizer-მა ის განიხილა და სრული სკანირება უფრო იაფად შეაფასა - ჩვეულებრივ იმიტომ, რომ პირობა ცხრილის დიდ წილს ემთხვევა, ან იმიტომ, რომ სტატისტიკა მოძველებულია. გაუშვი ANALYZE TABLE და ისევ შეამოწმე; თუ მაინც სკანირებას ამჯობინებს, ალბათ მართალია, და query-ს უფრო სელექციური პირობა სჭირდება.

უნდა გამოვიყენო FORCE INDEX?

იშვიათად და დროებით ზომად. ძალით არჩეული index ძალით რჩება მონაცემების შეცვლის შემდეგაც და გეგმა არასწორი ხდება. ჯერ სტატისტიკა, index ან query გაასწორე, და თუ მინიშნება საჭიროა, უპირატესობა მიანიჭე optimizer hint-ის სინტაქსს /*+ INDEX(orders idx_name) */, რომელიც 8.0-სა და უფრო ახალშია დოკუმენტირებული.

ბლოკავს index-ის დამატება ცხრილს?

MySQL 8-ში ჩვეულებრივ არა. InnoDB ცხრილზე მეორადი index-ის დამატება online ოპერაციაა, რომელიც წაკითხვასა და ჩაწერას უშვებს, დასაწყისსა და ბოლოში მოკლე metadata lock-ების გარდა. ცხადად მოითხოვე ALGORITHM=INPLACE, LOCK=NONE-ით, რომ თუ online შეუძლებელია, ბრძანება ჩავარდეს და არ დაბლოკოს.


კომენტარები

სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.

0/2000