Truy vấn SQL chậm từ 1 giây xuống còn 11 phút - tại sao?


7

Câu hỏi: Tôi đang chuyển truy vấn bên dưới (liệt kê các bảng theo phụ thuộc khóa ngoài) sang PostGreSql.

WITH Fkeys AS (

    SELECT DISTINCT 
         OnTable       = OnTable.name
        ,AgainstTable  = AgainstTable.name 
    FROM sysforeignkeys fk 

        INNER JOIN sysobjects onTable 
            ON fk.fkeyid = onTable.id 

        INNER JOIN sysobjects againstTable  
            ON fk.rkeyid = againstTable.id 

    WHERE 1=1
        AND AgainstTable.TYPE = 'U'
        AND OnTable.TYPE = 'U'
        -- ignore self joins; they cause an infinite recursion
        AND OnTable.Name <> AgainstTable.Name
    )

,MyData AS (

    SELECT 
         OnTable = o.name 
        ,AgainstTable = FKeys.againstTable 
    FROM sys.objects o 

    LEFT JOIN FKeys
        ON o.name = FKeys.onTable 

    WHERE (1=1) 
        AND o.type = 'U' 
        AND o.name NOT LIKE 'sys%' 
    )

,MyRecursion AS (

    -- base case
    SELECT  
         TableName    = OnTable
        ,Lvl        = 1
    FROM MyData
    WHERE 1=1
        AND AgainstTable IS NULL 

    -- recursive case
    UNION ALL 

    SELECT 
         TableName = OnTable 
        ,Lvl       = r.Lvl + 1 
    FROM MyData d 
        INNER JOIN MyRecursion r 
            ON d.AgainstTable = r.TableName 
)
SELECT 
     Lvl = MAX(Lvl)
    ,TableName
    --,strSql = 'delete from [' + tablename + ']'
FROM 
    MyRecursion
GROUP BY
    TableName

ORDER BY lvl

/*
ORDER BY 

     2 ASC
    ,1 ASC

*/

Sử dụng information_schema, truy vấn trông như thế này:

WITH Fkeys AS 
(
    SELECT DISTINCT 
         KCU1.TABLE_NAME AS OnTable 
        ,KCU2.TABLE_NAME AS AgainstTable 
    FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC 

    LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1 
        ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG  
        AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA 
        AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME 

    LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2 
        ON KCU2.CONSTRAINT_CATALOG =  RC.UNIQUE_CONSTRAINT_CATALOG  
        AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA 
        AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME 
        AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION 

    WHERE (1=1)
    AND KCU1.TABLE_NAME <> KCU2.TABLE_NAME 
)

,MyData AS 
( 
    SELECT 
         TABLE_NAME AS OnTable  
        ,FKeys.againstTable AS AgainstTable
    FROM INFORMATION_SCHEMA.TABLES 

    LEFT JOIN FKeys
        ON TABLE_NAME = FKeys.onTable  

    WHERE (1=1) 
        AND TABLE_TYPE = 'BASE TABLE'
        AND TABLE_NAME NOT IN ('sysdiagrams', 'dtproperties') 
)

,MyRecursion AS 
(
    -- base case
    SELECT  
         OnTable AS TableName 
        ,1 AS Lvl 
    FROM MyData
    WHERE 1=1
    AND AgainstTable IS NULL 

    -- recursive case
    UNION ALL 

    SELECT 
         OnTable AS TableName
        ,r.Lvl + 1 AS Lvl 
    FROM MyData d 

    INNER JOIN MyRecursion r 
        ON d.AgainstTable = r.TableName 
)

SELECT 
     MAX(Lvl) AS Lvl 
    ,TableName
    --,strSql = 'delete from [' + tablename + ']'
FROM 
    MyRecursion
GROUP BY
    TableName

ORDER BY lvl

/*
ORDER BY 

     2 ASC
    ,1 ASC

*/

Câu hỏi của tôi bây giờ là:

Trong SQL Server (được thử nghiệm trên 2008 R2): Tại sao truy vấn nhảy từ 1 giây đến 11 phút khi tôi thay thế

SELECT DISTINCT 
     OnTable       = OnTable.name
    ,AgainstTable  = AgainstTable.name 
FROM sysforeignkeys fk 

    INNER JOIN sysobjects onTable 
        ON fk.fkeyid = onTable.id 

    INNER JOIN sysobjects againstTable  
        ON fk.rkeyid = againstTable.id 

WHERE 1=1
    AND AgainstTable.TYPE = 'U'
    AND OnTable.TYPE = 'U'
    -- ignore self joins; they cause an infinite recursion
    AND OnTable.Name <> AgainstTable.Name

với

SELECT DISTINCT 
     KCU1.TABLE_NAME AS OnTable 
    ,KCU2.TABLE_NAME AS AgainstTable 
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC 

LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1 
    ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG  
    AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA 
    AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME 

LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2 
    ON KCU2.CONSTRAINT_CATALOG =  RC.UNIQUE_CONSTRAINT_CATALOG  
    AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA 
    AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME 
    AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION 

WHERE (1=1)
AND KCU1.TABLE_NAME <> KCU2.TABLE_NAME 

???

Theo như tôi có thể nói, thực sự không có sự khác biệt đáng kể về tốc độ khi chỉ chạy các truy vấn một phần riêng biệt. Ngoài ra, tập kết quả hoàn toàn giống nhau (tôi đã kiểm tra mọi hàng trong Excel), mặc dù thứ tự khác nhau.

Bên dưới phiên bản PostGreSQL đang hoạt động (hoàn thành trong 35 ms trên cùng một nội dung db [75 bảng] ...)
- Không bảo hành gì -

WITH RECURSIVE Fkeys AS 
(
    SELECT DISTINCT 
         KCU1.TABLE_NAME AS OnTable 
        ,KCU2.TABLE_NAME AS AgainstTable 
    FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC 

    LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1 
        ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG  
        AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA 
        AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME 

    LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2 
        ON KCU2.CONSTRAINT_CATALOG =  RC.UNIQUE_CONSTRAINT_CATALOG  
        AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA 
        AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME 
        AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION 
)

,MyData AS 
( 
    SELECT 
         TABLE_NAME AS OnTable  
        ,FKeys.againstTable AS AgainstTable
    FROM INFORMATION_SCHEMA.TABLES 

    LEFT JOIN FKeys
        ON TABLE_NAME = FKeys.onTable  

    WHERE (1=1) 
        AND TABLE_TYPE = 'BASE TABLE'
        AND TABLE_SCHEMA = 'public'
        --AND TABLE_NAME NOT IN ('sysdiagrams', 'dtproperties') 
)


,MyRecursion AS 
(
    -- base case
    SELECT  
         OnTable AS TableName 
        ,1 AS Lvl 
    FROM MyData
    WHERE 1=1
    AND AgainstTable IS NULL 

    -- recursive case
    UNION ALL 

    SELECT 
         OnTable AS TableName
        ,r.Lvl + 1 AS Lvl 
    FROM MyData d 

    INNER JOIN MyRecursion r 
        ON d.AgainstTable = r.TableName 
)

SELECT 
     MAX(Lvl) AS Lvl 
    ,TableName
    --,strSql = 'delete from [' + tablename + ']'
FROM 
    MyRecursion
GROUP BY
    TableName

ORDER BY lvl


/*
ORDER BY 

     2 ASC
    ,1 ASC

*/

Dường như

AND KCU1.TABLE_NAME <> KCU2.TABLE_NAME

là không cần thiết khi sử dụng information_schema, vì vậy nó thực sự sẽ nhanh hơn.


7
Tôi đã kiểm tra kế hoạch thực hiện cho truy vấn chính của bạn và f * ck tôi, đó là một mớ hỗn độn. oi41.tinypic.com/2zz0q41.jpg Tôi nghĩ đó là vấn đề của bạn.
ta.speot.is

@ ta.speot.is: Đúng, nhưng tôi đã hy vọng biết lý do của sự lộn xộn. Vì PG kết thúc sau 35 ms -> Báo cáo lỗi với MS.
Quandary

1
@BrandonMoore - Nó cho phép bạn, tốt hơn hoặc tồi tệ hơn, lập trình thêm các mệnh đề mà không cần phải xác minh nếu bạn cũng nên thêm một where.
Lieven Keersmaekers

1
@Brandon Moore: Như Lieven nói (chỉ khi mọi điều kiện bổ sung là và, nếu nó là hoặc, nó sẽ cần là 1 = 2), nhưng chủ yếu là đối với tôi để có thể nhanh chóng nhận xét bất kỳ và điều kiện nào mà không cần tạo ra một lỗi cú pháp, cho mục đích thử nghiệm.
Quandary

2
Chỉ cần nhìn vào kế hoạch thực hiện cho các truy vấn một phần có vẻ như tham gia trên idtrường là (không có gì đáng ngạc nhiên) hiệu quả hơn so với việc tham gia vào chuỗi, NAME, SCHEMAv.v. BTW bạn nên sử dụng, sys.objectsv.v. không được phản đốisysobjects
Martin Smith

Câu trả lời:


12

Tôi có lẽ sẽ từ bỏ các INFORMATION_SCHEMAkhung nhìn ở đây và sử dụng các sys.khung nhìn mới (trái ngược với các khung nhìn tương thích ngược), hoặc ít nhất là cụ thể hóa các kết quả của bảng JOINthành một bảng được lập chỉ mục trước tiên.

Các CTE đệ quy luôn có cùng một gói cơ bản trong SQL Server, trong đó mỗi hàng được thêm vào một bộ đệm ngăn xếp và được xử lý từng cái một. Điều này có nghĩa là việc nối giữa REFERENTIAL_CONSTRAINTS RC, KEY_COLUMN_USAGE KCU1, KEY_COLUMN_USAGE KCU2sẽ xảy ra nhiều lần như là kết quả của truy vấn sau SELECT COUNT(*) FROM MyRecursion.

Mà tôi giả sử trong trường hợp của bạn (từ thời gian thực hiện 11 phút) có lẽ là hàng ngàn lần nên bạn cần phần đệ quy để có hiệu quả nhất có thể. Truy vấn của bạn sẽ được thực hiện loại điều sau đây hàng ngàn lần.

   SELECT  
           KCU1.TABLE_CATALOG,
           KCU1.TABLE_SCHEMA,
           KCU1.TABLE_NAME
    FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC 
    INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1 
        ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG  
        AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA 
        AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME 
    INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2 
        ON KCU2.CONSTRAINT_CATALOG =  RC.UNIQUE_CONSTRAINT_CATALOG  
        AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA 
        AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME 
        AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION 
    WHERE KCU2.TABLE_NAME = 'FOO' 

(Lưu ý bên: Cả hai phiên bản truy vấn của bạn sẽ trả về kết quả không chính xác nếu cùng tên bảng trong các lược đồ khác nhau)

Như bạn có thể thấy kế hoạch cho việc này là khá kinh khủng.

Kế hoạch

So sánh điều này với kế hoạch cho systruy vấn của bạn có phần đơn giản hơn.

SELECT OnTable = OnTable.name, 
       AgainstTable = AgainstTable.name 
FROM   sysforeignkeys fk 
       INNER JOIN sysobjects OnTable 
         ON fk.fkeyid = OnTable.id 
       INNER JOIN sysobjects AgainstTable 
         ON fk.rkeyid = AgainstTable.id 
WHERE  AgainstTable.name = 'FOO' 

Kế hoạch 2

bạn có thể để khuyến khích các materialisation trung gian mà không cần tạo một #tempbảng một cách rõ ràng bằng cách thay đổi định nghĩa về MyDatađể

MyData AS 
( 
    SELECT TOP 99.999999 PERCENT
         TABLE_NAME AS OnTable  
        ,Fkeys.AgainstTable AS AgainstTable
    FROM INFORMATION_SCHEMA.TABLES 

    LEFT JOIN Fkeys
        ON TABLE_NAME = Fkeys.OnTable  

    WHERE (1=1) 
        AND TABLE_TYPE = 'BASE TABLE'
        AND TABLE_NAME NOT IN ('sysdiagrams', 'dtproperties') 
        ORDER BY TABLE_NAME
)

Thử nghiệm Adventureworks2008trên máy của tôi, điều này đã đưa thời gian chạy xuống từ khoảng 10 giây xuống còn 250ms (sau khi lần chạy đầu tiên không được thực hiện vì kế hoạch mất 2 giây để biên dịch). Nó bổ sung một bộ đệm háo hức vào kế hoạch cụ thể hóa kết quả của Tham gia vào cuộc gọi đệ quy đầu tiên sau đó phát lại nó trong các cuộc gọi tiếp theo. Tuy nhiên, hành vi này không được đảm bảo và bạn có thể muốn nâng cao yêu cầu mục Kết nối Cung cấp một gợi ý để buộc thực hiện trung gian hóa các CTE hoặc các bảng dẫn xuất

Tôi sẽ cảm thấy an toàn hơn khi tạo #tempbảng rõ ràng như dưới đây thay vì dựa vào hành vi này.

CREATE TABLE #MyData
(
OnTable SYSNAME,
AgainstTable NVARCHAR(128) NULL,
UNIQUE CLUSTERED (AgainstTable, OnTable)
);

WITH Fkeys AS 
(
    SELECT DISTINCT 
         KCU1.TABLE_NAME AS OnTable 
        ,KCU2.TABLE_NAME AS AgainstTable 
    FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC 

    LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1 
        ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG  
        AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA 
        AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME 

    LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2 
        ON KCU2.CONSTRAINT_CATALOG =  RC.UNIQUE_CONSTRAINT_CATALOG  
        AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA 
        AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME 
        AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION 

    WHERE (1=1)
    AND KCU1.TABLE_NAME <> KCU2.TABLE_NAME 
)

,MyData AS 
( 
    SELECT 
         TABLE_NAME AS OnTable  
        ,Fkeys.AgainstTable AS AgainstTable
    FROM INFORMATION_SCHEMA.TABLES 

    LEFT JOIN Fkeys
        ON TABLE_NAME = Fkeys.OnTable  

    WHERE (1=1) 
        AND TABLE_TYPE = 'BASE TABLE'
        AND TABLE_NAME NOT IN ('sysdiagrams', 'dtproperties') 
)
INSERT INTO #MyData
SELECT *
FROM MyData;


WITH MyRecursion AS 
(
    -- base case
    SELECT  
         OnTable AS TableName 
        ,1 AS Lvl 
    FROM #MyData
    WHERE 1=1
    AND AgainstTable IS NULL 

    -- recursive case
    UNION ALL 

    SELECT 
         OnTable AS TableName
        ,r.Lvl + 1 AS Lvl 
    FROM #MyData d 

    INNER JOIN MyRecursion r 
        ON d.AgainstTable = r.TableName 
)

SELECT 
     MAX(Lvl) AS Lvl 
    ,TableName
    --,strSql = 'delete from [' + tablename + ']'
FROM 
    MyRecursion
GROUP BY
    TableName

ORDER BY Lvl

DROP TABLE #MyData

Hay cách khác


Câu trả lời chính xác. Và cảm ơn bạn đã lưu ý, đó là một sản phẩm tốt. Tôi nghĩ hiện tại tôi cảm thấy an toàn hơn khi giữ phiên bản cũ hơn cho SQL Server thay vì tạo một bảng tạm thời / tem. Tôi dự định thay thế sysobjects bằng sys.objects. Hy vọng không có bất ngờ xấu ở đó. Một gợi ý CTE thực sự sẽ tốt, với thực tế là trình tối ưu hóa, không giống như đối tác pg của nó, không đủ thông minh để nhận ra điều này một cách tự động. Cho đến bây giờ tôi không nhận ra ai có thể xuất kế hoạch thực hiện dưới dạng hình ảnh. Một trong 4 điều tôi học được từ bài đăng này - cảm ơn bạn.
Quandary

2

Trong cả hai trường hợp, bạn đang truy vấn các khung nhìn, phần còn lại dành cho tương thích: Các khung nhìn tương thích và Các khung nhìn lược đồ thông tin .

Thay vào đó, hãy sử dụng chế độ xem danh mục để có hiệu suất tốt nhất (msdn: "Chúng tôi khuyên bạn nên sử dụng chế độ xem danh mục vì chúng là giao diện chung nhất cho siêu dữ liệu danh mục và cung cấp cách hiệu quả nhất để lấy, chuyển đổi và trình bày các hình thức tùy chỉnh của thông tin này") ..


2
Lược đồ thông tin không được để tương thích, đó là một phần của tiêu chuẩn SQL (92+) ...
Quandary
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.