Thay đổi truy vấn để cải thiện ước tính toán tử


14

Tôi có một truy vấn chạy trong một khoảng thời gian chấp nhận được nhưng tôi muốn đạt được hiệu suất cao nhất có thể từ nó.

Hoạt động tôi đang cố gắng cải thiện là "Tìm kiếm chỉ mục" ở bên phải của kế hoạch, từ Nút 17.

nhập mô tả hình ảnh ở đây

Tôi đã thêm các chỉ mục phù hợp nhưng ước tính tôi nhận được cho hoạt động đó là một nửa so với dự kiến.

Tôi đã tìm cách thay đổi các chỉ mục của mình và thêm một bảng tạm thời và viết lại truy vấn, nhưng tôi không thể đơn giản hóa nó nhiều hơn thế này để có được ước tính đúng.

Có ai có bất cứ đề nghị về những gì tôi có thể thử?

Kế hoạch đầy đủ và chi tiết của nó có thể được tìm thấy ở đây .

Kế hoạch không ẩn danh có thể được tìm thấy ở đây.

Cập nhật:

Tôi có cảm giác phiên bản ban đầu của câu hỏi gây ra nhiều nhầm lẫn, vì vậy tôi sẽ thêm mã gốc với một số giải thích.

create procedure [dbo].[someProcedure] @asType int, @customAttrValIds idlist readonly
as
begin
    set nocount on;

    declare @dist_ca_id int;

    select *
    into #temp
    from @customAttrValIds
        where id is not null;

    select @dist_ca_id = count(distinct CustomAttrID) 
    from CustomAttributeValues c
        inner join #temp a on c.Id = a.id;

    select a.Id
        , a.AssortmentId 
    from Assortments a
        inner join AssortmentCustomAttributeValues acav
            on a.Id = acav.Assortment_Id
        inner join CustomAttributeValues cav 
            on cav.Id = acav.CustomAttributeValue_Id
    where a.AssortmentType = @asType
        and acav.CustomAttributeValue_Id in (select id from #temp)
    group by a.AssortmentId
        , a.Id
    having count(distinct cav.CustomAttrID) = @dist_ca_id
    option(recompile);

end

Đáp án:

  1. Tại sao việc đặt tên ban đầu kỳ lạ trong liên kết pasteThePlan?

    Trả lời : Bởi vì tôi đã sử dụng gói ẩn danh từ SQL Sentry Plan Explorer.

  2. Tại sao OPTION RECOMPILE?

    Trả lời : Bởi vì tôi có thể đủ khả năng biên dịch lại để tránh việc đánh hơi tham số (dữ liệu bị / có thể bị sai lệch). Tôi đã thử nghiệm và tôi hài lòng với kế hoạch mà Trình tối ưu hóa tạo ra trong khi sử dụng OPTION RECOMPILE.

  3. WITH SCHEMABINDING?

    Trả lời : Tôi thực sự muốn tránh điều đó và sẽ chỉ sử dụng nó khi tôi có chế độ xem được lập chỉ mục. Dù sao, đây là một hàm hệ thống ( COUNT()) nên không sử dụng SCHEMABINDINGở đây.

Trả lời cho các câu hỏi có thể hơn:

  1. Tại sao tôi sử dụng INSERT INTO #temp FROM @customAttrributeValues?

    Trả lời : Bởi vì tôi nhận thấy và bây giờ biết rằng khi sử dụng các biến được cắm vào truy vấn, mọi ước tính xuất phát từ hoạt động với biến đó luôn là 1. Và tôi đã thử đưa dữ liệu vào bảng tạm thời và Ước tính sau đó bằng với Hàng thực tế .

  2. Tại sao tôi lại sử dụng and acav.CustomAttributeValue_Id in (select id from #temp)?

    Trả lời : Tôi có thể đã thay thế nó bằng THAM GIA trên #temp, nhưng các nhà phát triển đã rất bối rối và nhận được INtùy chọn này. Tôi thực sự không nghĩ rằng sẽ có một sự khác biệt ngay cả bằng cách thay thế và dù bằng cách nào, không có vấn đề gì với điều này.


Tôi đoán rằng các #tempsáng tạo và sử dụng sẽ là một vấn đề đối với hiệu suất, không tăng. Bạn đang lưu vào một bảng không được lập chỉ mục để được sử dụng một lần. Hãy thử loại bỏ nó hoàn toàn (và có thể thay đổi điều đó in (select id from #temp)thành existstruy vấn phụ.
ypercubeᵀᴹ

@ ypercubeᵀᴹ Đúng, chỉ khoảng một vài trang đọc với việc sử dụng biến thay vì bảng tạm thời.
Radu Gheorghiu

Nhân tiện, một biến bảng sẽ cung cấp ước tính số hàng chính xác khi được sử dụng với Tùy chọn (Biên dịch lại) - nhưng vẫn không có số liệu thống kê chi tiết, số lượng thẻ, v.v.
TH

@TH Vâng, tôi đã xem xét kế hoạch thực hiện thực tế tại các ước tính, khi sử dụng select id from @customAttrValIdsthay vì select id from #tempvà số lượng hàng ước tính là 1cho biến và 3cho #temp (khớp với # hàng thực tế). Đó là lý do tại sao tôi thay thế @bằng# . Và tôi DO nhớ một cuộc nói chuyện (từ Brent O hoặc Aaron Bertrand), nơi họ nói rằng khi sử dụng một biến tbl ước tính cho rằng sẽ luôn luôn được 1. Và như một sự cải tiến để có được ước tính tốt hơn họ sẽ sử dụng một bảng tạm thời.
Radu Gheorghiu

@RaduGheorghiu Vâng, nhưng trong thế giới của những người đó, tùy chọn (biên dịch lại) hiếm khi là một tùy chọn và họ cũng thích các bảng tạm thời vì những lý do hợp lệ khác. Có thể ước tính chỉ đơn giản là luôn hiển thị không chính xác là 1, vì nó thay đổi kế hoạch như đã thấy ở đây: theboreddba.com/C Chuyên / FinWithFlags / Lỗi
TH

Câu trả lời:


12

Kế hoạch được biên soạn trên phiên bản SQL Server 2008 R2 RTM (bản dựng 10.50.1600). Bạn nên cài đặt Gói dịch vụ 3 (bản dựng 10.50.6000), tiếp theo là các bản vá mới nhất để đưa nó lên bản dựng mới nhất (hiện tại) 10.50,6542. Điều này rất quan trọng vì một số lý do, bao gồm bảo mật, sửa lỗi và các tính năng mới.

Tối ưu hóa nhúng tham số

Liên quan đến câu hỏi hiện tại, SQL Server 2008 R2 RTM không hỗ trợ Tối ưu hóa nhúng tham số (PEO) cho OPTION (RECOMPILE). Ngay bây giờ, bạn đang trả chi phí biên dịch lại mà không nhận ra một trong những lợi ích chính.

Khi PEO khả dụng, SQL Server có thể sử dụng các giá trị bằng chữ được lưu trữ trong các biến và tham số cục bộ trực tiếp trong kế hoạch truy vấn. Điều này có thể dẫn đến đơn giản hóa đáng kể và tăng hiệu suất. Có nhiều thông tin hơn về điều đó trong bài viết của tôi, Thông số đánh hơi, nhúng và các tùy chọn RECOMPILE .

Hash, sắp xếp và trao đổi tràn

Chúng chỉ được hiển thị trong các kế hoạch thực hiện khi truy vấn được biên dịch trên SQL Server 2012 trở lên. Trong các phiên bản trước, chúng tôi phải theo dõi sự cố tràn trong khi truy vấn đang thực thi bằng Profiler hoặc Sự kiện mở rộng. Sự cố tràn luôn dẫn đến I / O vật lý đến (và từ) tempdb lưu trữ liên tục , điều này có thể gây ra hậu quả hiệu suất quan trọng, đặc biệt là nếu sự cố tràn lớn hoặc đường dẫn I / O chịu áp lực.

Trong kế hoạch thực hiện của bạn, có hai toán tử Hash Match (Uẩn). Bộ nhớ dành riêng cho bảng băm dựa trên ước tính cho các hàng đầu ra (nói cách khác, nó tỷ lệ thuận với số lượng nhóm được tìm thấy trong thời gian chạy). Bộ nhớ được cấp sẽ được sửa ngay trước khi bắt đầu thực thi và không thể phát triển trong khi thực thi, bất kể dung lượng bộ nhớ còn trống là bao nhiêu. Trong gói được cung cấp, cả hai toán tử Hash Match (Tổng hợp) tạo ra nhiều hàng hơn trình tối ưu hóa dự kiến ​​và do đó có thể gặp phải sự cố tràn sang tempdb khi chạy.

Ngoài ra còn có một toán tử Hash Match (Internal Join) trong kế hoạch. Bộ nhớ dành riêng cho bảng băm dựa trên ước tính cho các hàng đầu vào phía đầu dò . Đầu vào đầu dò ước tính 847.399 hàng, nhưng gặp phải 1.223.636 khi chạy. Sự dư thừa này cũng có thể gây ra sự cố tràn băm.

Tổng hợp dự phòng

Kết hợp băm (Tổng hợp) tại nút 8 thực hiện thao tác nhóm trên (Assortment_Id, CustomAttrID), nhưng các hàng đầu vào bằng với các hàng đầu ra:

Node 8 Hash Match (Tổng hợp)

Điều này cho thấy sự kết hợp cột là một khóa (vì vậy việc phân nhóm là không cần thiết về mặt ngữ nghĩa). Chi phí thực hiện tổng hợp dự phòng được tăng lên do cần phải vượt qua 1,4 triệu hàng hai lần trên các trao đổi phân vùng băm (toán tử Parallelism ở hai bên).

Do các cột liên quan đến từ các bảng khác nhau, việc truyền thông tin duy nhất này đến trình tối ưu hóa trở nên khó khăn hơn bình thường, do đó nó có thể tránh được hoạt động nhóm dự phòng và trao đổi không cần thiết.

Phân phối luồng không hiệu quả

Như đã lưu ý trong câu trả lời của Joe Obbish , việc trao đổi tại nút 14 sử dụng phân vùng băm để phân phối các hàng giữa các luồng. Thật không may, số lượng hàng nhỏ và bộ lập lịch có sẵn có nghĩa là cả ba hàng kết thúc trên một chuỗi. Kế hoạch rõ ràng song song chạy ser seri (với chi phí song song) cho đến khi trao đổi tại nút 9.

Bạn có thể giải quyết vấn đề này (để có được phân vùng vòng tròn hoặc phân vùng phát sóng) bằng cách loại bỏ Phân loại riêng biệt tại nút 13. Cách dễ nhất để làm điều đó là tạo một khóa chính được nhóm trên #tempbảng và thực hiện thao tác riêng biệt khi tải bảng:

CREATE TABLE #Temp
(
    id integer NOT NULL PRIMARY KEY CLUSTERED
);

INSERT #Temp
(
    id
)
SELECT DISTINCT
    CAV.id
FROM @customAttrValIds AS CAV
WHERE
    CAV.id IS NOT NULL;

Bảng tạm thời thống kê bộ nhớ đệm

Mặc dù sử dụng OPTION (RECOMPILE), SQL Server vẫn có thể lưu trữ đối tượng bảng tạm thời và các thống kê liên quan của nó giữa các lệnh gọi thủ tục. Đây thường là tối ưu hóa hiệu suất đáng hoan nghênh, nhưng nếu bảng tạm thời được điền với một lượng dữ liệu tương tự trên các cuộc gọi thủ tục liền kề, kế hoạch được biên dịch lại có thể dựa trên số liệu thống kê không chính xác (được lưu trong bộ thực thi trước đó). Điều này được trình bày chi tiết trong các bài viết của tôi, Các bảng tạm thời trong thủ tục lưu trữgiải thích bộ đệm tạm thời của bảng tạm thời .

Để tránh điều này, hãy sử dụng OPTION (RECOMPILE)cùng với một tường minh UPDATE STATISTICS #TempTablesau khi bảng tạm thời được điền và trước khi nó được tham chiếu trong một truy vấn.

Truy vấn viết lại

Phần này giả định những thay đổi để tạo #Tempbảng đã được thực hiện.

Với chi phí của sự cố tràn băm có thể và tổng hợp dự phòng (và các trao đổi xung quanh), có thể trả tiền để cụ thể hóa tập hợp tại nút 10:

CREATE TABLE #Temp2
(
    CustomAttrID integer NOT NULL,
    Assortment_Id integer NOT NULL,
);

INSERT #Temp2
(
    Assortment_Id,
    CustomAttrID
)
SELECT
    ACAV.Assortment_Id,
    CAV.CustomAttrID
FROM #temp AS T
JOIN dbo.CustomAttributeValues AS CAV
    ON CAV.Id = T.id
JOIN dbo.AssortmentCustomAttributeValues AS ACAV
    ON T.id = ACAV.CustomAttributeValue_Id;

ALTER TABLE #Temp2
ADD CONSTRAINT PK_#Temp2_Assortment_Id_CustomAttrID
PRIMARY KEY CLUSTERED (Assortment_Id, CustomAttrID);

Việc PRIMARY KEYnày được thêm vào trong một bước riêng biệt để đảm bảo việc xây dựng chỉ mục có thông tin chính xác về thẻ và để tránh sự cố bộ đệm thống kê bảng tạm thời.

Sự cụ thể hóa này hoàn toàn có khả năng xảy ra trong bộ nhớ (tránh I / O tempdb ) nếu thể hiện có đủ bộ nhớ khả dụng. Điều này thậm chí còn có khả năng hơn khi bạn nâng cấp lên SQL Server 2012 (SP1 CU10 / SP2 CU1 trở lên), đã cải thiện hành vi Eager Write .

Hành động này cung cấp cho trình tối ưu hóa thông tin chính xác của bộ tối ưu hóa trên tập trung gian, cho phép nó tạo số liệu thống kê và cho phép chúng tôi khai báo (Assortment_Id, CustomAttrID)dưới dạng khóa.

Kế hoạch cho dân số #Temp2sẽ giống như thế này (lưu ý quét chỉ mục theo cụm #Temp, không có Phân loại riêng biệt và trao đổi hiện sử dụng phân vùng hàng vòng tròn):

# Dân số Temp2

Với bộ có sẵn, truy vấn cuối cùng trở thành:

SELECT
    A.Id,
    A.AssortmentId
FROM
(
    SELECT
        T.Assortment_Id
    FROM #Temp2 AS T
    GROUP BY
        T.Assortment_Id
    HAVING
        COUNT_BIG(DISTINCT T.CustomAttrID) = @dist_ca_id
) AS DT
JOIN dbo.Assortments AS A
    ON A.Id = DT.Assortment_Id
WHERE
    A.AssortmentType = @asType
OPTION (RECOMPILE);

Chúng tôi có thể tự viết lại COUNT_BIG(DISTINCT...một cách đơn giản COUNT_BIG(*), nhưng với thông tin khóa mới, trình tối ưu hóa thực hiện điều đó cho chúng tôi:

Kế hoạch cuối cùng

Gói cuối cùng có thể sử dụng phép nối / băm / hợp nhất tùy thuộc vào thông tin thống kê về dữ liệu mà tôi không có quyền truy cập. Một lưu ý nhỏ khác: Tôi giả sử rằng một chỉ mục như CREATE [UNIQUE?] NONCLUSTERED INDEX IX_ ON dbo.Assortments (AssortmentType, Id, AssortmentId);tồn tại.

Dù sao, điều quan trọng về các kế hoạch cuối cùng là các ước tính sẽ tốt hơn nhiều và chuỗi hoạt động nhóm phức tạp đã được giảm xuống thành một Tập hợp luồng duy nhất (không yêu cầu bộ nhớ và do đó không thể tràn vào đĩa).

Thật khó để nói rằng hiệu suất thực sự sẽ tốt hơn trong trường hợp này với bảng tạm thời bổ sung, nhưng các ước tính và lựa chọn kế hoạch sẽ linh hoạt hơn nhiều đối với các thay đổi về khối lượng và phân phối dữ liệu theo thời gian. Điều đó có thể có giá trị hơn trong dài hạn so với mức tăng hiệu suất nhỏ hiện nay. Trong mọi trường hợp, bây giờ bạn có nhiều thông tin hơn để dựa vào quyết định cuối cùng của bạn.


9

Các ước tính cardinality trên truy vấn của bạn thực sự rất tốt. Thật hiếm khi có được số lượng hàng ước tính để khớp chính xác với số lượng hàng thực tế, đặc biệt là khi bạn có nhiều hàng này tham gia. Tham gia ước tính cardinality là khó khăn cho trình tối ưu hóa để có được đúng. Một điều quan trọng cần lưu ý là số lượng hàng ước tính cho phần bên trong của vòng lặp lồng nhau là trên mỗi lần thực hiện của vòng lặp đó. Vì vậy, khi SQL Server nói rằng 463869 hàng sẽ được tìm nạp với chỉ mục, hãy tìm ước tính thực trong trường hợp này là số lần thực thi (2) * 463869 = 927738 không quá xa so với số hàng thực tế, 1391608. Đáng ngạc nhiên, số lượng các hàng ước tính gần hoàn hảo ngay sau khi tham gia vòng lặp lồng nhau tại nút ID 10.

Ước tính cardinality kém chủ yếu là một vấn đề khi trình tối ưu hóa truy vấn chọn gói sai hoặc không cấp đủ bộ nhớ cho gói. Tôi không thấy bất kỳ sự cố tràn nào đến tempdb cho kế hoạch này, vì vậy bộ nhớ có vẻ ổn. Đối với phép nối vòng lặp lồng nhau mà bạn gọi ra, bạn có một bảng bên ngoài nhỏ và một bảng bên trong được lập chỉ mục. Có chuyện gì với nó vậy? Nói chính xác, bạn mong muốn trình tối ưu hóa truy vấn sẽ làm gì khác ở đây?

Về mặt cải thiện hiệu suất, điều nổi bật với tôi là SQL Server đang sử dụng thuật toán băm để phân phối các hàng song song dẫn đến tất cả chúng nằm trên cùng một luồng:

mất cân bằng chủ đề

Kết quả là, một luồng thực hiện tất cả công việc với chỉ mục tìm kiếm:

mất cân bằng tìm kiếm

Điều đó có nghĩa là truy vấn của bạn thực sự không chạy song song cho đến khi toán tử luồng phân vùng lại ở nút id 9. Điều bạn có thể muốn là phân vùng vòng tròn sao cho mỗi hàng kết thúc trên luồng của chính nó. Điều đó sẽ cho phép hai luồng thực hiện chỉ mục tìm kiếm nút id 17. Thêm một TOPtoán tử thừa có thể giúp bạn phân vùng vòng tròn. Tôi có thể thêm chi tiết ở đây nếu bạn thích.

Nếu bạn thực sự muốn tập trung vào ước tính cardinality, bạn có thể đặt các hàng sau lần đầu tiên tham gia vào bảng tạm thời. Nếu bạn thu thập số liệu thống kê trên bảng tạm thời cung cấp cho trình tối ưu hóa thêm thông tin về bảng bên ngoài cho phép nối vòng lặp lồng nhau mà bạn đã gọi ra. Nó cũng có thể dẫn đến phân vùng robin tròn.

Nếu bạn không sử dụng cờ theo dõi 4199 hoặc 2301, bạn có thể xem xét chúng. Trace flag 4199 cung cấp nhiều bản sửa lỗi tối ưu hóa, nhưng chúng có thể làm giảm một số khối lượng công việc. Cờ theo dõi 2301 thay đổi một số giả định cardinality tham gia của trình tối ưu hóa truy vấn và làm cho nó hoạt động mạnh hơn. Trong cả hai trường hợp kiểm tra cẩn thận trước khi cho phép chúng.


-2

Tôi tin rằng việc ước tính tốt hơn về việc tham gia đó sẽ không thay đổi kế hoạch, trừ khi 1,4 mill là một phần đủ của bảng để làm cho trình tối ưu hóa chọn quét chỉ mục (không phải cụm) với hàm băm hoặc hợp nhất. Tôi nghi ngờ rằng điều đó sẽ không xảy ra ở đây, cũng không thực sự hữu ích, nhưng bạn có thể kiểm tra các hiệu ứng bằng cách thay thế phép nối bên trong so với CustomAttributionValues ​​bằng phép nối băm bên trong và phép nối bên trong .

Tôi cũng đã nhìn rộng hơn về mã và không thể thấy bất kỳ cách nào để cải thiện nó - tất nhiên tôi rất muốn được chứng minh là sai. Và nếu bạn cảm thấy muốn đăng toàn bộ logic về những gì bạn đang cố gắng thực hiện, tôi sẽ quan tâm đến một cái nhìn khác.


3
Có một không gian kế hoạch rất lớn cho truy vấn đó, với nhiều tùy chọn cho thứ tự tham gia và lồng nhau, song song, tổng hợp cục bộ / toàn cầu, v.v. hầu hết sẽ bị ảnh hưởng bởi những thay đổi trong thống kê dẫn xuất (phân phối cũng như số lượng thẻ thô) tại nút kế hoạch 10. Cũng lưu ý rằng thường nên tránh các gợi ý tham gia vì chúng đi kèm với chế độ im lặng OPTION(FORCE ORDER), điều này ngăn cản trình tối ưu hóa sắp xếp lại tham gia từ chuỗi văn bản và nhiều tối ưu hóa khác bên cạnh.
Paul White phục hồi Monica

-12

Bạn sẽ không cải thiện từ Tìm kiếm Chỉ mục [không phân cụm]. Điều duy nhất tốt hơn tìm kiếm chỉ mục không phân cụm là Tìm kiếm chỉ mục cụm.

Ngoài ra, tôi đã là một DBA SQL trong mười năm qua và là nhà phát triển SQL trong năm năm trước đó và theo kinh nghiệm của tôi, rất hiếm khi tìm thấy một cải tiến cho Truy vấn SQL bằng cách nghiên cứu kế hoạch thực hiện mà bạn không thể t tìm bằng phương tiện khác. Lý do chính để tạo kế hoạch thực hiện là bởi vì nó thường sẽ đề xuất các chỉ mục bị thiếu cho bạn mà bạn có thể thêm để cải thiện hiệu suất.

Mức tăng hiệu suất chính sẽ là trong việc điều chỉnh chính Truy vấn SQL, nếu có bất kỳ sự thiếu hiệu quả nào ở đó. Ví dụ, một vài tháng trước tôi đã có một hàm SQL để chạy nhanh hơn 160 lần bằng cách viết lại một SELECT UNION SELECTbảng trụ kiểu để sử dụng PIVOTtoán tử SQL tiêu chuẩn .

insert into Variable1 values (?), (?), (?)


select *
    into Object1
    from Variable2
        where Column1 is not null;



select Variable3 = Function1(distinct Column2) 
    from Object2 Object3
        inner join Object1 Object4 on Object3.Column1 = Object4.Column1;



select Object4.Column1
        , Object4.Column3 
    from Object5 Object4
        inner join Object6 Object7
            on Object4.Column1 = Object7.Column4
        inner join Object2 Object8 
            on Object8.Column1 = Object7.Column5
    where Object4.Column6 = Variable4
        and Object7.Column5 in (select Column1 from Object1)
    group by Object4.Column3
        , Object4.Column1
    having Function1(distinct Object8.Column2) = Variable3
    option(recompile);

Vì vậy, hãy xem, SELECT * INTOnói chung là kém hiệu quả hơn so với một tiêu chuẩn INSERT Object1 (column list) SELECT column list. Vì vậy, tôi sẽ viết lại điều đó. Tiếp theo, nếu Function1 được xác định mà không có a WITH SCHEMABINDING, việc thêm WITH SCHEMABINDINGmệnh đề sẽ cho phép nó chạy nhanh hơn.

Bạn đã chọn rất nhiều bí danh không có ý nghĩa, như đặt bí danh Object2 làm Object3. Bạn nên chọn các bí danh tốt hơn mà không làm xáo trộn mã. Bạn có "Object7.Column5 in (chọn Cột1 từ Object1)".

INmệnh đề của bản chất này luôn luôn được viết hiệu quả hơn EXISTS (SELECT 1 FROM Object1 o1 WHERE o1.Column1 = Object7.Column5). Có lẽ tôi nên viết theo cách khác. EXISTSsẽ luôn luôn ít nhất là tốt như IN. Nó không phải luôn luôn tốt hơn, nhưng thường là như vậy.

Ngoài ra, tôi nghi ngờ rằng option(recompile)đang cải thiện hiệu suất truy vấn ở đây. Tôi sẽ kiểm tra loại bỏ nó.


6
Nếu tìm kiếm chỉ mục không bao gồm bao gồm truy vấn, thì hầu như luôn luôn tốt hơn tìm kiếm chỉ mục được nhóm, bởi vì theo định nghĩa, chỉ mục được nhóm có tất cả các cột trong đó và chỉ mục không bao gồm có ít cột hơn nên sẽ cần ít lượt tìm trang hơn (và ít cấp độ hơn của các bước vào cây b) để lấy dữ liệu. Vì vậy, không chính xác để nói rằng một tìm kiếm chỉ mục cụm sẽ luôn luôn tốt hơn.
ErikE
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.