JavaRush /จาวาบล็อก /Random-TH /การเพิ่มประสิทธิภาพ SQL ที่ยอดเยี่ยมที่ไม่ขึ้นอยู่กับโมเด...

การเพิ่มประสิทธิภาพ SQL ที่ยอดเยี่ยมที่ไม่ขึ้นอยู่กับโมเดลต้นทุน ตอนที่ 4

เผยแพร่ในกลุ่ม
การเพิ่มประสิทธิภาพ SQL ที่ยอดเยี่ยมที่ไม่ขึ้นอยู่กับโมเดลต้นทุน ส่วนที่ 1 การเพิ่มประสิทธิภาพ SQL ที่ยอดเยี่ยมที่ไม่ขึ้นอยู่กับโมเดลต้นทุน ส่วนที่ 2 การเพิ่มประสิทธิภาพ SQL ที่ยอดเยี่ยมที่ไม่ขึ้นอยู่กับโมเดลต้นทุน ส่วนที่ 3 การเพิ่มประสิทธิภาพ SQL ที่ยอดเยี่ยมที่ไม่ขึ้นอยู่กับโมเดลต้นทุน  ตอนที่ 4 - 1

8. ตรวจสอบข้อจำกัด

โอ้ นี่มันสิ่งดีๆ! ฐานข้อมูลSakila ของเรา มี ข้อจำกัดใน การตรวจสอบใน คอลัมน์ FILM.RATING :
CREATE TABLE film (
  ..
  RATING varchar(10) DEFAULT 'G',
  ..
  CONSTRAINT check_special_rating
    CHECK (rating IN ('G','PG','PG-13','R','NC-17')),
  ..
);
อย่างจริงจัง ใช้ ข้อจำกัด CHECKเพื่อรับรองความสมบูรณ์ของข้อมูล ค่าใช้จ่ายในการเพิ่มนั้นต่ำมาก - น้อยกว่าข้อจำกัดอื่น ๆ เช่นPRIMARY , UNIQUEหรือFOREIGN KEYเนื่องจากไม่ต้องการดัชนีในการทำงาน ดังนั้นคุณจึงใช้งานได้จริง "ฟรี" แต่มีความแตกต่างที่น่าสนใจเกี่ยวกับการเพิ่มประสิทธิภาพ! พิจารณาคำถามต่อไปนี้:

ภาคแสดงที่เป็นไปไม่ได้

เราได้พบกับเพรดิเคตที่เป็นไปไม่ได้แล้ว แม้จะ ไม่ใช่ข้อจำกัด NULL (ซึ่งจริงๆ แล้วเป็นชนิดพิเศษของCHECK constraint ) แต่อันนี้เจ๋งกว่า:
SELECT *
FROM film
WHERE rating = 'N/A';
ไม่มีภาพยนตร์ดังกล่าว และไม่สามารถมีได้ เนื่องจาก ข้อจำกัด CHECKป้องกันการแทรก (หรืออัปเดต) อีกครั้ง สิ่งนี้ควรแปลเป็นคำสั่งที่จะไม่ทำอะไรเลย แล้วคำขอนี้ล่ะ?
CREATE INDEX idx_film_rating ON film (rating);

SELECT count(*)
FROM film
WHERE rating NOT IN ('G','PG','PG-13','R');
ต้องขอบคุณดัชนีด้านบน เพียงแค่สแกนดัชนีอย่างรวดเร็วและนับภาพยนตร์ทั้งหมดที่มีเรตติ้ง = 'NC-17' ก็ เพียงพอ แล้ว เนื่องจากเหลือเพียงเรตติ้งเดียวเท่านั้น ดังนั้นควรเขียนแบบสอบถามใหม่ดังนี้:
SELECT count(*)
FROM film
WHERE rating = 'NC-17';
ซึ่งควรเป็นกรณีนี้โดยไม่คำนึงถึงดัชนี เนื่องจากการเปรียบเทียบคอลัมน์กับค่าเดียวจะเร็วกว่าการเปรียบเทียบกับ 4 ดังนั้นฐานข้อมูลใดที่สามารถทำได้?

ดีบี2

ภาคแสดงที่เป็นไปไม่ได้ (คะแนน = 'N/A') เจ๋ง!
Explain Plan
-----------------------------------
ID | Operation      |   Rows | Cost
 1 | RETURN         |        |    0
 2 |  TBSCAN GENROW | 0 of 0 |    0

Predicate Information
 2 - RESID (1 = 0)
ภาคแสดงย้อนกลับ (เรตติ้ง = 'NC-17') ไม่...
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'))
แม้ว่าขั้นตอน ID=3 จะใช้ดัชนี และแม้ว่าจำนวนสมาชิกถูกต้อง การสแกนแบบเต็มเกิดขึ้นเนื่องจากแผนไม่มีเพรดิเคตช่วง มีเพียงเพรดิเคต "SARG" เท่านั้น ดูบทวิจารณ์ของ Marcus Wynand เพื่อดูราย ละเอียด คุณยังสามารถสาธิตสิ่งนี้ได้โดยการกลับภาคแสดงด้วยตนเองและรับ:
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')
ตอนนี้เรามีภาคแสดงพิสัยที่ต้องการแล้ว

MySQL

MySQL รองรับ ไวยากรณ์ข้อจำกัด CHECKแต่ด้วยเหตุผลบางอย่างไม่ได้บังคับใช้ ลองสิ่งนี้:
CREATE TABLE x (a INT CHECK (a != 0));
INSERT INTO x VALUES (0);
SELECT * FROM x;
และคุณจะได้รับ:
A
-
0
ศูนย์คะแนนสำหรับ MySQL (จริง ๆ แล้วทำไมไม่รองรับ ข้อจำกัด CHECK เท่านั้น )

ออราเคิล

ภาคแสดงที่เป็นไปไม่ได้ (คะแนน = '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')
ขอย้ำอีกครั้งว่าตัวกรองที่แปลกมากNULL IS NOT NULLตัดFULL TABLE SCANซึ่งสามารถลบออกจากแผนได้อย่างง่ายดายเช่นกัน แต่อย่างน้อยมันก็ได้ผล! ภาคแสดงย้อนกลับ (คะแนน = 'NC-17') อ๊ะ:
----------------------------------------------------------------------------
| 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'))
เพรดิเคตไม่สามารถกลับด้านได้ การประเมินจำนวนนับนั้นง่อยมาก นอกจากนี้เรายังได้รับINDEX FAST FULL SCANแทนINDEX RANGE SCANและ เพรดิเคต ตัวกรองแทน เพรดิเคต การเข้าถึง แต่นี่คือสิ่งที่เราควรได้รับ ตัวอย่างเช่น โดยการกลับภาคแสดงด้วยตนเอง:
------------------------------------------------------------------------
| 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')
แย่จัง!

PostgreSQL

โปรดทราบว่าฐานข้อมูลSakila เวอร์ชัน PostgreSQL ใช้ ประเภท ENUM แทนข้อจำกัดCHECKในคอลัมน์RATING ฉัน ทำซ้ำตารางโดยใช้ข้อจำกัด CHECKแทน ภาคแสดงที่เป็นไปไม่ได้ (คะแนน = 'N/A') ใช้ไม่ได้:
QUERY PLAN
------------------------------------------------------
Seq Scan on film2  (cost=0.00..67.50 rows=1 width=385)
  Filter: ((rating)::text = 'N/A'::text)
ภาคแสดงย้อนกลับ (เรตติ้ง = 'NC-17') ใช้ไม่ได้เช่นกัน:
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[]))
เสียใจมาก! หมายเหตุ:ตามที่David Rowley กรุณาชี้ให้เราทราบในความคิดเห็นคุณลักษณะนี้สามารถเปิดใช้งานได้โดยการตั้งค่าพารามิเตอร์:
SET constraint_exclusion TO on;
SQL Server
Невозможный предикат (rating = 'N/A')
Да!
|--Constant Scan
ภาคแสดงย้อนกลับ (คะแนน = 'NC-17') ใช่ด้วย!
|--Compute Scalar
     |--Stream Aggregate
          |--Index Seek(OBJECT:([idx_film_rating]), SEEK:([rating]='NC-17'))

สรุป

ฐานข้อมูล ภาคแสดงที่เป็นไปไม่ได้ ภาคแสดงย้อนกลับ
DB2 LUW 10.5 ใช่ เลขที่
มายเอสแอล 8.0.2 ไม่รองรับ ไม่รองรับ
ออราเคิล 12.2.0.1 ใช่ เลขที่
PostgreSQL 9.6 เลขที่ เลขที่

9. การเชื่อมต่อแบบสะท้อนกลับที่ไม่จำเป็น

เมื่อแบบสอบถามของคุณซับซ้อนมากขึ้น คุณอาจจำเป็นต้องดำเนินการรวมแบบสะท้อนกลับบนโต๊ะโดยยึดตามคีย์หลัก เชื่อฉันเถอะ นี่เป็นแนวทางปฏิบัติทั่วไปในการสร้างมุมมองที่ซับซ้อนและเชื่อมต่อเข้าด้วยกัน ดังนั้นการตรวจสอบให้แน่ใจว่าฐานข้อมูลให้ความสำคัญกับสิ่งนี้เป็นส่วนสำคัญในการปรับโค้ด SQL ที่ซับซ้อนให้เหมาะสม ฉันจะไม่แสดงตัวอย่างที่ซับซ้อน แค่ตัวอย่างง่ายๆ ก็เพียงพอแล้ว เช่น
SELECT a1.first_name, a1.last_name
FROM actor a1
JOIN actor a2 ON a1.actor_id = a2.actor_id;
นี่ถือเป็นกรณีพิเศษ ของการ กำจัดJOINเนื่องจากเราไม่ต้องการการรวมA2 จริงๆ เราจึงสามารถทำทุกอย่างที่เราต้องการด้วยเพียงตารางA1 ถัดไปการกำจัด INNER JOIN จะทำงานได้อย่างถูกต้องหากมี FOREIGN KEYซึ่งเราไม่มีที่นี่ แต่ต้องขอบคุณคีย์หลักโดยACTOR_IDเราสามารถพิสูจน์ได้ว่าในความเป็นจริงA1 = A2 ในแง่หนึ่ง นี่คือการปิดสกรรมกริยาอีกครั้ง คุณสามารถไปได้ไกลกว่านี้และใช้คอลัมน์จากทั้งตารางA1และA2 :
SELECT a1.first_name, a2.last_name
FROM actor a1
JOIN actor a2 ON a1.actor_id = a2.actor_id;
ในกรณีคลาสสิกของJOIN elimination จะไม่สามารถกำจัดได้อีกต่อไป เนื่องจากทั้งสองตารางถูกฉายภาพ แต่เนื่องจากเราได้พิสูจน์แล้วว่าA1 = A2จึงสามารถใช้แทนกันได้ ดังนั้นเราจึงสามารถแปลงแบบสอบถามเป็น:
SELECT first_name, last_name
FROM actor;
DBMS ใดที่สามารถทำได้?

ดีบี2

การฉายภาพเฉพาะตาราง A1 เท่านั้น ใช่:
Explain Plan
------------------------------------------------
ID | Operation     |                 Rows | Cost
 1 | RETURN        |                      |   20
 2 |  TBSCAN ACTOR | 200 of 200 (100.00%) |   20
การฉายภาพตาราง A1 และ A2 ... ใช่ด้วย:
Explain Plan
------------------------------------------------
ID | Operation     |                 Rows | Cost
 1 | RETURN        |                      |   20
 2 |  TBSCAN ACTOR | 200 of 200 (100.00%) |   20

MySQL

การฉายภาพเฉพาะตาราง A1 เท่านั้น
ID  TABLE  REF          EXTRA
-----------------------------------
1   a1
1   a2     a1.actor_id  Using index
การฉายภาพตาราง A1 และ A2 ... หมายเลขเช่นกัน
ID  TABLE  REF          EXTRA
-----------------------------------
1   a1
1   a2     a1.actor_id
ผิดหวังเต็มๆ...

ออราเคิล

การฉายภาพของตาราง A1 เท่านั้น ใช่
--------------------------------------------
| Id  | Operation         | Name  | E-Rows |
--------------------------------------------
|   0 | SELECT STATEMENT  |       |        |
|   1 |  TABLE ACCESS FULL| ACTOR |    200 |
--------------------------------------------
การฉายภาพตาราง A1 และ A2 ใช่อีกครั้ง
--------------------------------------------
| Id  | Operation         | Name  | E-Rows |
--------------------------------------------
|   0 | SELECT STATEMENT  |       |        |
|   1 |  TABLE ACCESS FULL| ACTOR |    200 |
--------------------------------------------

PostgreSQL

การฉายภาพเฉพาะตาราง A1 เท่านั้น หมายเลข:
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)
การฉายภาพตาราง A1 และ A2 และไม่มีอีกครั้ง:
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)

เซิร์ฟเวอร์ SQL

การฉายภาพตาราง A1 เท่านั้น ผิดปกติพอไม่มี! (แต่โปรดจำไว้ว่าฉันใช้ SQL Server 2014 เวอร์ชันใหม่กว่าอาจมีการแก้ไขนี้ ฉันสามารถใช้การอัพเกรดได้อย่างแน่นอน!)
|--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]))
การฉายตาราง A1 และ A2 ไม่ได้อีกแล้วและแผนก็เปลี่ยนไปในทางที่แย่ลง:
|--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]))

สรุป

พูดตามตรง ฉันคาดหวังว่าการเพิ่มประสิทธิภาพนี้จะดำเนินการกับฐานข้อมูลทั้งหมด แต่ฉันคิดผิดมากอย่างน่าเศร้า นอกเหนือจากการกำจัด JOIN แล้วนี่ยังเป็นหนึ่งในการปรับปรุงประสิทธิภาพที่สำคัญที่สุด ซึ่งช่วยให้คุณสร้างคำสั่ง SQL ขนาดใหญ่จากส่วนที่นำมาใช้ซ้ำได้ เช่น มุมมองและฟังก์ชันตาราง น่าเสียดายที่ฐานข้อมูลทั่วไป 3 ใน 5 นี้ไม่รองรับ
ฐานข้อมูล การลบการรวมแบบสะท้อนแสง การฉายภาพตารางเดียว การกำจัดการเชื่อมต่อแบบสะท้อน การฉายภาพแบบเต็ม
DB2 LUW 10.5 ใช่ ใช่
มายเอสแอล 8.0.2 เลขที่ เลขที่
ออราเคิล 12.2.0.1 ใช่ ใช่
PostgreSQL 9.6 เลขที่ เลขที่
เซิร์ฟเวอร์ SQL 2014 เลขที่ เลขที่
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION