Các mệnh đề WHERE được áp dụng theo thứ tự chúng được viết?


36

Tôi đang cố gắng tối ưu hóa một truy vấn nhìn vào một bảng lớn (37 triệu hàng) và có một câu hỏi về thứ tự các thao tác được thực hiện trong một truy vấn.

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )

Các WHEREmệnh đề cho phạm vi ngày được thực hiện trước truy vấn con? Đây có phải là một cách tốt để đặt các mệnh đề hạn chế nhất lên trước để tránh các vòng lặp lớn cho các mệnh đề khác, để thực hiện nhanh hơn?

Bây giờ các truy vấn mất rất nhiều thời gian để thực hiện.

Câu trả lời:


68

Để giải thích về câu trả lời của @ alci:

PostgreSQL không quan tâm bạn viết thứ gì trong

  • PostgreQuery hoàn toàn không quan tâm đến thứ tự của các mục trong một WHEREmệnh đề và chọn chỉ mục và thứ tự thực hiện chỉ dựa trên ước tính chi phí và chọn lọc.

  • Thứ tự tham gia được viết cũng được bỏ qua cho đến cấu hình join_collapse_limit; nếu có nhiều kết nối hơn thế, nó sẽ thực hiện chúng theo thứ tự chúng được viết.

  • Các truy vấn con có thể được thực hiện trước hoặc sau truy vấn có chứa chúng, tùy thuộc vào những gì nhanh nhất, miễn là truy vấn con được thực hiện trước khi truy vấn bên ngoài thực sự cần thông tin. Thông thường trong thực tế, truy vấn con được thực hiện ở giữa hoặc xen kẽ với truy vấn bên ngoài.

  • Không có gì đảm bảo PostgreSQL thực sự sẽ thực hiện các phần của truy vấn. Họ có thể được tối ưu hóa hoàn toàn đi. Điều này rất quan trọng nếu bạn gọi các hàm có tác dụng phụ.

PostgreSQL sẽ chuyển đổi truy vấn của bạn

PostgreSQL sẽ chuyển đổi nhiều truy vấn trong khi vẫn giữ được các hiệu ứng chính xác tương tự, để làm cho chúng chạy nhanh hơn trong khi không thay đổi kết quả.

  • Các thuật ngữ bên ngoài truy vấn con có thể bị đẩy xuống truy vấn phụ để chúng thực thi như một phần của truy vấn phụ không phải là nơi bạn đã viết chúng trong truy vấn bên ngoài

  • Các thuật ngữ trong truy vấn con có thể được kéo lên truy vấn bên ngoài để việc thực hiện chúng được thực hiện như một phần của truy vấn bên ngoài, chứ không phải nơi bạn đã viết chúng trong truy vấn phụ

  • Truy vấn con có thể, và thường là, làm phẳng thành một liên kết trên bàn bên ngoài. Điều tương tự cũng đúng với những thứ như EXISTSNOT EXISTStruy vấn.

  • Lượt xem được làm phẳng vào truy vấn sử dụng chế độ xem

  • Các hàm SQL thường được đưa vào truy vấn gọi

  • ... và có rất nhiều phép biến đổi khác được thực hiện cho các truy vấn, chẳng hạn như đánh giá trước biểu thức không đổi, không tương quan của một số truy vấn con và tất cả các loại thủ thuật lập kế hoạch / tối ưu hóa khác.

Nói chung, PostgreSQL có thể chuyển đổi và viết lại truy vấn của bạn một cách ồ ạt, đến điểm mà mỗi truy vấn này:

select my_table.*
from my_table
left join other_table on (my_table.id = other_table.my_table_id)
where other_table.id is null;

select *
from my_table
where not exists (
  select 1
  from other_table
  where other_table.my_table_id = my_table.id
);

select *
from my_table
where my_table.id not in (
  select my_table_id
  from other_table
  where my_table_id is not null
);

tất cả thường sẽ tạo ra chính xác cùng một kế hoạch truy vấn. (Giả sử tôi đã không phạm phải bất kỳ sai lầm ngớ ngẩn nào ở trên).

Không có gì lạ khi chỉ cố gắng tối ưu hóa một truy vấn để thấy rằng trình hoạch định truy vấn đã tìm ra các thủ thuật bạn đang thử và áp dụng chúng một cách tự động, vì vậy phiên bản tối ưu hóa bằng tay không tốt hơn phiên bản gốc.

Hạn chế

Công cụ lập kế hoạch / trình tối ưu hóa khác xa so với omnicient và bị giới hạn bởi yêu cầu phải tuyệt đối chắc chắn rằng nó không thể thay đổi hiệu ứng của truy vấn, dữ liệu có sẵn để đưa ra quyết định, các quy tắc đã được triển khai và thời gian của CPU nó có thể đủ khả năng để chi tiêu suy nghĩ tối ưu hóa. Ví dụ:

  • Công cụ lập kế hoạch dựa trên số liệu thống kê được lưu giữ ANALYZE(thường thông qua autovacuum). Nếu những điều này đã lỗi thời, sự lựa chọn kế hoạch có thể là xấu.

  • Các số liệu thống kê chỉ là một mẫu, vì vậy chúng có thể gây hiểu nhầm do hiệu ứng lấy mẫu, đặc biệt là nếu mẫu quá nhỏ được lấy. Lựa chọn kế hoạch xấu có thể dẫn đến.

  • Các thống kê không theo dõi một số loại dữ liệu về bảng, như tương quan giữa các cột. Điều này có thể khiến người lập kế hoạch đưa ra quyết định tồi khi họ cho rằng mọi thứ là độc lập khi họ không.

  • Công cụ lập kế hoạch dựa trên các tham số chi phí muốn random_page_costcho nó biết tốc độ tương đối của các hoạt động khác nhau trên hệ thống cụ thể mà nó được cài đặt. Đây chỉ là hướng dẫn. Nếu họ sai lầm nghiêm trọng, họ có thể dẫn đến các lựa chọn kế hoạch kém.

  • Bất kỳ truy vấn con nào có LIMIThoặc OFFSETkhông thể được làm phẳng hoặc có thể bị kéo / đẩy xuống. Điều này không có nghĩa là nó sẽ thực hiện trước khi tất cả các bộ phận của truy vấn bên ngoài, tuy nhiên, hoặc thậm chí là nó sẽ thực hiện ở tất cả .

  • Các thuật ngữ CTE (các mệnh đề trong WITHtruy vấn) luôn được thực thi toàn bộ, nếu chúng được thực thi hoàn toàn. Chúng không thể được làm phẳng và các điều khoản không thể được đẩy lên hoặc kéo xuống qua hàng rào thuật ngữ CTE. Các thuật ngữ CTE luôn được thực hiện trước khi truy vấn cuối cùng. Đây không phải là hành vi tiêu chuẩn SQL , nhưng nó được ghi lại như cách PostgreQuery thực hiện.

  • PostgreQuery có một khả năng hạn chế để tối ưu hóa các truy vấn trên các bảng, security_barrierkhung nhìn và các loại quan hệ đặc biệt khác

  • PostgreQuery sẽ không nội tuyến một hàm được viết bằng bất cứ thứ gì ngoại trừ SQL đơn giản, cũng không thực hiện pullup / đẩy xuống giữa nó và truy vấn bên ngoài.

  • Công cụ lập kế hoạch / tối ưu hóa thực sự ngu ngốc về việc chọn các chỉ mục biểu thức và về sự khác biệt loại dữ liệu tầm thường giữa chỉ mục và biểu thức.

Tấn nhiều hơn, quá.

Truy vấn của bạn

Trong trường hợp truy vấn của bạn:

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )

không có gì ngăn cản nó được làm phẳng thành một truy vấn đơn giản hơn với một tập hợp các phép nối bổ sung, và rất có khả năng nó sẽ xảy ra.

Nó có thể sẽ bật ra một cái gì đó như (chưa được kiểm tra, rõ ràng):

select 1 
from workdays day
inner join offer on day.offer_id = offer.offer_day
inner join province on offer.id_province = province.id_province  
inner join center cr on cr.id_cr = province.id_cr 
where upper(offer.code_status) <> 'A' 
   and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
   and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
   and day.date_day >= '2014-10-01' 
   and day.date_day <= '2015-09-30';

PostgreSQL sau đó sẽ tối ưu hóa thứ tự tham gia và các phương thức tham gia dựa trên ước tính số lượng chọn lọc và số hàng và các chỉ mục có sẵn. Nếu những điều này phản ánh hợp lý thực tế thì nó sẽ tham gia và chạy các mục mệnh đề where theo thứ tự nào là tốt nhất - thường trộn chúng lại với nhau, do đó, một chút về điều này, sau đó quay lại phần đầu tiên, sau đó quay lại phần đầu tiên v.v.

Làm thế nào để xem những gì trình tối ưu hóa đã làm

Bạn không thể thấy SQL mà PostgreQuery tối ưu hóa truy vấn của bạn, bởi vì nó chuyển đổi SQL thành biểu diễn cây truy vấn nội bộ sau đó sửa đổi điều đó. Bạn có thể kết xuất kế hoạch truy vấn và so sánh nó với các truy vấn khác.

Không có cách nào để "khai thác" kế hoạch truy vấn đó hoặc cây kế hoạch nội bộ trở lại SQL.

http://explain.depesz.com/ có một người trợ giúp kế hoạch truy vấn khá. Nếu bạn hoàn toàn mới đối với các kế hoạch truy vấn, v.v. (trong trường hợp đó tôi rất ngạc nhiên là bạn đã thực hiện được điều này qua bài viết này) thì PGAdmin có trình xem kế hoạch truy vấn đồ họa cung cấp ít thông tin hơn nhưng đơn giản hơn.

Đọc liên quan:

Khả năng đẩy / kéo và làm phẳng tiếp tục cải thiện trong mỗi bản phát hành . PostgreSQL thường đúng về các quyết định kéo lên / đẩy xuống / làm phẳng, nhưng không phải lúc nào cũng vậy, vì vậy đôi khi bạn phải (ab) sử dụng CTE hoặc OFFSET 0hack. Nếu bạn tìm thấy một trường hợp như vậy, báo cáo một lỗi kế hoạch truy vấn.


Nếu bạn thực sự, thực sự quan tâm, bạn cũng có thể sử dụng debug_print_planstùy chọn để xem gói truy vấn thô, nhưng tôi hứa bạn không muốn đọc nó. Có thật không.


Wow ... câu trả lời khá đầy đủ :-) Một trong những trường hợp tôi có kế hoạch chậm với Postgresql (cũng như với các công cụ DB nổi tiếng khác, như Oracle), là với mối tương quan giữa các cột hoặc nhiều liên kết tương quan. Nó thường sẽ kết thúc việc thực hiện các vòng lặp lồng nhau, nghĩ rằng chỉ có một vài hàng tại thời điểm này của kế hoạch, trong khi thực tế có rất nhiều hàng ngàn. Một cách để tối ưu hóa các loại truy vấn này là 'đặt enable_nestloop = off;' trong thời gian của truy vấn.
alci

Tôi gặp phải tình huống v9.5.5 đang cố gắng áp dụng TO_DATE trước khi kiểm tra xem nó có thể được áp dụng hay không, trong một truy vấn mệnh đề 7 đơn giản. Đặt hàng quan trọng.
dùng1133275

@ user1133275 Trong trường hợp đó, nó chỉ hoạt động cho bạn một cách tình cờ, vì ước tính chi phí tính toán là như nhau. PostgreSQL vẫn có thể quyết định chạy to_datetrước khi kiểm tra trong một số phiên bản mới hơn hoặc do một số thay đổi thống kê tối ưu hóa. Để chạy kiểm tra một cách đáng tin cậy trước một chức năng chỉ nên chạy sau kiểm tra, hãy sử dụng một CASEcâu lệnh.
Craig Ringer

một trong những câu trả lời hay nhất tôi từng thấy trên SO! Đồng ý, anh bạn!
62mkv

Tôi đã trải nghiệm các tình huống trong đó việc thêm truy vấn đơn giản order bylàm cho việc thực hiện truy vấn nhanh hơn nhiều so với khi không có order by. Đó là một trong những lý do tôi viết các truy vấn của mình bằng cách tham gia theo cách mà tôi muốn thực hiện chúng - thật tuyệt khi có trình tối ưu hóa tuyệt vời nhưng tôi nghĩ sẽ không khôn ngoan khi tin tưởng hoàn toàn vào số phận của mình và viết truy vấn mà không nghĩ làm sao nó đã may bethi hành ... Câu trả lời tuyệt vời !!
Greg0ry

17

SQL là một ngôn ngữ khai báo: bạn nói những gì bạn muốn chứ không phải làm thế nào để làm điều đó. RDBMS sẽ chọn cách nó sẽ thực hiện truy vấn, được gọi là kế hoạch thực hiện.

Ngày xưa (5-10 năm trước), cách viết một truy vấn có tác động trực tiếp đến kế hoạch thực hiện, nhưng ngày nay, hầu hết các công cụ cơ sở dữ liệu SQL đều sử dụng Trình tối ưu hóa dựa trên chi phí để lập kế hoạch. Đó là, nó sẽ đánh giá các chiến lược khác nhau để thực hiện truy vấn, dựa trên số liệu thống kê của nó trên các đối tượng cơ sở dữ liệu và chọn chiến lược tốt nhất.

Hầu hết thời gian, nó thực sự là tốt nhất, nhưng đôi khi công cụ DB sẽ đưa ra những lựa chọn tồi, dẫn đến các truy vấn rất chậm.


Cần lưu ý rằng trên một số thứ tự truy vấn RDBMS vẫn còn có ý nghĩa, nhưng đối với những thứ cao cấp hơn, mọi thứ bạn nói đều đúng trong thực tế cũng như lý thuyết. Khi trình hoạch định truy vấn đang chọn các lựa chọn xấu về thứ tự thực hiện, thường có các gợi ý truy vấn có sẵn để đẩy nó theo hướng hiệu quả hơn (chẳng hạn như WITH(INDEX(<index>))trong MSSQL để buộc lựa chọn chỉ mục cho một phép nối cụ thể).
David Spillett

Câu hỏi là nếu một số chỉ số trên date_daythực sự tồn tại. Nếu không có thì trình tối ưu hóa không có nhiều kế hoạch để so sánh.
jkavalik
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.