wiki:QueryOptimization

Version 78 (modified by 231027, 6 days ago) ( diff )

--

Оптимизација на прашалници

Анализа и оптимизација на Venue_Layout

Овој поглед ја прикажува деталната физичка структура на секој објект (сала), поврзувајќи ги поединечните седишта со нивните сектори и самите локации. Патеката на релациите е поставена линеарно, овозможувајќи брза проверка на точната позиција на седиштето преку неговиот ред и број.

CREATE OR REPLACE VIEW "Venue_Layout" AS
SELECT v.venue_id,
       v.name AS venue_name,
       s.section_id,
       s.name AS section_name,
       st.seat_id,
       st.row_number,
       st.seat_number
FROM "Venue" v
JOIN "Section" s ON v.venue_id = s.venue_id
JOIN "Seat" st ON s.section_id = st.section_id;

1. Примарен филтер:

Примарен филтер за овој поглед е venue_id (ID на објектот), бидејќи најчестото пребарување е насочено кон визуелизација или вчитување на комплетната мапа на седишта за еден конкретен објект кога се купува билет.

2. Случај на употреба:

Погледот се користи при интерактивниот приказ на салата/стадионот во корисничкиот интерфејс. Кога купувачот ќе избере настан, апликацијата мора веднаш да го исцрта распоредот на седишта по секции и редови за тој објект. Перформансите тука директно влијаат врз брзината на вчитување на корисничката страница за избор на седиште.

3. Иницијално време:

  • SELECT: 0.475 ms (Екстремно брзо поради постоечките уникатни констреинти uq_section_venue_name на табелата Section и uq_seat_section_number на табелата Seat).
  • INSERT: 19.912 ms (Релативно бавно, каде што најголемиот дел од времето паѓа на тригер проверката за foreign key констреинтот).
  • UPDATE: 0.137 ms (Инстантна брзина благодарение на примарниот клуч).

4. Анализа на планот на извршување (без индекси):

При SELECT операцијата, PostgreSQL паметно ги користи веќе постоечките уникатни индекси генерирани од бизнис констреинтите, овозможувајќи брз Index Scan. Меѓутоа, при INSERT во табелата Seat, базата троши дури 19.752 ms само на тригерот за проверка на foreign key (fk_seat_section), бидејќи без ажурирана статистика, планерот мора рачно да ја проверува релацијата на диск.

  • SELECT
EXPLAIN ANALYZE
SELECT * FROM "Venue_Layout"
WHERE venue_id = 1;

QUERY PLAN
Nested Loop (cost\=1.01..8110.27 rows\=1886 width\=54) (actual time\=0.152..0.475 rows\=775.00 loops\=1)
Buffers: shared hit\=21 read\=13 dirtied\=1
-> Nested Loop (cost\=0.57..23.73 rows\=5 width\=38) (actual time\=0.129..0.131 rows\=5.00 loops\=1)
Buffers: shared read\=6
-> Index Scan using ""Venue_pkey"" on ""Venue"" v (cost\=0.29..8.30 rows\=1 width\=28) (actual time\=0.061..0.061 rows\=1.00 loops\=1)
Index Cond: (venue_id \= 1)
Index Searches: 1
Buffers: shared read\=3
-> Index Scan using uq_section_venue_name on ""Section"" s (cost\=0.29..15.38 rows\=5 width\=18) (actual time\=0.051..0.052 rows\=5.00 loops\=1)
Index Cond: (venue_id \= 1)
Index Searches: 1
Buffers: shared read\=3
-> Index Scan using uq_seat_section_number on ""Seat"" st (cost\=0.44..1608.23 rows\=908 width\=24) (actual time\=0.009..0.050 rows\=155.00 loops\=5)
Index Cond: (section_id \= s.section_id)
Index Searches: 5
Buffers: shared hit\=21 read\=7 dirtied\=1
Planning:
Buffers: shared hit\=4 read\=11
Planning Time: 0.631 ms
Execution Time: 0.577 ms
  • INSERT
EXPLAIN ANALYZE
INSERT INTO "Seat" (seat_id, section_id, row_number, seat_number)
VALUES (99999999, 1, 1, 99);

QUERY PLAN
Insert on ""Seat"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=0.135..0.136 rows\=0.00 loops\=1)
Buffers: shared hit\=5 read\=3 dirtied\=1
-> Result (cost\=0.00..0.01 rows\=1 width\=24) (actual time\=0.001..0.001 rows\=1.00 loops\=1)
Planning Time: 0.058 ms
Trigger for constraint fk_seat_section: time\=19.752 calls\=1
Execution Time: 19.912 ms
  • UPDATE
EXPLAIN ANALYZE
UPDATE "Seat"
SET seat_number = 100
WHERE seat_id = 99999999;

QUERY PLAN
Update on ""Seat"" (cost\=0.44..8.46 rows\=0 width\=0) (actual time\=0.106..0.106 rows\=0.00 loops\=1)
Buffers: shared hit\=12
-> Index Scan using ""Seat_pkey"" on ""Seat"" (cost\=0.44..8.46 rows\=1 width\=10) (actual time\=0.048..0.049 rows\=1.00 loops\=1)
Index Cond: (seat_id \= 99999999)
Index Searches: 1
Buffers: shared hit\=4
Planning Time: 0.171 ms
Execution Time: 0.137 ms

5. Оптимизација и индексирање:

Бидејќи постоечките уникатни констреинти веќе идеално ги покриваат JOIN релациите, креирањето на дополнителни индекси е непотребно и би довело до залудно трошење на мемориски ресурси. Наместо тоа, за да го решиме тесното грло при INSERT операциите, се извршува наредбата ANALYZE за табелите во релација со цел да се обноват статистиките на внатрешниот планер.

ANALYZE "Venue";
ANALYZE "Section";
ANALYZE "Seat";

6. Резултат по оптимизација:

По извршување на ANALYZE, базата стекна целосен увид во дистрибуцијата на податоците, со што времето на INSERT се намали на 0.628 ms, што претставува забрзување од околу 30 пати. Операциите за SELECT и UPDATE ги задржаа своите врвни перформанси во под-милисекунден опсег.

Анализа и оптимизација на User_Tickets

Овој поглед дава детален хронолошки преглед на сите купени поединечни билети по корисник, вклучувајќи ја точната платена цена, QR-кодот за влез и терминот на настанот. Преку релацијата со ставките за рефундација, погледот нуди и инстантна информација за тоа кои карти се откажани и кога се вратени парите.

CREATE OR REPLACE VIEW "User_Tickets" AS
SELECT u.user_id,
       u.username,
       toi.order_item_id,
       t.ticket_id,
       e.event_id,
       e.name AS event_name,
       eh.event_time,
       toi.qr_code,
       toi.item_price AS price_paid,
       tri.refund_item_id,
       tr.refund_time
FROM "User" u
JOIN "Regular_User" ru ON u.user_id = ru.user_id
JOIN "Ticket_Order" o ON ru.user_id = o.user_id
JOIN "Ticket_Order_Item" toi ON o.order_id = toi.order_id
JOIN "Ticket" t ON toi.ticket_id = t.ticket_id
JOIN "Event_Happening" eh ON t.event_happening_id = eh.event_happening_id
JOIN "Event" e ON eh.event_id = e.event_id
LEFT JOIN "Ticket_Refund_Item" tri ON toi.order_item_id = tri.order_item_id
LEFT JOIN "Ticket_Refund" tr ON tri.refund_id = tr.refund_id;

1. Примарен филтер:

Примарен филтер за овој поглед е user_id (ID на корисникот), бидејќи најчестото и критично пребарување е кога најавениот клиент ја отвора својата корисничка профилна страница за да ги види своите активни или минати билети.

2. Случај на употреба:

Погледот претставува јадро на апликацијата во делот за дигитални билети (My Tickets). Секојпат кога корисникот сака да го прикаже својот QR-код на билетот за некој настан, системот го извршува овој прашалник. Доцнење на овој одзив предизвикува лошо корисничко искуство и застој на влезните капии.

3. Иницијално време:

  • SELECT: 676.751 ms (Исклучително бавно поради комплексноста од 9 поврзани табели и секвенцијално скенирање на огромната табела за билети).
  • INSERT: 18.187 ms (Бавно извршување поради диск операции при евалуација на констреинтите).
  • UPDATE: 0.071 ms (Инстантна брзина благодарение на примарниот клуч).

4. Анализа на планот на извршување (без индекси):

При селекција без индекси, базата е принудена да користи Gather операција со два дополнителни паралелни работници (Workers Planned: 2). Најголемиот проблем се јавува во тоа што се врши секвенцијално скенирање со проверка на релациите низ хард дискот, што генерира огромни 38,603 диск читања (shared read=38603). Кај INSERT операцијата, дури 17.637 ms се губат во тригерите за проверка на foreign key констреинтите бидејќи базата нема брза индексна патека до поврзаните записи.

  • SELECT
EXPLAIN ANALYZE
SELECT * FROM "User_Tickets"
WHERE user_id = 5;

QUERY PLAN
Gather (cost\=66949.49..416072.10 rows\=4 width\=145) (actual time\=667.628..676.676 rows\=0.00 loops\=1)
Workers Planned: 2
Workers Launched: 2
Buffers: shared read\=38603
-> Nested Loop Left Join (cost\=65949.49..415071.70 rows\=2 width\=145) (actual time\=612.744..612.748 rows\=0.00 loops\=3)
Buffers: shared read\=38603
-> Nested Loop Left Join (cost\=65949.07..415070.67 rows\=2 width\=145) (actual time\=612.743..612.747 rows\=0.00 loops\=3)
Buffers: shared read\=38603
-> Nested Loop (cost\=65948.64..415069.63 rows\=2 width\=129) (actual time\=612.742..612.746 rows\=0.00 loops\=3)
Buffers: shared read\=38603
-> Nested Loop (cost\=65948.35..415068.90 rows\=2 width\=98) (actual time\=612.742..612.745 rows\=0.00 loops\=3)
Buffers: shared read\=38603
-> Nested Loop (cost\=65948.07..415068.27 rows\=2 width\=90) (actual time\=612.741..612.745 rows\=0.00 loops\=3)
Buffers: shared read\=38603
-> Nested Loop (cost\=65947.63..415065.48 rows\=2 width\=82) (actual time\=612.741..612.744 rows\=0.00 loops\=3)
Buffers: shared read\=38603
-> Parallel Hash Join (cost\=65946.77..415052.55 rows\=2 width\=62) (actual time\=612.740..612.743 rows\=0.00 loops\=3)
Hash Cond: (toi.order_id \= o.order_id)
Buffers: shared read\=38603
-> Parallel Seq Scan on ""Ticket_Order_Item"" toi (cost\=0.00..326137.01 rows\=8750001 width\=62) (never executed)
-> Parallel Hash (cost\=65946.76..65946.76 rows\=1 width\=16) (actual time\=612.667..612.667 rows\=0.00 loops\=3)
Buckets: 1024 Batches: 1 Memory Usage: 0kB
Buffers: shared read\=38603
-> Parallel Seq Scan on ""Ticket_Order"" o (cost\=0.00..65946.76 rows\=1 width\=16) (actual time\=612.428..612.428 rows\=0.00 loops\=3)
Filter: (user_id \= 5)
Rows Removed by Filter: 1750000
Buffers: shared read\=38603
-> Materialize (cost\=0.86..12.91 rows\=1 width\=36) (never executed)
-> Nested Loop (cost\=0.86..12.90 rows\=1 width\=36) (never executed)
-> Index Scan using ""User_pkey"" on ""User"" u (cost\=0.43..8.45 rows\=1 width\=28) (never executed)
Index Cond: (user_id \= 5)
Index Searches: 0
-> Index Only Scan using ""Regular_User_pkey"" on ""Regular_User"" ru (cost\=0.43..4.45 rows\=1 width\=8) (never executed)
Index Cond: (user_id \= 5)
Heap Fetches: 0
Index Searches: 0
-> Index Scan using ""Ticket_pkey"" on ""Ticket"" t (cost\=0.44..1.39 rows\=1 width\=16) (never executed)
Index Cond: (ticket_id \= toi.ticket_id)
Index Searches: 0
-> Index Scan using ""Event_Happening_pkey"" on ""Event_Happening"" eh (cost\=0.29..0.31 rows\=1 width\=24) (never executed)
Index Cond: (event_happening_id \= t.event_happening_id)
Index Searches: 0
-> Index Scan using ""Event_pkey"" on ""Event"" e (cost\=0.29..0.37 rows\=1 width\=39) (never executed)
Index Cond: (event_id \= eh.event_id)
Index Searches: 0
-> Index Scan using ""Ticket_Refund_Item_order_item_id_key"" on ""Ticket_Refund_Item"" tri (cost\=0.43..0.52 rows\=1 width\=24) (never executed)
Index Cond: (order_item_id \= toi.order_item_id)
Index Searches: 0
-> Index Scan using ""Ticket_Refund_pkey"" on ""Ticket_Refund"" tr (cost\=0.42..0.52 rows\=1 width\=16) (never executed)
Index Cond: (refund_id \= tri.refund_id)
Index Searches: 0
Planning:
Buffers: shared hit\=38 read\=57 dirtied\=3
Planning Time: 29.035 ms
Execution Time: 676.751 ms
  • INSERT
EXPLAIN ANALYZE
INSERT INTO "Ticket_Order_Item" (order_item_id, order_id, ticket_id, item_price, qr_code)
VALUES (99999999, 1, 1, 1200.00, '3ebd5fa12ea8781d1e9ae4333484984a');

QUERY PLAN
Insert on ""Ticket_Order_Item"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=17.637..17.638 rows\=0.00 loops\=1)
Buffers: shared hit\=4 read\=3 dirtied\=1
-> Result (cost\=0.00..0.01 rows\=1 width\=556) (actual time\=0.001..0.002 rows\=1.00 loops\=1)
Planning Time: 0.029 ms
Trigger for constraint fk_item_order: time\=0.342 calls\=1
Trigger for constraint fk_item_ticket: time\=0.191 calls\=1
Execution Time: 18.187 ms
  • UPDATE
EXPLAIN ANALYZE
UPDATE "Ticket_Order_Item"
SET qr_code = '3ebd5fa12ec8781d1e9ae4333484984a'
WHERE order_item_id = 99999999;

QUERY PLAN
Update on ""Ticket_Order_Item"" (cost\=0.44..8.46 rows\=0 width\=0) (actual time\=0.051..0.052 rows\=0.00 loops\=1)
Buffers: shared hit\=7
-> Index Scan using ""Ticket_Order_Item_pkey"" on ""Ticket_Order_Item"" (cost\=0.44..8.46 rows\=1 width\=522) (actual time\=0.023..0.024 rows\=1.00 loops\=1)
Index Cond: (order_item_id \= 99999999)
Index Searches: 1
Buffers: shared hit\=4
Planning Time: 0.137 ms
Execution Time: 0.071 ms

5. Оптимизација и индексирање:

За драстично кратење на времето, воведуваме B-tree индекси врз надворешните клучеви кои ја контролираат хиерархијата на релациите од корисникот па се до ставката на нарачката. Дополнително, се извршува ANALYZE за стабилизација на статистиките.

CREATE INDEX CONCURRENTLY idx_ticket_order_user_id
ON "Ticket_Order" (user_id);

CREATE INDEX CONCURRENTLY idx_toi_order_id
ON "Ticket_Order_Item" (order_id);

CREATE INDEX CONCURRENTLY idx_toi_ticket_id
ON "Ticket_Order_Item" (ticket_id);


ANALYZE "Ticket_Order";
ANALYZE "Ticket_Order_Item";

6. Резултат по оптимизација:

По воведувањето на индексите и ажурирањето на статистиката, добиен е најголемиот перформансен бенефит во системот:

  • Времето за SELECT падна на неверојатни 0.073 ms, што претставува забрзување од над 9.000 пати. Читањето од диск е целосно елиминирано (shared read=0), а базата сега извршува директен и молскавично брз Index Scan во RAM меморијата користејќи само 11 мемориски буфери.
  • Времето за INSERT падна на 0.655 ms (околу 27 пати побрзо), со оглед на тоа што проверката на foreign key констреинтите сега веднаш се резолвира преку новите индексни структури.
  • Операцијата UPDATE ги задржа своите стабилни и конзистентни под-милисекундни перформанси (0.135 ms).
  • SELECT
EXPLAIN ANALYZE
SELECT * FROM "User_Tickets"
WHERE user_id = 99;

QUERY PLAN
Nested Loop Left Join (cost\=3.59..42.94 rows\=4 width\=145) (actual time\=0.070..0.073 rows\=0.00 loops\=1)
Buffers: shared hit\=11
-> Nested Loop Left Join (cost\=3.17..40.87 rows\=4 width\=145) (actual time\=0.070..0.072 rows\=0.00 loops\=1)
Buffers: shared hit\=11
-> Nested Loop (cost\=2.74..38.81 rows\=4 width\=129) (actual time\=0.070..0.071 rows\=0.00 loops\=1)
Buffers: shared hit\=11
-> Nested Loop (cost\=2.46..37.34 rows\=4 width\=98) (actual time\=0.069..0.071 rows\=0.00 loops\=1)
Buffers: shared hit\=11
-> Nested Loop (cost\=2.17..36.09 rows\=4 width\=90) (actual time\=0.069..0.071 rows\=0.00 loops\=1)
Buffers: shared hit\=11
-> Nested Loop (cost\=1.73..30.51 rows\=4 width\=82) (actual time\=0.069..0.070 rows\=0.00 loops\=1)
Buffers: shared hit\=11
-> Nested Loop (cost\=1.29..21.36 rows\=1 width\=36) (actual time\=0.069..0.069 rows\=0.00 loops\=1)
Buffers: shared hit\=11
-> Nested Loop (cost\=0.86..12.90 rows\=1 width\=36) (actual time\=0.039..0.041 rows\=1.00 loops\=1)
Buffers: shared hit\=8
-> Index Scan using ""User_pkey"" on ""User"" u (cost\=0.43..8.45 rows\=1 width\=28) (actual time\=0.017..0.018 rows\=1.00 loops\=1)
Index Cond: (user_id \= 99)
Index Searches: 1
Buffers: shared hit\=4
-> Index Only Scan using ""Regular_User_pkey"" on ""Regular_User"" ru (cost\=0.43..4.45 rows\=1 width\=8) (actual time\=0.018..0.019 rows\=1.00 loops\=1)
Index Cond: (user_id \= 99)
Heap Fetches: 0
Index Searches: 1
Buffers: shared hit\=4
-> Index Scan using idx_ticket_order_user_id on ""Ticket_Order"" o (cost\=0.43..8.44 rows\=1 width\=16) (actual time\=0.027..0.027 rows\=0.00 loops\=1)
Index Cond: (user_id \= 99)
Index Searches: 1
Buffers: shared hit\=3
-> Index Scan using idx_toi_order_id on ""Ticket_Order_Item"" toi (cost\=0.44..8.89 rows\=26 width\=62) (never executed)
Index Cond: (order_id \= o.order_id)
Index Searches: 0
-> Index Scan using ""Ticket_pkey"" on ""Ticket"" t (cost\=0.44..1.39 rows\=1 width\=16) (never executed)
Index Cond: (ticket_id \= toi.ticket_id)
Index Searches: 0
-> Index Scan using ""Event_Happening_pkey"" on ""Event_Happening"" eh (cost\=0.29..0.31 rows\=1 width\=24) (never executed)
Index Cond: (event_happening_id \= t.event_happening_id)
Index Searches: 0
-> Index Scan using ""Event_pkey"" on ""Event"" e (cost\=0.29..0.37 rows\=1 width\=39) (never executed)
Index Cond: (event_id \= eh.event_id)
Index Searches: 0
-> Index Scan using ""Ticket_Refund_Item_order_item_id_key"" on ""Ticket_Refund_Item"" tri (cost\=0.43..0.52 rows\=1 width\=24) (never executed)
Index Cond: (order_item_id \= toi.order_item_id)
Index Searches: 0
-> Index Scan using ""Ticket_Refund_pkey"" on ""Ticket_Refund"" tr (cost\=0.42..0.52 rows\=1 width\=16) (never executed)
Index Cond: (refund_id \= tri.refund_id)
Index Searches: 0
Planning:
Buffers: shared hit\=295 read\=8
Planning Time: 74.096 ms
Execution Time: 0.196 ms
  • INSERT
EXPLAIN ANALYZE
INSERT INTO "Ticket_Order_Item" (order_item_id, order_id, ticket_id, item_price, qr_code)
VALUES (77777777, 1, 1, 1200.00, '3ebc5fd22ec8681d1e9ae4333484984a');

QUERY PLAN
Insert on ""Ticket_Order_Item"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=0.307..0.307 rows\=0.00 loops\=1)
Buffers: shared hit\=12 dirtied\=3
-> Result (cost\=0.00..0.01 rows\=1 width\=556) (actual time\=0.001..0.001 rows\=1.00 loops\=1)
Planning Time: 0.032 ms
Trigger for constraint fk_item_order: time\=0.200 calls\=1
Trigger for constraint fk_item_ticket: time\=0.132 calls\=1
Execution Time: 0.655 ms
  • UPDATE
EXPLAIN ANALYZE
UPDATE "Ticket_Order_Item"
SET qr_code = '3ebc5fd20ec8681d1e9ee5733484984a'
WHERE order_item_id = 77777777;

QUERY PLAN
Update on ""Ticket_Order_Item"" (cost\=0.44..8.46 rows\=0 width\=0) (actual time\=0.091..0.091 rows\=0.00 loops\=1)
Buffers: shared hit\=7
-> Index Scan using ""Ticket_Order_Item_pkey"" on ""Ticket_Order_Item"" (cost\=0.44..8.46 rows\=1 width\=522) (actual time\=0.022..0.023 rows\=1.00 loops\=1)
Index Cond: (order_item_id \= 77777777)
Index Searches: 1
Buffers: shared hit\=4
Planning Time: 0.120 ms
Execution Time: 0.135 ms

Анализа и оптимизација на Future_Events

Овој поглед служи за динамично генерирање на репертоарот, прикажувајќи ги исклучиво претстојните настани преку филтрирање на изминатите термини во однос на моменталното време на системот. Дополнително, тој ја прикажува комплетната географска адреса и локација на објектот каде ќе се одржи настанот.

CREATE OR REPLACE VIEW "Future_Events" AS
SELECT
    e.event_id,
    e.name AS event_name,
    eh.event_happening_id,
    eh.event_time,
    v.venue_id,
    v.name AS venue_name,
    v.address_street AS street,
    v.address_city AS city,
    v.address_country AS country
FROM "Event" e
JOIN "Event_Happening" eh ON e.event_id = eh.event_id
JOIN "Venue" v ON eh.venue_id = v.venue_id
WHERE eh.event_time > CURRENT_TIMESTAMP;

1. Примарен филтер:

Примарниот филтер е колоната event_time во табелата Event_Happening. Погледот постојано го бара само подмножеството на записи кои се во иднина.

2. Случај на употреба:

Овој поглед е „лицето“ на платформата. Се користи при вчитување на листата на претстојни настани за корисниците. Ефикасноста овде директно го дефинира времето на првично вчитување на апликацијата.

3. Иницијално време:

  • SELECT: 100.080 ms (Бавно поради Seq Scan низ илјадници записи во Event_Happening за проверка на времето).
  • INSERT: 12.452 ms (Процесирањето на тригерите и запишувањето на податокот).
  • UPDATE: 0.104 ms (Инстантна брзина преку primary key).

4. Анализа на планот на извршување (без индекси):

Без индекс на времето, базата мораше да прави Sequential Scan низ целата табела Event_Happening (12,973 записи), што резултираше со бавно филтрирање во меморијата (Filter: event_time > CURRENT_TIMESTAMP). Дополнително, базата трошеше непотребни 466 читања од диск за да ја провери секоја редица.

  • SELECT
EXPLAIN ANALYZE
SELECT * FROM "Future_Events"
WHERE city = 'London';

QUERY PLAN
Nested Loop (cost\=263.49..1377.68 rows\=332 width\=121) (actual time\=3.243..100.080 rows\=340.00 loops\=1)
Buffers: shared hit\=778 read\=844
-> Hash Join (cost\=263.20..1234.31 rows\=332 width\=90) (actual time\=2.695..18.025 rows\=340.00 loops\=1)
Hash Cond: (eh.venue_id \= v.venue_id)
Buffers: shared hit\=2 read\=600
-> Seq Scan on ""Event_Happening"" eh (cost\=0.00..937.09 rows\=12957 width\=32) (actual time\=0.154..12.105 rows\=12973.00 loops\=1)
Filter: (event_time > CURRENT_TIMESTAMP)
Rows Removed by Filter: 18365
Buffers: shared hit\=1 read\=466
-> Hash (cost\=260.00..260.00 rows\=256 width\=66) (actual time\=2.516..2.517 rows\=256.00 loops\=1)
Buckets: 1024 Batches: 1 Memory Usage: 33kB
Buffers: shared hit\=1 read\=134
-> Seq Scan on ""Venue"" v (cost\=0.00..260.00 rows\=256 width\=66) (actual time\=0.222..2.393 rows\=256.00 loops\=1)
Filter: ((address_city)::text \= 'London'::text)
Rows Removed by Filter: 9744
Buffers: shared hit\=1 read\=134
-> Index Scan using ""Event_pkey"" on ""Event"" e (cost\=0.29..0.43 rows\=1 width\=39) (actual time\=0.237..0.237 rows\=1.00 loops\=340)
Index Cond: (event_id \= eh.event_id)
Index Searches: 340
Buffers: shared hit\=776 read\=244
Planning:
Buffers: shared hit\=31 read\=8
Planning Time: 0.659 ms
Execution Time: 100.249 ms
  • INSERT
EXPLAIN ANALYZE
INSERT INTO "Event_Happening" (event_happening_id, event_id, venue_id, event_time, duration_minutes, organizers)
VALUES (99999999, 1, 1, '2026-12-31 20:00:00', 120, 'Avalon Production');

QUERY PLAN
Insert on ""Event_Happening"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=12.004..12.005 rows\=0.00 loops\=1)
Buffers: shared hit\=4 read\=5 dirtied\=1
-> Result (cost\=0.00..0.01 rows\=1 width\=100) (actual time\=0.002..0.003 rows\=1.00 loops\=1)
Planning Time: 0.047 ms
Trigger for constraint fk_happening_event: time\=0.276 calls\=1
Trigger for constraint fk_happening_venue: time\=0.134 calls\=1
Execution Time: 12.452 ms
  • UPDATE
EXPLAIN ANALYZE
UPDATE "Event_Happening"
SET event_time = '2027-01-01 21:00:00'
WHERE event_happening_id = 99999999;

QUERY PLAN
Update on ""Event_Happening"" (cost\=0.29..8.30 rows\=0 width\=0) (actual time\=0.072..0.072 rows\=0.00 loops\=1)
Buffers: shared hit\=12
-> Index Scan using ""Event_Happening_pkey"" on ""Event_Happening"" (cost\=0.29..8.30 rows\=1 width\=14) (actual time\=0.023..0.024 rows\=1.00 loops\=1)
Index Cond: (event_happening_id \= 99999999)
Index Searches: 1
Buffers: shared hit\=3
Planning Time: 0.089 ms
Execution Time: 0.104 ms

5. Оптимизација и индексирање:

За да се елиминира потребата од пребарување на целата табела, креиравме B-tree индекс врз колоната event_time. Ова му овозможува на планерот да го лоцира временскиот „праг“ моментално.

CREATE INDEX CONCURRENTLY idx_event_happening_time
ON "Event_Happening" (event_time);


ANALYZE "Event_Happening";

6. Резултат по оптимизација:

Со воведувањето на индексот, базата премина на Bitmap Index Scan, што значи дека ја чита само релевантната мапа на записи наместо целиот диск.

  • Времето за SELECT падна на 8.451 ms, што е приближно 12 пати побрзо.
  • Времето за INSERT падна на 0.458 ms (околу 27 пати побрзо), бидејќи индексната структура се ажурира ефикасно.
  • SELECT
EXPLAIN ANALYZE
SELECT * FROM "Future_Events"
WHERE city = 'Berlin';

QUERY PLAN
Nested Loop (cost\=509.90..1246.42 rows\=96 width\=121) (actual time\=2.527..8.451 rows\=114.00 loops\=1)
Buffers: shared hit\=981
-> Hash Join (cost\=509.62..1204.96 rows\=96 width\=90) (actual time\=2.504..7.880 rows\=114.00 loops\=1)
Hash Cond: (eh.venue_id \= v.venue_id)
Buffers: shared hit\=639
-> Bitmap Heap Scan on ""Event_Happening"" eh (cost\=248.69..910.02 rows\=12955 width\=32) (actual time\=0.722..3.795 rows\=12973.00 loops\=1)
Recheck Cond: (event_time > CURRENT_TIMESTAMP)
Heap Blocks: exact\=467
Buffers: shared hit\=504
-> Bitmap Index Scan on idx_event_happening_time (cost\=0.00..245.45 rows\=12955 width\=0) (actual time\=0.656..0.656 rows\=12976.00 loops\=1)
Index Cond: (event_time > CURRENT_TIMESTAMP)
Index Searches: 1
Buffers: shared hit\=37
-> Hash (cost\=260.00..260.00 rows\=74 width\=66) (actual time\=1.770..1.771 rows\=74.00 loops\=1)
Buckets: 1024 Batches: 1 Memory Usage: 15kB
Buffers: shared hit\=135
-> Seq Scan on ""Venue"" v (cost\=0.00..260.00 rows\=74 width\=66) (actual time\=0.011..1.739 rows\=74.00 loops\=1)
Filter: ((address_city)::text \= 'Berlin'::text)
Rows Removed by Filter: 9926
Buffers: shared hit\=135
-> Index Scan using ""Event_pkey"" on ""Event"" e (cost\=0.29..0.43 rows\=1 width\=39) (actual time\=0.004..0.004 rows\=1.00 loops\=114)
Index Cond: (event_id \= eh.event_id)
Index Searches: 114
Buffers: shared hit\=342
Planning:
Buffers: shared hit\=40
Planning Time: 0.547 ms
Execution Time: 8.502 ms
  • INSERT
EXPLAIN ANALYZE
INSERT INTO "Event_Happening" (event_happening_id, event_id, venue_id, event_time, duration_minutes, organizers)
VALUES (88888888, 1, 1, '2028-05-15 19:30:00', 90, 'Berlin Concerts GmbH');

QUERY PLAN
Insert on ""Event_Happening"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=0.275..0.275 rows\=0.00 loops\=1)
Buffers: shared hit\=12 dirtied\=2
-> Result (cost\=0.00..0.01 rows\=1 width\=100) (actual time\=0.001..0.001 rows\=1.00 loops\=1)
Planning Time: 0.031 ms
Trigger for constraint fk_happening_event: time\=0.109 calls\=1
Trigger for constraint fk_happening_venue: time\=0.058 calls\=1
Execution Time: 0.458 ms
  • UPDATE
EXPLAIN ANALYZE
UPDATE "Event_Happening"
SET event_time = '2028-05-16 20:00:00'
WHERE event_happening_id = 88888888;

QUERY PLAN
Update on ""Event_Happening"" (cost\=0.29..8.30 rows\=0 width\=0) (actual time\=0.073..0.073 rows\=0.00 loops\=1)
Buffers: shared hit\=14
-> Index Scan using ""Event_Happening_pkey"" on ""Event_Happening"" (cost\=0.29..8.30 rows\=1 width\=14) (actual time\=0.022..0.023 rows\=1.00 loops\=1)
Index Cond: (event_happening_id \= 88888888)
Index Searches: 1
Buffers: shared hit\=3
Planning Time: 0.107 ms
Execution Time: 0.119 ms

Анализа и оптимизација на Available_Tickets

Овој поглед овозможува моментален увид во инвентарот на достапни слободни седишта за активните настани. Во него е имплементирана комплексна логика која ја калкулира крајната продажна цена во реално време, земајќи го предвид актуелниот процент на попуст за тековниот временски период во кој се наоѓа купувачот.

CREATE OR REPLACE VIEW "Available_Tickets" AS
SELECT
    t.ticket_id,
    ROUND(
        t.base_price * (1 - COALESCE(ep.price_discount_percent, 0) / 100.0),
        2
    ) AS price,
    e.event_id,
    e.name AS event_name,
    eh.event_happening_id,
    eh.event_time,
    v.name AS venue_name,
    s.name AS section_name,
    st.row_number,
    st.seat_number
FROM "Ticket" t
JOIN "Event_Happening" eh ON t.event_happening_id = eh.event_happening_id
JOIN "Event" e ON eh.event_id = e.event_id
JOIN "Venue" v ON eh.venue_id = v.venue_id
JOIN "Seat" st ON t.seat_id = st.seat_id
JOIN "Section" s ON st.section_id = s.section_id
LEFT JOIN "Event_Period" ep ON eh.event_happening_id = ep.event_happening_id
                           AND CURRENT_DATE BETWEEN ep.start_date AND ep.end_date
WHERE t.is_available = TRUE;

1. Примарен филтер:

Погледот се потпира на два филтера: event_happening_id (за специфичниот настани) и is_available (за статусот на билетот).

2. Случај на употреба:

Ова е клучниот поглед што се користи во моментот кога корисникот сака да купи билет. Брзината овде директно го намалува ризикот од "Race Condition" - односно, двајца корисници да се обидат да го резервираат истото место истовремено поради доцнење на податоците.

3. Иницијално време:

  • SELECT: 79.398 ms (Релативно бавно поради Bitmap Heap Scan и Filter операција врз табела со илјадници записи).
  • INSERT: 18.031 ms (Високо време поради проверките на констреинтите при вметнување нови билети).
  • UPDATE: 0.125 ms (Инстантна брзина преку primary key).

4. Анализа на планот на извршување (без индекси):

Без композитен индекс, базата користеше Bitmap Heap Scan за пребарување на билетите. Иако го пронаоѓаше настанот, таа мораше да прави дополнителна проверка во меморијата (Filter: is_available) и да отфрла огромен број записи (Rows Removed by Filter: 374). Ова трошеше 754 читања од диск.

  • SELECT
EXPLAIN ANALYZE
SELECT * FROM "Available_Tickets"
WHERE event_happening_id = 1;

QUERY PLAN
Nested Loop (cost\=2838.67..2934.30 rows\=357 width\=125) (actual time\=77.881..79.398 rows\=360.00 loops\=1)
Buffers: shared hit\=36 read\=754 dirtied\=1
-> Nested Loop Left Join (cost\=5.31..45.04 rows\=1 width\=79) (actual time\=3.244..3.250 rows\=1.00 loops\=1)
Buffers: shared hit\=8 read\=5
-> Nested Loop (cost\=0.86..24.91 rows\=1 width\=75) (actual time\=2.758..2.763 rows\=1.00 loops\=1)
Buffers: shared hit\=8 read\=1
-> Nested Loop (cost\=0.57..16.61 rows\=1 width\=63) (actual time\=0.020..0.024 rows\=1.00 loops\=1)
Buffers: shared hit\=6
-> Index Scan using ""Event_Happening_pkey"" on ""Event_Happening"" eh (cost\=0.29..8.30 rows\=1 width\=32) (actual time\=0.013..0.016 rows\=1.00 loops\=1)
Index Cond: (event_happening_id \= 1)
Index Searches: 1
Buffers: shared hit\=3
-> Index Scan using ""Event_pkey"" on ""Event"" e (cost\=0.29..8.30 rows\=1 width\=39) (actual time\=0.005..0.005 rows\=1.00 loops\=1)
Index Cond: (event_id \= eh.event_id)
Index Searches: 1
Buffers: shared hit\=3
-> Index Scan using ""Venue_pkey"" on ""Venue"" v (cost\=0.29..8.30 rows\=1 width\=28) (actual time\=2.734..2.734 rows\=1.00 loops\=1)
Index Cond: (venue_id \= eh.venue_id)
Index Searches: 1
Buffers: shared hit\=2 read\=1
-> Bitmap Heap Scan on ""Event_Period"" ep (cost\=4.45..20.12 rows\=1 width\=12) (actual time\=0.481..0.481 rows\=0.00 loops\=1)
Recheck Cond: (event_happening_id \= 1)
Filter: ((CURRENT_DATE >\= start_date) AND (CURRENT_DATE <\= end_date))
Rows Removed by Filter: 4
Heap Blocks: exact\=1
Buffers: shared read\=4
-> Bitmap Index Scan on uq_period_happening_name (cost\=0.00..4.45 rows\=4 width\=0) (actual time\=0.088..0.088 rows\=4.00 loops\=1)
Index Cond: (event_happening_id \= 1)
Index Searches: 1
Buffers: shared read\=3
-> Merge Join (cost\=2833.37..2881.23 rows\=357 width\=31) (actual time\=74.625..75.812 rows\=360.00 loops\=1)
Merge Cond: (st.seat_id \= t.seat_id)
Buffers: shared hit\=28 read\=749 dirtied\=1
-> Nested Loop (cost\=0.74..1196963.53 rows\=20753208 width\=18) (actual time\=0.227..1.135 rows\=734.00 loops\=1)
Buffers: shared hit\=23 read\=2
-> Index Scan using ""Seat_pkey"" on ""Seat"" st (cost\=0.44..671104.56 rows\=20753208 width\=24) (actual time\=0.024..0.343 rows\=734.00 loops\=1)
Index Searches: 1
Buffers: shared hit\=8 read\=2
-> Memoize (cost\=0.30..0.32 rows\=1 width\=10) (actual time\=0.001..0.001 rows\=1.00 loops\=734)
Cache Key: st.section_id
Cache Mode: logical
Hits: 729 Misses: 5 Evictions: 0 Overflows: 0 Memory Usage: 1kB
Buffers: shared hit\=15
-> Index Scan using ""Section_pkey"" on ""Section"" s (cost\=0.29..0.31 rows\=1 width\=10) (actual time\=0.009..0.009 rows\=1.00 loops\=5)
Index Cond: (section_id \= st.section_id)
Index Searches: 5
Buffers: shared hit\=15
-> Sort (cost\=2830.84..2831.74 rows\=357 width\=29) (actual time\=74.391..74.441 rows\=360.00 loops\=1)
Sort Key: t.seat_id
Sort Method: quicksort Memory: 41kB
Buffers: shared hit\=5 read\=747 dirtied\=1
-> Bitmap Heap Scan on ""Ticket"" t (cost\=22.15..2815.71 rows\=357 width\=29) (actual time\=1.866..73.806 rows\=360.00 loops\=1)
Recheck Cond: (event_happening_id \= 1)
Filter: is_available
Rows Removed by Filter: 374
Heap Blocks: exact\=744
Buffers: shared hit\=5 read\=747 dirtied\=1
-> Bitmap Index Scan on uq_ticket_happening_seat (cost\=0.00..22.06 rows\=733 width\=0) (actual time\=1.599..1.599 rows\=744.00 loops\=1)
Index Cond: (event_happening_id \= 1)
Index Searches: 1
Buffers: shared hit\=4 read\=4
Planning:
Buffers: shared hit\=59 read\=8
Planning Time: 1.571 ms
Execution Time: 79.504 ms
  • INSERT
EXPLAIN ANALYZE
INSERT INTO "Ticket" (ticket_id, event_happening_id, seat_id, base_price, is_available)
VALUES (99999999, 1, 99999, 1500.00, TRUE);

QUERY PLAN
Insert on ""Ticket"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=16.622..16.622 rows\=0.00 loops\=1)
Buffers: shared hit\=8 read\=3 dirtied\=2
-> Result (cost\=0.00..0.01 rows\=1 width\=41) (actual time\=0.001..0.002 rows\=1.00 loops\=1)
Planning Time: 0.028 ms
Trigger for constraint fk_ticket_seat: time\=1.188 calls\=1
Trigger for constraint fk_ticket_happening: time\=0.202 calls\=1
Execution Time: 18.031 ms
  • UPDATE
EXPLAIN ANALYZE
UPDATE "Ticket"
SET is_available = FALSE
WHERE ticket_id = 99999999;

QUERY PLAN
Update on ""Ticket"" (cost\=0.44..8.46 rows\=0 width\=0) (actual time\=0.086..0.087 rows\=0.00 loops\=1)
Buffers: shared hit\=7
-> Index Scan using ""Ticket_pkey"" on ""Ticket"" (cost\=0.44..8.46 rows\=1 width\=7) (actual time\=0.036..0.038 rows\=1.00 loops\=1)
Index Cond: (ticket_id \= 99999999)
Index Searches: 1
Buffers: shared hit\=4
Planning Time: 0.254 ms
Execution Time: 0.125 ms

5. Оптимизација и индексирање:

За да се елиминира потребата од скенирање на сите билети (вклучително и оние што веќе се продадени), го имплементиравме овој индекс:

CREATE INDEX CONCURRENTLY idx_ticket_available_happening
ON "Ticket" (event_happening_id)
WHERE is_available = TRUE;


ANALYZE "Ticket";

6. Резултат по оптимизација:

Со индексот, времето на извршување драматично се подобри:

  • Времето за SELECT падна на 1.800 ms, што е 44 пати побрзо. Базата сега користи директна патека до податоците и нема „филтрирање“ на отфрлени редици.
  • Времето за INSERT падна на 0.628 ms (околу 28 пати побрзо), бидејќи констреинтите сега побрзо ги потврдуваат постојните записи.
  • SELECT
EXPLAIN ANALYZE
SELECT * FROM "Available_Tickets"
WHERE event_happening_id = 2;

QUERY PLAN
Nested Loop (cost\=1436.93..1541.30 rows\=363 width\=125) (actual time\=0.899..1.800 rows\=360.00 loops\=1)
Buffers: shared hit\=402
-> Nested Loop Left Join (cost\=5.31..45.04 rows\=1 width\=79) (actual time\=0.078..0.081 rows\=1.00 loops\=1)
Buffers: shared hit\=13
-> Nested Loop (cost\=0.86..24.91 rows\=1 width\=75) (actual time\=0.031..0.034 rows\=1.00 loops\=1)
Buffers: shared hit\=9
-> Nested Loop (cost\=0.57..16.61 rows\=1 width\=63) (actual time\=0.025..0.027 rows\=1.00 loops\=1)
Buffers: shared hit\=6
-> Index Scan using ""Event_Happening_pkey"" on ""Event_Happening"" eh (cost\=0.29..8.30 rows\=1 width\=32) (actual time\=0.016..0.017 rows\=1.00 loops\=1)
Index Cond: (event_happening_id \= 2)
Index Searches: 1
Buffers: shared hit\=3
-> Index Scan using ""Event_pkey"" on ""Event"" e (cost\=0.29..8.30 rows\=1 width\=39) (actual time\=0.005..0.005 rows\=1.00 loops\=1)
Index Cond: (event_id \= eh.event_id)
Index Searches: 1
Buffers: shared hit\=3
-> Index Scan using ""Venue_pkey"" on ""Venue"" v (cost\=0.29..8.30 rows\=1 width\=28) (actual time\=0.005..0.005 rows\=1.00 loops\=1)
Index Cond: (venue_id \= eh.venue_id)
Index Searches: 1
Buffers: shared hit\=3
-> Bitmap Heap Scan on ""Event_Period"" ep (cost\=4.45..20.12 rows\=1 width\=12) (actual time\=0.043..0.043 rows\=0.00 loops\=1)
Recheck Cond: (event_happening_id \= 2)
Filter: ((CURRENT_DATE >\= start_date) AND (CURRENT_DATE <\= end_date))
Rows Removed by Filter: 4
Heap Blocks: exact\=1
Buffers: shared hit\=4
-> Bitmap Index Scan on uq_period_happening_name (cost\=0.00..4.45 rows\=4 width\=0) (actual time\=0.008..0.008 rows\=4.00 loops\=1)
Index Cond: (event_happening_id \= 2)
Index Searches: 1
Buffers: shared hit\=3
-> Merge Join (cost\=1431.62..1488.09 rows\=363 width\=31) (actual time\=0.815..1.498 rows\=360.00 loops\=1)
Merge Cond: (st.seat_id \= t.seat_id)
Buffers: shared hit\=389
-> Nested Loop (cost\=0.74..1197017.90 rows\=20753360 width\=18) (actual time\=0.177..0.692 rows\=735.00 loops\=1)
Buffers: shared hit\=25
-> Index Scan using ""Seat_pkey"" on ""Seat"" st (cost\=0.44..671106.84 rows\=20753360 width\=24) (actual time\=0.009..0.188 rows\=735.00 loops\=1)
Index Searches: 1
Buffers: shared hit\=10
-> Memoize (cost\=0.30..0.32 rows\=1 width\=10) (actual time\=0.000..0.000 rows\=1.00 loops\=735)
Cache Key: st.section_id
Cache Mode: logical
Hits: 730 Misses: 5 Evictions: 0 Overflows: 0 Memory Usage: 1kB
Buffers: shared hit\=15
-> Index Scan using ""Section_pkey"" on ""Section"" s (cost\=0.29..0.31 rows\=1 width\=10) (actual time\=0.003..0.004 rows\=1.00 loops\=5)
Index Cond: (section_id \= st.section_id)
Index Searches: 5
Buffers: shared hit\=15
-> Sort (cost\=1429.07..1429.98 rows\=363 width\=29) (actual time\=0.629..0.658 rows\=360.00 loops\=1)
Sort Key: t.seat_id
Sort Method: quicksort Memory: 41kB
Buffers: shared hit\=364
-> Bitmap Heap Scan on ""Ticket"" t (cost\=7.25..1413.64 rows\=363 width\=29) (actual time\=0.088..0.525 rows\=360.00 loops\=1)
Recheck Cond: ((event_happening_id \= 2) AND is_available)
Heap Blocks: exact\=361
Buffers: shared hit\=364
-> Bitmap Index Scan on idx_ticket_available_happening (cost\=0.00..7.16 rows\=363 width\=0) (actual time\=0.038..0.038 rows\=361.00 loops\=1)
Index Cond: (event_happening_id \= 2)
Index Searches: 1
Buffers: shared hit\=3
Planning:
Buffers: shared hit\=100 read\=2
Planning Time: 8.763 ms
Execution Time: 1.992 ms
  • INSERT
EXPLAIN ANALYZE
INSERT INTO "Ticket" (ticket_id, event_happening_id, seat_id, base_price, is_available)
VALUES (88888888, 2, 77777, 1800.00, TRUE);

QUERY PLAN
Insert on ""Ticket"" (cost\=0.00..0.01 rows\=0 width\=0) (actual time\=0.304..0.304 rows\=0.00 loops\=1)
Buffers: shared hit\=15 dirtied\=2
-> Result (cost\=0.00..0.01 rows\=1 width\=41) (actual time\=0.001..0.001 rows\=1.00 loops\=1)
Planning Time: 0.029 ms
Trigger for constraint fk_ticket_seat: time\=0.201 calls\=1
Trigger for constraint fk_ticket_happening: time\=0.107 calls\=1
Execution Time: 0.628 ms
  • UPDATE
EXPLAIN ANALYZE
UPDATE "Ticket"
SET is_available = FALSE
WHERE ticket_id = 88888888;

QUERY PLAN
Update on ""Ticket"" (cost\=0.44..8.46 rows\=0 width\=0) (actual time\=0.098..0.098 rows\=0.00 loops\=1)
Buffers: shared hit\=14
-> Index Scan using ""Ticket_pkey"" on ""Ticket"" (cost\=0.44..8.46 rows\=1 width\=7) (actual time\=0.026..0.028 rows\=1.00 loops\=1)
Index Cond: (ticket_id \= 88888888)
Index Searches: 1
Buffers: shared hit\=4
Planning Time: 0.125 ms
Execution Time: 0.155 ms
Note: See TracWiki for help on using the wiki.