JavaRush /Java Blog /Random-TL /Mga cool na pag-optimize ng SQL na hindi nakadepende sa m...

Mga cool na pag-optimize ng SQL na hindi nakadepende sa modelo ng gastos. Bahagi 2

Nai-publish sa grupo
Mga cool na pag-optimize ng SQL na hindi nakadepende sa modelo ng gastos. Bahagi 1 Mga cool na SQL optimization na hindi nakadepende sa modelo ng gastos.  Bahagi 2 - 1

4. Pag-aalis ng mga "walang kahulugan" na panaguri

Ang parehong walang kahulugan ay mga panaguri na (halos) laging totoo. Tulad ng maaari mong isipin, kung ikaw ay nagtatanong:
SELECT * FROM actor WHERE 1 = 1;
...kung gayon ang mga database ay hindi aktwal na isasagawa ito, ngunit babalewalain lamang ito. Minsan akong sumagot ng isang tanong tungkol dito sa Stack Overflow at iyon ang dahilan kung bakit napagpasyahan kong isulat ang artikulong ito. Iiwan ko ang pagsubok na ito bilang isang ehersisyo sa mambabasa, ngunit ano ang mangyayari kung ang panaguri ay medyo "walang kahulugan"? Halimbawa:
SELECT * FROM film WHERE release_year = release_year;
Kailangan mo ba talagang ihambing ang halaga sa sarili nito para sa bawat hilera? Hindi, walang halaga kung saan magiging FALSE ang predicate na ito , tama ba? Ngunit kailangan pa rin nating suriin ito. Bagama't ang panaguri ay hindi maaaring katumbas ng FALSE , maaaring ito ay katumbas ng NULL sa lahat ng dako , muli dahil sa tatlong halagang lohika. Ang RELEASE_YEAR column ay nullable, at kung ang alinman sa mga row ay may RELEASE_YEAR IS NULL , pagkatapos ay NULL = NULL ang mga resulta sa NULL at ang row ay dapat na alisin. Kaya ang kahilingan ay nagiging sumusunod:
SELECT * FROM film WHERE release_year IS NOT NULL;
Aling mga database ang gumagawa nito?

DB2

Oo!
Explain Plan
-------------------------------------------------
ID | Operation    |                   Rows | Cost
 1 | RETURN       |                        |   49
 2 |  TBSCAN FILM | 1000 of 1000 (100.00%) |   49
Predicate Information
 2 - SARG Q1.RELEASE_YEAR IS NOT NULL

MySQL

Ito ay isang kahihiyan, ngunit ang MySQL, muli, ay hindi naglalagay ng mga predicate sa mga plano sa pagpapatupad, kaya ang pag-uunawa kung ang MySQL ay nagpapatupad ng partikular na pag-optimize na ito ay medyo nakakalito. Maaari kang magsagawa ng pagtatasa ng pagganap at tingnan kung may ginagawang malalaking paghahambing. O maaari kang magdagdag ng index:
CREATE INDEX i_release_year ON film (release_year);
At makakuha ng mga plano para sa mga sumusunod na kahilingan bilang kapalit:
SELECT * FROM film WHERE release_year = release_year;
SELECT * FROM film WHERE release_year IS NOT NULL;
Kung gumagana ang pag-optimize, ang mga plano ng parehong mga query ay dapat na halos pareho. Ngunit sa kasong ito hindi ito ang kaso:
ID  TABLE  POSSIBLE_KEYS   ROWS  FILTERED  EXTRA
------------------------------------------------------
1   film             1000  10.00           Using where

ID  TABLE  POSSIBLE_KEYS   ROWS  FILTERED  EXTRA
------------------------------------------------------
1   film   i_release_year  1000  100.00    Using where
Gaya ng nakikita mo, malaki ang pagkakaiba ng aming dalawang query sa mga halaga ng POSSIBLE_KEYS at FILTERED na mga column . Kaya gusto kong makipagsapalaran ng isang makatwirang hula na hindi ito ino-optimize ng MySQL.

Oracle

Oo!
----------------------------------------------------
| Id  | Operation         | Name | Starts | E-Rows |
----------------------------------------------------
|   0 | SELECT STATEMENT  |      |      1 |        |
|*  1 |  TABLE ACCESS FULL| FILM |      1 |   1000 |
----------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("RELEASE_YEAR" IS NOT NULL)

PostgreSQL

Sa kasamaang palad hindi!
QUERY PLAN
--------------------------------------------------------------
Seq Scan on film  (cost=0.00..67.50 rows=5 width=386)
  Filter: ((release_year)::integer = (release_year)::integer)
Iba-iba ang mga plano at gastos. Lalo na, tingnan ang pagsusuri ng cardinality, na talagang hindi maganda, habang ang predicate na ito:
SELECT * FROM film WHERE release_year IS NOT NULL;
nagbibigay ng mas mahusay na mga resulta:
QUERY PLAN
---------------------------------------------------------
Seq Scan on film  (cost=0.00..65.00 rows=1000 width=386)
  Filter: (release_year IS NOT NULL)
Bummer!

SQL Server

Kakatwa, hindi rin ito ginagawa ng SQL Server:
|--Table Scan(OBJECT:([film]), WHERE:([release_year]=[release_year]))
Gayunpaman, batay sa hitsura ng plano, ang pagtatasa ng cardinality ay tama, gayundin ang gastos. Ngunit sa aking karanasan sa SQL Server, sasabihin ko na sa kasong ito, walang nangyayaring pag-optimize, dahil ipapakita ng SQL Server ang aktwal na naisakatuparan na predicate sa plano (upang makita kung bakit, tingnan ang mga halimbawa ng pagpilit ng CHECK sa ibaba). Paano naman ang mga "walang kahulugan" na predicates sa NOT NULL columns? Ang conversion sa itaas ay kailangan lang dahil RELEASE_YEAR ay maaaring hindi matukoy. Ano ang mangyayari kung patakbuhin mo ang parehong walang kabuluhang query sa, halimbawa, sa FILM_ID column ?
SELECT * FROM film WHERE film_id = film_id
Ngayon ba ito ay tumutugma sa walang panaguri sa lahat? O hindi bababa sa kung paano ito dapat. Ngunit ito ba?

DB2

Oo!
Explain Plan
-------------------------------------------------
ID | Operation    |                   Rows | Cost
 1 | RETURN       |                        |   49
 2 |  TBSCAN FILM | 1000 of 1000 (100.00%) |   49
Walang mga predicate ang inilapat at pinipili namin ang lahat ng mga pelikula.

MySQL

Oo! (Muli, edukadong hula)
ID  TABLE  POSSIBLE_KEYS   ROWS  FILTERED  EXTRA
------------------------------------------------------
1   film                   1000  100.00
Pansinin na ang EXTRA column ay walang laman na ngayon, na parang wala tayong WHERE clause!

Oracle

Oo!
----------------------------------------------------
| Id  | Operation         | Name | Starts | E-Rows |
----------------------------------------------------
|   0 | SELECT STATEMENT  |      |      1 |        |
|   1 |  TABLE ACCESS FULL| FILM |      1 |   1000 |
----------------------------------------------------
Muli, walang nalalapat na panaguri.

PostgreSQL

Wow, hindi na naman!
QUERY PLAN
------------------------------------------------------
Seq Scan on film  (cost=0.00..67.50 rows=5 width=386)
  Filter: (film_id = film_id)
Ang filter ay inilapat at ang cardinality score ay 5 pa rin. Bummer!

SQL Server

At eto ulit no!
|--Table Scan(OBJECT:([film]), WHERE:([film_id]=[film_id]))

Buod

Tila isang simpleng pag-optimize, ngunit hindi ito ginagamit sa lahat ng DBMS; sa partikular, kakaiba, hindi ito ginagamit sa SQL Server!
Database Walang kahulugan ngunit kinakailangang mga panaguri (NULL semantics) Walang kahulugan at hindi kinakailangang mga predicate (non-NULL semantics)
DB2 LUW 10.5 Oo Oo
MySQL 8.0.2 Hindi Oo
Oracle 12.2.0.1 Oo Oo
PostgreSQL 9.6 Hindi Hindi
SQL Server 2014 Hindi Hindi

5. Projection sa EXISTS subquery

Kapansin-pansin, palagi akong tinatanong tungkol sa mga ito sa aking master class, kung saan ipinagtatanggol ko ang punto ng view na ang SELECT * ay karaniwang hindi nagdudulot ng anumang kabutihan. Ang tanong ay: posible bang gamitin ang SELECT * sa isang EXISTS subquery ? Halimbawa, kung kailangan nating maghanap ng mga aktor na gumanap sa mga pelikula...
SELECT first_name, last_name
FROM actor a
WHERE EXISTS (
  SELECT * -- Is this OK?
  FROM film_actor fa
  WHERE a.actor_id = fa.actor_id
)
At ang sagot ay oo. Pwede. Ang asterisk ay hindi nakakaapekto sa kahilingan. Paano ka nakakasigurado dito? Isaalang-alang ang sumusunod na query:
-- DB2
SELECT 1 / 0 FROM sysibm.dual
-- Oracle
SELECT 1 / 0 FROM dual
-- PostgreSQL, SQL Server
SELECT 1 / 0
-- MySQL
SELECT pow(-1, 0.5);
Ang lahat ng mga database na ito ay nag-uulat ng isang dibisyon sa pamamagitan ng zero error. Tandaan ang isang kawili-wiling katotohanan: sa MySQL, kapag hinati namin sa zero, makakakuha kami ng NULL sa halip na isang error, kaya kailangan naming gumawa ng isa pang ilegal na aksyon. Ngayon, ano ang mangyayari kung isagawa natin, sa halip na nasa itaas, ang mga sumusunod na query?
-- DB2
SELECT CASE WHEN EXISTS (
  SELECT 1 / 0 FROM sysibm.dual
) THEN 1 ELSE 0 END
FROM sysibm.dual
-- Oracle
SELECT CASE WHEN EXISTS (
  SELECT 1 / 0 FROM dual
) THEN 1 ELSE 0 END
FROM dual
-- PostgreSQL
SELECT EXISTS (SELECT 1 / 0)
-- SQL Server
SELECT CASE WHEN EXISTS (
  SELECT 1 / 0
) THEN 1 ELSE 0 END
-- MySQL
SELECT EXISTS (SELECT pow(-1, 0.5));
Ngayon wala sa mga database ang nagbabalik ng error. Lahat sila ay nagbabalik ng TAMA o 1 . Nangangahulugan ito na wala sa aming mga database ang aktwal na sinusuri ang projection (iyon ay, ang SELECT clause ) ng EXISTS subquery . Halimbawa, ipinapakita ng SQL Server ang sumusunod na plano:
|--Constant Scan(VALUES:((CASE WHEN (1) THEN (1) ELSE (0) END)))
Tulad ng makikita mo, ang CASE expression ay na-convert sa isang pare-pareho at ang subquery ay inalis. Ang iba pang mga database ay nag-iimbak ng subquery sa plano at huwag magbanggit ng anuman tungkol sa projection, kaya tingnan natin muli ang orihinal na plano ng query sa Oracle:
SELECT first_name, last_name
FROM actor a
WHERE EXISTS (
  SELECT *
  FROM film_actor fa
  WHERE a.actor_id = fa.actor_id
)
Ang plano sa query sa itaas ay ganito ang hitsura:
------------------------------------------------------------------
| Id  | Operation             | Name                    | E-Rows |
------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                         |        |
|*  1 |  HASH JOIN SEMI       |                         |    200 |
|   2 |   TABLE ACCESS FULL   | ACTOR                   |    200 |
|   3 |   INDEX FAST FULL SCAN| IDX_FK_FILM_ACTOR_ACTOR |   5462 |
------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("A"."ACTOR_ID"="FA"."ACTOR_ID")
Column Projection Information (identified by operation id):
-----------------------------------------------------------
   1 - (#keys=1) LAST_NAME, FIRST_NAME
   2 - (rowset=256) A.ACTOR_ID, FIRST_NAME, LAST_NAME
   3 - FA.ACTOR_ID
Inoobserbahan namin ang impormasyon tungkol sa projection sa Id=3 . Sa katunayan, hindi namin ina-access ang FILM_ACTOR table dahil hindi namin kailangan. Ang EXISTS predicate ay maaaring isagawa gamit ang isang foreign key index sa isang column na ACTOR_ID - lahat ng kailangan para sa query na ito - kahit na isinulat namin ang SELECT * .

Buod

Sa kabutihang-palad, lahat ng aming mga database ay nag-aalis ng projection mula sa EXISTS subquery :
Database MAY Projection
DB2 LUW 10.5 Oo
MySQL 8.0.2 Oo
Oracle 12.2.0.1 Oo
PostgreSQL 9.6 Oo
SQL Server 2014 Oo
Manatiling nakatutok para sa Bahagi 3 , kung saan tatalakayin natin ang iba pang mga cool na pag-optimize ng SQL.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION