JavaRush /Blog Java /Random-MS /Pengoptimuman SQL hebat yang tidak bergantung pada model ...

Pengoptimuman SQL hebat yang tidak bergantung pada model kos. Bahagian 4

Diterbitkan dalam kumpulan
Pengoptimuman SQL hebat yang tidak bergantung pada model kos. Bahagian 1 Pengoptimuman SQL hebat yang tidak bergantung pada model kos. Bahagian 2 Pengoptimuman SQL hebat yang tidak bergantung pada model kos. Bahagian 3 Pengoptimuman SQL hebat yang tidak bergantung pada model kos.  Bahagian 4 - 1

8. SEMAK sekatan

Oh, ini adalah perkara yang menarik! Pangkalan data Sakila kami mempunyai kekangan SEMAK pada lajur FILM.RATING :
CREATE TABLE film (
  ..
  RATING varchar(10) DEFAULT 'G',
  ..
  CONSTRAINT check_special_rating
    CHECK (rating IN ('G','PG','PG-13','R','NC-17')),
  ..
);
Serius, gunakan kekangan CHECK untuk memastikan integriti data. Kos untuk menambahnya adalah sangat rendah - lebih kurang daripada sekatan lain, contohnya, PRIMARY , UNIK atau FOREIGN KEY , kerana mereka tidak memerlukan indeks untuk berfungsi, jadi anda boleh mendapatkannya secara praktikal "secara percuma". Tetapi ada nuansa menarik yang berkaitan dengan pengoptimuman! Pertimbangkan pertanyaan berikut:

Predikat yang mustahil

Kami telah pun menemui mustahil predikat , malah NOT NULL kekangan (yang sebenarnya merupakan jenis khas kekangan CHECK ), tetapi yang ini lebih keren:
SELECT *
FROM film
WHERE rating = 'N/A';
Tiada filem sedemikian, dan tidak boleh ada, kerana kekangan CHECK menghalang penyisipannya (atau kemas kini). Sekali lagi, ini sepatutnya diterjemahkan kepada arahan untuk tidak melakukan apa-apa. Bagaimana dengan permintaan ini?
CREATE INDEX idx_film_rating ON film (rating);

SELECT count(*)
FROM film
WHERE rating NOT IN ('G','PG','PG-13','R');
Terima kasih kepada indeks di atas, mungkin cukup dengan hanya melakukan imbasan pantas indeks dan mengira semua filem dengan rating = 'NC-17' , memandangkan itu sahaja rating yang tinggal. Jadi pertanyaan harus ditulis semula seperti ini:
SELECT count(*)
FROM film
WHERE rating = 'NC-17';
Ini sepatutnya berlaku tanpa mengira indeks, kerana membandingkan lajur dengan satu nilai adalah lebih pantas daripada membandingkan dengan 4. Jadi, pangkalan data apa yang boleh melakukan ini?

DB2

Predikat mustahil (rating = 'N/A') Sejuk!
Explain Plan
-----------------------------------
ID | Operation      |   Rows | Cost
 1 | RETURN         |        |    0
 2 |  TBSCAN GENROW | 0 of 0 |    0

Predicate Information
 2 - RESID (1 = 0)
Predikat songsang (rating = 'NC-17') Tidak...
Explain Plan
------------------------------------------------------------
ID | Operation                |                  Rows | Cost
 1 | RETURN                   |                       |   34
 2 |  GRPBY (COMPLETE)        |    1 of 210 (   .48%) |   34
 3 |   IXSCAN IDX_FILM_RATING | 210 of 1000 ( 21.00%) |   34

Predicate Information
 3 - SARG  NOT(Q1.RATING IN ('G', 'PG', 'PG-13', 'R'))
Walaupun langkah ID=3 menggunakan indeks, dan walaupun kardinaliti adalah betul, imbasan penuh berlaku kerana pelan itu tidak mempunyai predikat julat, hanya predikat "SARG". Lihat ulasan Marcus Wynd untuk butiran . Anda juga boleh menunjukkan ini dengan menyongsangkan predikat secara manual dan mendapatkan:
Explain Plan
------------------------------------------------------------
ID | Operation                |                  Rows | Cost
 1 | RETURN                   |                       |    7
 2 |  GRPBY (COMPLETE)        |    1 of 210 (   .48%) |    7
 3 |   IXSCAN IDX_FILM_RATING | 210 of 1000 ( 21.00%) |    7

Predicate Information
 3 - START (Q1.RATING = 'NC-17')
      STOP (Q1.RATING = 'NC-17')
Sekarang kita mempunyai predikat julat yang dikehendaki.

MySQL

MySQL menyokong sintaks kekangan CHECK , tetapi atas sebab tertentu tidak menguatkuasakannya. Cuba ini:
CREATE TABLE x (a INT CHECK (a != 0));
INSERT INTO x VALUES (0);
SELECT * FROM x;
dan anda akan mendapat:
A
-
0
Mata sifar untuk MySQL (benar-benar, mengapa tidak menyokong kekangan CHECK sahaja ?)

Oracle

Predikat mustahil (penilaian = 'N/A')
--------------------------------------------------------------
| Id  | Operation          | Name | Starts | E-Rows | A-Rows |
--------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |      1 |        |      0 |
|*  1 |  FILTER            |      |      1 |        |      0 |
|*  2 |   TABLE ACCESS FULL| FILM |      0 |     89 |      0 |
--------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(NULL IS NOT NULL)
   2 - filter("RATING"='N/A')
Sekali lagi, penapis yang sangat pelik NULL IS NOT NULL , memotong FULL TABLE SCAN , yang boleh dengan mudah dialih keluar daripada pelan sama sekali. Tetapi sekurang-kurangnya ia berfungsi! Predikat songsang (rating = 'NC-17') Oops:
----------------------------------------------------------------------------
| Id  | Operation             | Name            | Starts | E-Rows | A-Rows |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                 |      1 |        |      1 |
|   1 |  SORT AGGREGATE       |                 |      1 |      1 |      1 |
|*  2 |   INDEX FAST FULL SCAN| IDX_FILM_RATING |      1 |    415 |    210 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter((RATING'PG-13' AND RATING'R' AND RATING'PG' AND RATING'G'))
Predikat tidak boleh disongsangkan, penilaian kardinaliti sangat pincang, di samping itu kita mendapat INDEX FAST FULL SCAN bukannya INDEX RANGE SCAN , dan predikat penapis bukannya predikat akses . Tetapi inilah yang harus kita dapatkan, sebagai contoh, dengan menyongsangkan predikat secara manual:
------------------------------------------------------------------------
| Id  | Operation         | Name            | Starts | E-Rows | A-Rows |
------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                 |      1 |        |      1 |
|   1 |  SORT AGGREGATE   |                 |      1 |      1 |      1 |
|*  2 |   INDEX RANGE SCAN| IDX_FILM_RATING |      1 |    210 |    210 |
------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("RATING"='NC-17')
Bummer!

PostgreSQL

Ambil perhatian bahawa versi PostgreSQL pangkalan data Sakila menggunakan jenis ENUM dan bukannya kekangan CHECK pada lajur RATING . Saya menduplikasi jadual menggunakan kekangan CHECK sebaliknya . Predikat mustahil (penilaian = 'N/A') Tidak berfungsi:
QUERY PLAN
------------------------------------------------------
Seq Scan on film2  (cost=0.00..67.50 rows=1 width=385)
  Filter: ((rating)::text = 'N/A'::text)
Predikat terbalik (rating = 'NC-17') tidak berfungsi sama ada:
QUERY PLAN
------------------------------------------------------------------
Aggregate  (cost=70.53..70.54 rows=1 width=8)
  ->  Seq Scan on film2  (cost=0.00..70.00 rows=210 width=0)
        Filter: ((rating)::text  ALL ('{G,PG,PG-13,R}'::text[]))
maaf sangat! Nota: Seperti yang ditunjukkan oleh David Rowley kepada kami dalam ulasan , ciri ini boleh didayakan dengan menetapkan parameter:
SET constraint_exclusion TO on;
SQL Server
Невозможный предикат (rating = 'N/A')
Да!
|--Constant Scan
Predikat songsang (rating = 'NC-17') Ya juga!
|--Compute Scalar
     |--Stream Aggregate
          |--Index Seek(OBJECT:([idx_film_rating]), SEEK:([rating]='NC-17'))

Ringkasan

Pangkalan data Predikat yang mustahil Predikat songsang
DB2 LUW 10.5 ya Tidak
MySQL 8.0.2 Tidak disokong Tidak disokong
Oracle 12.2.0.1 ya Tidak
PostgreSQL 9.6 Tidak Tidak

9. Sambungan refleksif yang tidak perlu.

Apabila pertanyaan anda menjadi lebih kompleks, anda mungkin akhirnya perlu melakukan gabungan reflektif pada jadual berdasarkan kunci utamanya. Percayalah, ini adalah amalan yang sangat biasa apabila membina pandangan yang kompleks dan menghubungkannya antara satu sama lain, jadi memastikan pangkalan data memberi perhatian kepada perkara ini adalah bahagian penting dalam mengoptimumkan kod SQL yang kompleks. Saya tidak akan menunjukkan contoh yang kompleks, yang mudah akan mencukupi, contohnya:
SELECT a1.first_name, a1.last_name
FROM actor a1
JOIN actor a2 ON a1.actor_id = a2.actor_id;
Ini boleh dilihat sebagai satu kes khas penyingkiran JOIN , kerana kita sebenarnya tidak memerlukan gabungan ke A2 , kita boleh melakukan semua yang kita perlukan hanya dengan jadual A1 . Seterusnya, penyingkiran INNER JOIN hanya berfungsi dengan baik jika terdapat KUNCI ASING , yang kami tiada di sini. Tetapi terima kasih kepada kunci utama oleh ACTOR_ID , kita boleh membuktikan bahawa sebenarnya A1 = A2 . Dari satu segi, ini sekali lagi penutupan transitif . Anda boleh pergi lebih jauh dan menggunakan lajur dari kedua-dua jadual A1 dan A2 :
SELECT a1.first_name, a2.last_name
FROM actor a1
JOIN actor a2 ON a1.actor_id = a2.actor_id;
Dalam kes klasik penyingkiran JOIN , ia tidak lagi boleh dihapuskan kerana kedua-dua jadual diunjurkan. Tetapi kerana kita telah membuktikan bahawa A1 = A2 , maka ia boleh ditukar ganti, jadi kita boleh mengharapkan pertanyaan ditukar kepada:
SELECT first_name, last_name
FROM actor;
Apakah DBMS boleh melakukan ini?

DB2

Unjuran jadual A1 sahaja Ya:
Explain Plan
------------------------------------------------
ID | Operation     |                 Rows | Cost
 1 | RETURN        |                      |   20
 2 |  TBSCAN ACTOR | 200 of 200 (100.00%) |   20
Unjuran jadual A1 dan A2 ... juga ya:
Explain Plan
------------------------------------------------
ID | Operation     |                 Rows | Cost
 1 | RETURN        |                      |   20
 2 |  TBSCAN ACTOR | 200 of 200 (100.00%) |   20

MySQL

Unjuran jadual A1 sahaja No.
ID  TABLE  REF          EXTRA
-----------------------------------
1   a1
1   a2     a1.actor_id  Using index
Unjuran jadual A1 dan A2 ... juga tidak
ID  TABLE  REF          EXTRA
-----------------------------------
1   a1
1   a2     a1.actor_id
kekecewaan sepenuhnya...

Oracle

Unjuran jadual A1 sahaja Ya
--------------------------------------------
| Id  | Operation         | Name  | E-Rows |
--------------------------------------------
|   0 | SELECT STATEMENT  |       |        |
|   1 |  TABLE ACCESS FULL| ACTOR |    200 |
--------------------------------------------
Unjuran jadual A1 dan A2 Ya sekali lagi
--------------------------------------------
| Id  | Operation         | Name  | E-Rows |
--------------------------------------------
|   0 | SELECT STATEMENT  |       |        |
|   1 |  TABLE ACCESS FULL| ACTOR |    200 |
--------------------------------------------

PostgreSQL

Unjuran jadual A1 sahaja No:
QUERY PLAN
--------------------------------------------------------------------
Hash Join  (cost=6.50..13.25 rows=200 width=13)
  Hash Cond: (a1.actor_id = a2.actor_id)
  ->  Seq Scan on actor a1  (cost=0.00..4.00 rows=200 width=17)
  ->  Hash  (cost=4.00..4.00 rows=200 width=4)
        ->  Seq Scan on actor a2  (cost=0.00..4.00 rows=200 width=4)
Unjuran jadual A1 dan A2 Dan sekali lagi tidak:
QUERY PLAN
---------------------------------------------------------------------
Hash Join  (cost=6.50..13.25 rows=200 width=13)
  Hash Cond: (a1.actor_id = a2.actor_id)
  ->  Seq Scan on actor a1  (cost=0.00..4.00 rows=200 width=10)
  ->  Hash  (cost=4.00..4.00 rows=200 width=11)
        ->  Seq Scan on actor a2  (cost=0.00..4.00 rows=200 width=11)

Pelayan SQL

Unjuran jadual A1 sahaja Anehnya, tidak! (Tetapi perlu diingat bahawa saya menggunakan SQL Server 2014, versi yang lebih baharu mungkin membetulkannya. Saya pasti boleh menggunakan peningkatan!)
|--Merge Join(Inner Join, MERGE:([a2].[actor_id])=([a1].[actor_id]))
     |--Index Scan(OBJECT:([a2]))
     |--Sort(ORDER BY:([a1].[actor_id] ASC))
          |--Table Scan(OBJECT:([a1]))
Unjuran jadual A1 dan A2 Tidak lagi, malah rancangannya berubah menjadi lebih teruk:
|--Hash Match(Inner Join, HASH:([a1].[actor_id])=([a2].[actor_id]))
     |--Table Scan(OBJECT:([sakila].[dbo].[actor] AS [a1]))
     |--Table Scan(OBJECT:([sakila].[dbo].[actor] AS [a2]))

Ringkasan

Terus terang saya katakan, saya menjangkakan bahawa pengoptimuman ini akan dilakukan pada semua pangkalan data, tetapi saya sangat tersilap, sedihnya. Bersama-sama dengan menghapuskan JOIN , ini adalah salah satu pengoptimuman yang paling penting, membolehkan anda membina pertanyaan SQL yang besar daripada bahagian yang boleh digunakan semula seperti paparan dan fungsi jadual. Malangnya, ia tidak disokong dalam 3 daripada 5 pangkalan data yang paling biasa.
Pangkalan data Mengalih keluar cantuman reflektif, unjuran jadual tunggal Penghapusan sambungan refleksif, unjuran penuh
DB2 LUW 10.5 ya ya
MySQL 8.0.2 Tidak Tidak
Oracle 12.2.0.1 ya ya
PostgreSQL 9.6 Tidak Tidak
SQL Server 2014 Tidak Tidak
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION