Changes between Version 3 and Version 4 of Indexing


Ignore:
Timestamp:
09/10/26 22:50:42 (6 hours ago)
Author:
222004
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Indexing

    v3 v4  
    11== Датотечни системи и организација на податоци - Индекси
    22
    3 === Преглед на шемата на AirportDB\\
    4 
    5 Во нашите case-сценарија табелата booking со ~3.5 милиони редови е главната цел за индексирање. Повеќето сценарија ја вклучуваат.\\
    6 
    7 === Типови на индекси во MySql
    8 **B-Tree (BTree) index — default индекс во MySql**\\
    9 Најчест тип. Работи добро за: = , <, >, BETWEEN, LIKE 'prefix%', ORDER BY, GROUP BY.\\
    10 \\
    11 **Composite index**
    12 Покрива повеќе услови. Редоследот во овој тип на индекс е критичен — индексот може да даде подобри перформанси амо ако WHERE ги содржи условите одлево надесно (leftmost prefix rule).
     3=== Вовед
     4
     5Експериментите се изведени во MySQL 8.0.43. Оригиналната `airportdb` база се користеше само за читање, а сите експериментални индекси беа креирани во изолираната `airportdb_index_lab`.
     6
     7Во тест околината:
     8* `booking` има 54,304,619 записи
     9* `weatherdata` има 4,626,432 записи
     10
     11InnoDB ги организира податоците во 16 KiB pages кои се кешираат во buffer pool. PRIMARY KEY претставува clustered структура за чување на редовите, додека нормалните PRIMARY и secondary индекси се организирани како B+ tree структури погодни за пребарување по еднаквост и опсег.
     12
     13Secondary index содржи indexed key и PRIMARY KEY вредност. Composite index содржи повеќе колони во точно определен редослед, а covering index е индекс кој ги содржи сите податоци што му се потребни на конкретен прашалник.
     14
     15=== 1. Secondary index - пребарување на резервации по патник
     16
     17Сценарио: оператор пребарува резервации за конкретен патник.
     18
     19{{{
     20SELECT booking_id, flight_id, seat, price
     21FROM airportdb_index_lab.booking
     22WHERE passenger_id = 10001;
     23}}}
     24
     25Најпрво прашалникот беше извршен без secondary index на `passenger_id`.
     26
     27|| '''Мерка''' || '''Без secondary index''' || '''idx_exp_booking_passenger''' ||
     28|| Access type || `ALL` || `ref` ||
     29|| Обработени / matching rows || ~54.3M || 1,436 ||
     30|| Benchmark median || 13.62 s || 0.005 s ||
     31|| Дополнителен storage / build || - || 847,233,024 B / 61.97 s ||
     32
     33[[Image(3.PNG)]]
     34
     35'''Слика 1.''' Без passenger index, MySQL користи `ALL` и проценува дека треба да обработи 54,163,080 редови.
     36
     37Потоа беше креиран:
     38
     39{{{
     40CREATE INDEX idx_exp_booking_passenger
     41ON airportdb_index_lab.booking(passenger_id);
     42}}}
     43
     44[[Image(4.PNG)]]
     45
     46'''Слика 2.''' Со passenger index, пристапот станува index lookup и се добиваат 1,436 записи.
     47
     48Индексот значително ја намалува количината на работа за овој selective lookup. Сепак, индексот не е covering за `flight_id`, `seat` и `price`, па за тие колони се потребни дополнителни пристапи до clustered редовите.
     49
     50Прикажаниот benchmark median е добиен од повторени нормални извршувања на прашалникот. Времето од `EXPLAIN ANALYZE` е посебна instrumentation мерка и не се користи како директна замена за benchmark времето.
     51
     52=== 2. Composite index - редослед на колоните
     53
     54Кај composite index редоследот на колоните е важен. За тестирање беше користен следниот прашалник:
     55
     56{{{
     57SELECT COUNT(*) AS measurement_count
     58FROM airportdb_index_lab.weatherdata
     59WHERE station = 1
     60  AND time >= '08:00:00'
     61  AND time < '09:00:00';
     62}}}
     63
     64Беа споредени:
     65
     66{{{
     67(station, time, log_date)
     68}}}
     69
     70и
     71
     72{{{
     73(time, station, log_date)
     74}}}
     75
     76|| '''Index''' || '''Прочитани index entries''' || '''Вратени редови''' || '''Benchmark median''' ||
     77|| `(station,time,log_date)` || 48,192 || 48,192 || 19.94 ms ||
     78|| `(time,station,log_date)` || 192,768 || 48,192 || 69.21 ms ||
     79
     80[[Image(6.PNG)]]
     81
     82'''Слика 3.''' Equality-first редослед: прво се ограничува `station = 1`, а потоа се пребарува временскиот опсег.
     83
     84[[Image(7.PNG)]]
     85
     86'''Слика 4.''' Range-first редослед: индексот почнува од `time`, па се читаат записи за сите четири stations пред да се филтрира `station = 1`.
     87
     88Вториот индекс чита 4 пати повеќе index entries и има приближно 3.47 пати подолго измерено време. Ова практично го покажува значењето на leftmost-prefix правилото и пристапот equality-before-range.
     89
     90=== 3. Covering index и однесување на optimizer-от
     91
     92За едномесечен weather workload беше спореден потесниот индексен пристап со covering index:
     93
     94{{{
     95(station, log_date, time, temp, humidity)
     96}}}
     97
     98|| '''Weather workload''' || '''Narrow / PRIMARY path''' || '''Covering index''' ||
     99|| Structural rows / median || 35,712 / 12.47 ms || 8,928 / 5.62 ms ||
     100|| Analytical median (31 groups) || 11.29 ms || 4.55 ms ||
     101|| INDEX_LENGTH || 83,476,480 B || 109,740,032 B ||
     102
     103Covering index-от овозможува потребните колони да се прочитаат директно од индексот и во execution plan се појавува `Using index`. Со тоа се избегнуваат дополнителни clustered-row reads, но операциите како `AVG`, `MIN`, `MAX` и `GROUP BY` сепак мора да се извршат.
     104
     105Експериментот покажа и дека постоење на индекс не значи дека optimizer-от мора да го избере. Кај едномесечниот weather query, `(station,log_date,time)` беше прикажан во `possible_keys`, но MySQL сепак го избра PRIMARY индексот бидејќи го процени како поевтин план.
     106
     107Дополнително беше тестиран и invisible index. Кога индексот е invisible, тој и понатаму физички постои, зафаќа простор и се одржува при промени, но optimizer-от го игнорира при нормален избор на план.
     108
     109=== 4. Посложен аналитички workload
     110
     111Следното сценарио го анализира ефектот на индексите врз посложен аналитички прашалник: top revenue routes по ден за периодот `2015-07-01` – `2015-07-03`.
     112
     113Логичкиот тек е:
     114
     115{{{
     116flight departure range
     117    -> flight / booking JOIN
     118    -> COUNT / SUM / AVG
     119    -> route_metrics CTE
     120    -> DENSE_RANK()
     121    -> top 5 routes per day
     122}}}
     123
     124Во тестот се обработуваат:
     125* 14,858 flights
     126* приближно 1.74M joined bookings
     127* 14,858 route groups
     128* 15 final rows
     129
     130Baseline без соодветен booking index беше анализиран само со `EXPLAIN`: `booking` имаше `ALL` access со проценка од приближно 54M редови. За baseline не беше мерено реално runtime.
     131
     132Беа споредени следните два индекси:
     133
     134|| '''Booking index''' || '''Access''' || '''Benchmark median''' || '''INDEX_LENGTH во lab''' ||
     135|| `(flight_id)` || non-covering lookup || 41.23 s || 1,694,466,048 B ||
     136|| `(flight_id,price)` || covering lookup || 1.89 s || 2,007,990,272 B ||
     137
     138Со додавање на `price` во индексот, вредностите потребни за `SUM(price)` и `AVG(price)` се достапни директно од индексот.
     139
     140Во оваа тест околина median времето се намали приближно 21.78 пати, но поширокиот индекс користи дополнителни 313,524,224 B во однос на потесниот.
     141
     142Aggregation, CTE materialization, ranking и sorting и понатаму остануваат дел од execution plan-от.
     143
     144[[Image(17.PNG)]]
     145
     146'''Слика 5.''' Посложен CTE/window query и дел од неговиот execution tree.
     147
     148=== 5. FULLTEXT index
     149
     150Покрај B+ tree индексите, MySQL поддржува и други типови на пребарување.
     151
     152Во `airplane_type` веќе постои:
     153
     154{{{
     155FULLTEXT description_full(identifier, description)
     156}}}
     157
    13158Пример:
    14 {{{
    15 
    16 CREATE INDEX idx_comp ON tabela (col_a, col_b, col_c);
    17 
    18 }}}
    19 
    20 Може да се користи за:\\
    21    WHERE col_a = ?\\
    22    WHERE col_a = ? AND col_b = ?\\
    23    WHERE col_a = ? AND col_b = ? AND col_c = ?\\
    24    WHERE col_a = ? ORDER BY col_b\\
    25  НЕ може за:\\
    26    WHERE col_b = ?        \\ 
    27    WHERE col_c = ?          \\
    28 
    29 \\
    30 
    31 **Covering Index**\\
    32 Индекс кој ги содржи сите колони од еден прашалник. Ако индексот ги има сите тие колони, MySQL не мора воопшто да ја чита самата табела (clustered data).
    33 Се што му треба, го зема директно од индексот. - Со овој тип на индекс се намалува бројот на И/О операции, што доведува до побрзо извршување, но е поголем индекс што зафаќа повеќе простор и ги успорува сите INSERT/UPDATE операции.\\
    34 Пример за креирање:
    35 {{{
    36 
    37 CREATE INDEX idx_cov ON booking (flight_id, price, seat);
    38 
    39 }}}
    40 \\
    41 **Hash индекс**\\
    42 Само за точна еднаквост (=). Не поддржува range, ORDER BY, GROUP BY. Во MySQL InnoDB НЕ може да се креира рачно - InnoDB го прави адаптивен hash автоматски. Може рачно само во MEMORY engine.\\
    43 Пример за креирање:\\
    44 {{{
    45 
    46 CREATE TABLE temp_lookup (
    47   id INT NOT NULL,
    48   val VARCHAR(50),
    49   PRIMARY KEY (id)
    50 ) ENGINE=MEMORY;
    51  
    52 CREATE INDEX idx_hash ON temp_lookup (vrednost) USING HASH;
    53 
    54 }}}
    55 
    56 Некои други типови: FULLTEXT индекс (за пребарување на текст), SPATIAL индекс (геолокациски).\\
    57 \\
    58 
    59 == Сценарија и индексирање на прашалници
    60 === Сценарио 1:  Агент на шалтер го пребарува целото патничко досие за конкретен патник
    61 Табели: booking JOIN flight JOIN passengerdetails\\
    62 Цел: B-Tree индекс на еден столбец (booking.passenger_id)\\
    63 \\
    64 Најпрво, за тестирање, ќе ја извршиме следната скрипта која безбедно ќе ги избрише сите предефинирани индекси кои веќе ги има во Airportdb од страна на MySql, без нарушување на foreign-key constraints://
    65 
    66 [wiki:Script1 Погледни ја скриптата овде]
    67 //
    68 **Прашалник без индекс:**
    69 {{{
    70  
    71 EXPLAIN ANALYZE
    72 SELECT
    73     b.booking_id,
    74     b.seat,
    75     b.price,
    76     f.flightno,
    77     f.departure,
    78     f.arrival,
    79     pd.firstname,
    80     pd.lastname,
    81     pd.country
    82 FROM booking b
    83 JOIN flight f         ON b.flight_id    = f.flight_id
    84 JOIN passengerdetails pd ON b.passenger_id = pd.passenger_id
    85 WHERE b.passenger_id = 10001
    86 ORDER BY f.departure DESC;
    87 
    88 }}}
    89 
    90 
    91 
    92 
     159
     160{{{
     161SELECT type_id, identifier,
     162       MATCH(identifier, description)
     163       AGAINST('Boeing' IN NATURAL LANGUAGE MODE) AS relevance
     164FROM airportdb.airplane_type
     165WHERE MATCH(identifier, description)
     166      AGAINST('Boeing' IN NATURAL LANGUAGE MODE)
     167ORDER BY relevance DESC, type_id;
     168}}}
     169
     170Прашалникот врати 36 редови, а execution plan-от покажа `Full-text index search`.
     171
     172[[Image(16.PNG)]]
     173
     174'''Слика 6.''' FULLTEXT пребарување со `MATCH ... AGAINST` и `Full-text index search`.
     175
     176FULLTEXT индексот е наменет за пребарување на текст според зборови и relevance, а не за обично B+ tree пребарување по еднаквост или опсег.
     177
     178SPATIAL индексите се користат за просторни податоци. Explicit HASH индекс не е нормална user-defined опција за InnoDB табелите во овој проект; таков индекс може директно да се користи, на пример, кај MEMORY engine.
     179
     180=== 6. Read / write / storage tradeoff
     181
     182Индексите го забрзуваат читањето, но имаат цена при INSERT, UPDATE и DELETE бидејќи мора да се одржуваат дополнителни index структури.
     183
     184За контролираниот INSERT тест:
     185
     186|| '''Мерка''' || '''PRIMARY only''' || '''PRIMARY + passenger index''' ||
     187|| Inserted rows || 197,129 || 197,129 ||
     188|| Duration || 1.363 s || 1.913 s ||
     189|| Additional secondary storage || - || 3,686,400 B ||
     190
     191Со passenger secondary index истиот INSERT batch беше 40.32% побавен бидејќи InnoDB мора да ги ажурира и clustered PRIMARY и secondary B+ tree структурата.
     192
     193Од друга страна, за passenger lookup истиот индекс го намали времето од 13.62 s на приближно 0.005 s. Затоа користа од индекс зависи од односот меѓу read и write операции и од достапниот storage.
     194
     195=== Заклучок
     196
     197Од тестовите се гледа дека индексите имаат најголем ефект кога се избрани според конкретниот начин на користење на податоците.
     198
     199Кај пребарувањето по `passenger_id`, secondary index го намали пристапот од околу 54.3M записи на 1,436. Кај composite index, редоследот на колоните влијаеше директно врз бројот на прочитани index entries. Covering index дополнително го намали пристапот до clustered редовите, а во дел од тестовите MySQL избра друг индекс иако постоеше дополнителен кандидат.
     200
     201Пошироките индекси зафаќаат повеќе простор и го зголемуваат трошокот при INSERT, UPDATE и DELETE. Затоа при избор на индекс треба да се споредат execution plan, бројот на обработени редови, времето на извршување и дополнителниот storage/write cost.