2013-03-22 24 views
7

Tôi có một bảng thực sự lớn chứa đại diện số nguyên của địa chỉ IP và bảng thứ hai có dãy bắt đầu và kết thúc của số nguyên biểu diễn địa chỉ IP. Bảng thứ hai được sử dụng để trả lại quốc gia theo several stackoverflow articles. Mặc dù điều này trả về kết quả yêu cầu, hiệu suất khá kém. Có cách nào khác để thực hiện thay thế cao hơn để tham gia vào một phạm vi không? Bên dưới là một bộ mã mẫu cho biết cách hoạt động của tính năng tham gia hiện tại:Hiệu suất tối ưu để tham gia vào dãy giá trị

CREATE TABLE #BaseTable 
    (SomeIntegerValue INT PRIMARY KEY); 

INSERT INTO #BaseTable (SomeIntegerValue) 
SELECT SomeIntegerValue 
FROM (VALUES 
    (123), (456), (789)) Data (SomeIntegerValue); 

CREATE TABLE #RangeLookupTable 
    (RangeStartValue INT PRIMARY KEY 
    , RangeEndValue INT NOT NULL); 

INSERT INTO #RangeLookupTable (RangeStartValue, RangeEndValue) 
SELECT RangeStartValue, RangeEndValue 
FROM (VALUES 
     (0, 100), (101, 200), (201, 300) 
    , (301, 400), (401, 500), (501, 600) 
    , (701, 800), (901, 1000)) Data (RangeStartValue, RangeEndValue); 

SELECT * 
FROM #BaseTable bt 
JOIN #RangeLookupTable rlt 
    ON bt.SomeIntegerValue BETWEEN rlt.RangeStartValue AND rlt.RangeEndValue 
+0

Tôi đã chỉnh sửa tiêu đề của bạn. Vui lòng xem, "[Câu hỏi có nên bao gồm" thẻ "trong tiêu đề của họ không?] (Http://meta.stackexchange.com/questions/19190/)", trong đó sự đồng thuận là "không, họ không nên". –

+0

Làm việc cho tôi. Cảm ơn. –

+0

Bạn có chỉ mục trong bảng thực sự của mình không? Bởi vì kịch bản thử nghiệm của bạn không tạo ra bất kỳ nên có thể mọi lời khuyên được đưa ra sẽ cho bạn thêm các chỉ mục. –

Trả lời

0

Đây gần như chắc chắn là sự cố lập chỉ mục. Bạn hiện có chỉ mục trên RangeStartValue (khóa chính), nhưng không có trên RangeEndValue, do đó, có thể phải quét toàn bộ cột thứ hai sau khi thu hẹp đầu tiên. Thử lập chỉ mục RangeEndValue và xem cách ảnh hưởng đến nó.

Tôi không thạo về chất lượng hiệu suất của mệnh đề BETWEEN, nhưng bạn có thể đảm bảo rằng không phải là vấn đề bằng cách viết cả hai mặt của so sánh với kiểm tra bất bình đẳng.


Cũng trong tập lệnh thử nghiệm này, bạn đang chọn mọi hàng trong bảng cơ sở, tôi cho rằng bạn không thực hiện trong db sản xuất của mình?

+0

Tôi đã thử bao gồm cả RangeEndValue như một phần của khóa chính và là một chỉ mục riêng của riêng nó cả trong bảng thực và trong tập lệnh thử nghiệm được cung cấp, nhưng điều này không thay đổi kế hoạch truy vấn hoặc hiệu suất thực tế theo bất kỳ cách nào. Tôi cũng đã thử bao gồm CREATE INDEX IX_RangeEndValue ON #RangeLookupTable (RangeEndValue); và điều này đã thay đổi kế hoạch truy vấn để sử dụng chỉ mục không được nhóm tìm kiếm thay vì tìm kiếm chỉ mục nhóm, nhưng chi phí thực tế giống hệt nhau trong cả tập lệnh thử nghiệm và với bảng thực tế. –

0

Vấn đề là bảng tra cứu của bạn có các ô (các) địa chỉ chồng chéo không trùng nhau. Tuy nhiên, SQL Server có thể không nhận ra điều này. Vì vậy, khi bạn có ipaddress between A and B, nó phải quét toàn bộ chỉ mục bắt đầu từ đầu và kết thúc bằng A.

Tôi không biết liệu có cách nào giải thích những gì bảng thực sự đang thực hiện hay không, theo cách trình tối ưu hóa sẽ chuyển đến bản ghi thích hợp đầu tiên trong chỉ mục. Có thể một cái gì đó như thế này sẽ hoạt động:

select bt.*, 
     (select top 1 RangeEndValue 
     from #RangeLookupTable rlt 
     where rlt.RangeStartValue <= bt.SomeIntegerValue 
     order by RangeStartValue desc) 
FROM #BaseTable bt 

Điều này có thể "lừa" trình tối ưu hóa chỉ xem một bản ghi trong chỉ mục. Dữ liệu trong mẫu của bạn quá nhỏ để biết liệu điều này có ảnh hưởng đến hiệu suất hay không.

Cách tiếp cận khác là sử dụng tham gia equi để ngừng tìm kiếm. Trong mỗi bảng, thêm phần TypeA của địa chỉ (byte đầu tiên). Điều này có thể là dự phòng với trường thứ hai có địa chỉ đầy đủ hoặc bạn có thể đặt ba byte khác vào trường tiếp theo. Bất kỳ danh sách ip nào mở rộng nhiều địa chỉ TypeA sẽ cần được chia thành các mục riêng biệt.

Đặt trường này làm cột đầu tiên trong chỉ mục có địa chỉ (hoặc phần còn lại của địa chỉ) làm phần thứ hai của khóa chính. Bạn có thể sử dụng các ràng buộc để tạo khóa chính với nhiều cột.

Truy vấn sau đó sẽ như thế nào:

SELECT * 
FROM #BaseTable bt join 
    #RangeLookupTable rlt 
    ON bt.typeA = rlt.typeA and 
     bt.SomeIntegerValue BETWEEN rlt.RangeStartValue AND rlt.RangeEndValue 

Chỉ số quét sau đó sẽ được giới hạn chỉ với các giá trị với byte đầu tiên cùng. Tất nhiên, bạn cũng có thể mở rộng này thành TypeAB, sử dụng hai byte đầu tiên.

1

Nếu tình huống cụ thể cho phép giữ không chuẩn hóa dữ liệu trong bảng và sau đó truy vấn từ bảng đó thay vì bảng cơ sở chuẩn hóa, có thể đạt được thời gian truy xuất rất nhanh. Kế hoạch thực thi truy vấn hiển thị 2x trong SELECT, ngay cả với dữ liệu mẫu này của 3 hàng.

Cách tiếp cận như vậy có thể xảy ra trong một kịch bản có số lần viết tương đối ít hơn và nhiều hoạt động đọc hơn.JOIN sẽ cần phải được thực thi chỉ khi cập nhật dữ liệu; thử nghiệm với dữ liệu thực tế sẽ cho thấy sự cải thiện bao nhiêu (hoặc cho dù có chút nào!) thực sự đạt được trong hình ảnh hệ thống tổng thể (UPDATE + SELECT).

Mã mẫu, cùng với ảnh chụp màn hình Kế hoạch thực thi cho câu lệnh SELECT, được đưa ra dưới đây.

CREATE TABLE #BaseTable 
    (SomeIntegerValue INT PRIMARY KEY); 

INSERT INTO #BaseTable (SomeIntegerValue) 
SELECT SomeIntegerValue 
FROM (VALUES 
    (123), (456), (789)) Data (SomeIntegerValue); 

CREATE TABLE #RangeLookupTable 
    (RangeStartValue INT PRIMARY KEY 
    , RangeEndValue INT NOT NULL); 

INSERT INTO #RangeLookupTable (RangeStartValue, RangeEndValue) 
SELECT RangeStartValue, RangeEndValue 
FROM (VALUES 
     (0, 100), (101, 200), (201, 300) 
    , (301, 400), (401, 500), (501, 600) 
    , (701, 800), (901, 1000)) Data (RangeStartValue, RangeEndValue); 

-- Alternative approach: Denormalized base table 
CREATE TABLE #BaseTable2 
    (SomeIntegerValue INT PRIMARY KEY, 
     RangeStartValue INT null, 
     RangeEndValue INT NULL); 

INSERT INTO #BaseTable2 (SomeIntegerValue) 
SELECT SomeIntegerValue 
FROM (VALUES 
    (123), (456), (789)) Data (SomeIntegerValue); 

UPDATE #BaseTable2 
SET RangeStartValue = rlt.RangeStartValue, 
    RangeEndValue = rlt.RangeEndValue 
FROM #BaseTable2 bt2 
JOIN #RangeLookupTable rlt 
    ON bt2.SomeIntegerValue BETWEEN rlt.RangeStartValue AND rlt.RangeEndValue 

-- The original: SELECT with a JOIN 
SELECT * 
FROM #BaseTable bt 
JOIN #RangeLookupTable rlt 
    ON bt.SomeIntegerValue BETWEEN rlt.RangeStartValue AND rlt.RangeEndValue 

-- The alternative: SELECT from the denormalized base table 
SELECT * from #BaseTable2; 

GO 

kế hoạch thực hiện truy vấn cho vs Tham gia denormalized chọn:

Query Execution with a JOIN vs. a denormalized table

0

Tôi đã thử nghiệm 15 cách tiếp cận riêng biệt mà tôi nghĩ sẽ làm việc và giải pháp này là tốt nhất bởi cho đến nay:

SELECT bt.* 
    , RangeStartValue = 
     (SELECT TOP 1 RangeStartValue 
     FROM #RangeLookupTable rlt 
     WHERE bt.SomeIntegerValue >= rlt.RangeStartValue 
     ORDER BY rlt.RangeStartValue) 
FROM #BaseTable bt; 

Điều này tạo ra chỉ mục nhóm được tìm kiếm trên bảng tra cứu và có thể chuyển qua hàng triệu bản ghi trong vài giây. Xin lưu ý rằng tôi đã điều chỉnh giải pháp này từ mã số in this blog.

Các vấn đề liên quan