Thật đơn giản: truy vấn chạy chỉ trong vài giây trong SQL Developer kết nối với Oracle 11g mất 15-25 phút trong SSRS 2008 R2. Tôi đã không thử các phiên bản khác của SSRS. Cho đến nay tôi đang thực hiện tất cả việc thực hiện báo cáo từ VS 2008.Truy vấn chạy nhanh trong Oracle SQL Developer, nhưng chậm trong SSRS 2008 R2
Tôi đang sử dụng Nhà cung cấp OLE DB "OraOLEDB.Oracle.1" mà trước đây dường như cho tôi kết quả tốt hơn so với sử dụng nhà cung cấp Oracle.
Đây là những gì tôi đã có thể để xác định cho đến nay:
• Việc chậm trễ là trong giai đoạn thực hiện DataSet và không có gì để làm với các kết quả thiết lập hoặc thời gian render. (Chứng minh bằng cách chọn cùng một rowset trực tiếp từ một bảng mà tôi đã chèn nó vào.)
• Bản thân SSRS không bị treo. Nó thực sự chờ đợi Oracle là nơi trì hoãn (được chứng minh bằng cách chấm dứt phiên DB từ phía Oracle, dẫn đến một lỗi nhắc nhở trong SSRS về phiên bị giết).
• Tôi đã thử truy vấn trực tiếp với các tham số trong biểu mẫu: Tham số. Các phiên bản đầu tiên của truy vấn của tôi đơn giản hơn rất nhiều cho việc truy vấn trực tiếp, nhưng nó dường như quá phức tạp nhất định, truy vấn sẽ bắt đầu lấy từ SSRS mãi mãi.
• Sau đó tôi chuyển sang thực thi một SP chèn các kết quả truy vấn vào bảng hoặc bảng tạm thời toàn cầu. Điều này đã giúp trong một thời gian ngắn, làm cho tôi xa hơn so với truy vấn trực tiếp, nhưng một lần nữa, nó gần như có vẻ như tăng độ phức tạp truy vấn hoặc chiều dài cuối cùng đã phá vỡ phương pháp này, quá. Lưu ý: việc chạy một tệp bảng SP hoạt động vì tùy chọn "sử dụng giao dịch đơn lẻ" được chọn trong tùy chọn DataSource, DataSets sau đó được chạy theo thứ tự xuất hiện của chúng trong tệp rdl. Các DataSets trả về không có Fields nào vẫn chạy, miễn là tất cả các tham số của chúng đều được thỏa mãn.
• Tôi vừa thử chức năng trả lại bảng và điều này vẫn không cải thiện, mặc dù các cuộc gọi trực tiếp với tham số theo nghĩa đen trong SQL Developer sẽ trở lại sau 1-5 giây.
• Cơ sở dữ liệu được đề cập không có số liệu thống kê. Nó là một phần của một sản phẩm được tạo ra bởi một nhà cung cấp và chúng tôi đã không có thời gian hoặc quản lý mua để tạo/cập nhật số liệu thống kê. Tôi đã chơi với gợi ý DYNAMIC_SAMPLING để tính toán số liệu thống kê và có kế hoạch thực hiện tốt hơn: không có thống kê mà trình tối ưu hóa dựa trên chi phí đã sử dụng LOOP kém thay vì tham gia HASH, gây ra nhiều lần thực thi tương tự. Vì vậy, tôi đưa ra gợi ý truy vấn để buộc tham gia thứ tự và cũng để gây ra nó để sử dụng tham gia băm chiến lược, đưa thời gian thực hiện trở lại chỉ vài giây. Tôi đã không quay trở lại và thử truy vấn trực tiếp trong SSRS bằng cách sử dụng các gợi ý thực hiện.
• Tôi có một số trợ giúp từ Oracle DBA của chúng tôi, người đã thiết lập một dấu vết (hoặc bất kỳ thứ gì tương đương với Oracle) và anh ta có thể thấy mọi thứ đang chạy, nhưng anh ta không tìm thấy gì hữu ích cho đến nay. Thật không may thời gian của mình là hạn chế và chúng tôi đã không thể thực sự khai thác để tìm hiểu những gì đang được thực hiện phía máy chủ. Tôi không có kinh nghiệm để làm điều này một cách nhanh chóng hoặc thời gian để nghiên cứu về cách làm điều này bản thân mình. Gợi ý về những gì cần làm để xác định những gì đang xảy ra sẽ được đánh giá cao.
giả thuyết duy nhất của tôi là:
• Các truy vấn được bằng cách nào đó nhận được một kế hoạch thực hiện xấu. Ví dụ: sử dụng LOOP không đúng cách thay vì tham gia HASH khi có hàng chục nghìn hàng "bên trái" hoặc hàng bên ngoài thay vì chỉ vài trăm.
• SSRS có thể gửi các tham số như nvarchar (4000) hoặc thứ gì đó thay vì hợp lý, và như Oracle SP & thông số chức năng không có thông số kỹ thuật về độ dài nhưng có độ dài thực hiện từ cuộc gọi truy vấn. như tham số sniffing là rối tung lên kế hoạch thực hiện như trong điểm trước đó.
• Truy vấn bằng cách nào đó được viết lại bởi SSRS/nhà cung cấp. Tôi đang sử dụng một tham số đa giá trị, nhưng không phải là: tham số đang được gửi dưới dạng tham gia biểu thức (Tham số! MultiValuedParameter.Value, ",") nên không cần viết lại. Chỉ cần một ràng buộc đơn giản và gửi. Tôi không thấy làm thế nào điều này có thể đúng trong SP và các chức năng gọi, nhưng gosh, những gì khác nó có thể được?
Tôi nhận ra đó là một truy vấn rất phức tạp và dài dòng, nhưng nó thực hiện chính xác những gì tôi cần. Nó chạy trong 1-5 giây tùy thuộc vào bao nhiêu dữ liệu được yêu cầu. Một số trong những lý do cho sự phức tạp là:
- đúng xử lý các chi phí danh sách trung tâm tham số bằng dấu phẩy
- Cho phép phân tích hàng tuần là không bắt buộc và nếu có, đảm bảo tất cả các tuần trong một tháng được hiển thị ngay cả khi không có dữ liệu cho họ.
- Hiển thị "Không có hóa đơn" khi thích hợp.
- Cho phép số tháng tóm tắt thay đổi.
- Có tổng số YTD tùy chọn.
- Bao gồm dữ liệu so sánh trước đây/lịch sử có nghĩa là tôi không thể đơn giản sử dụng nhà cung cấp của tháng này, tôi phải hiển thị tất cả các nhà cung cấp sẽ ở trong bất kỳ cột lịch sử nào.
Dù sao thì đây là truy vấn, phiên bản SP (mặc dù tôi không nghĩ rằng nó sẽ giúp ích nhiều).
create or replace
PROCEDURE VendorInvoiceSummary (
FromDate IN date,
ToDate IN date,
CostCenterList IN varchar2,
IncludeWeekly IN varchar2,
ComparisonMonths IN number,
IncludeYTD IN varchar2
)
AS
BEGIN
INSERT INTO InvoiceSummary (Mo, CostCenter, Vendor, VendorName, Section, TimeUnit, Amt)
SELECT
Mo,
CostCenter,
Vendor,
VendorName,
Section,
TimeUnit,
Amt
FROM (
WITH CostCenters AS (
SELECT Substr(REGEXP_SUBSTR(CostCenterList, '[^,]+', 1, LEVEL) || ' ', 1, 15) CostCenter
FROM DUAL
CONNECT BY LEVEL <= Length(CostCenterList) - Length(Replace(CostCenterList, ',', '')) + 1
), Invoices AS (
SELECT /*+ORDERED USE_HASH(D)*/
TRUNC(I.Invoice_Dte, 'YYYY') Yr,
TRUNC(I.Invoice_Dte, 'MM') Mo,
D.Dis_Acct_Unit CostCenter,
I.Vendor,
V.Vendor_VName,
CASE
WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate
THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM'))/7 + 1
ELSE 0
END WkNum,
Sum(D.To_Base_Amt) To_Base_Amt
FROM
ICCompany C
INNER JOIN APInvoice I
ON C.Company = I.Company
INNER JOIN APDistrib D
ON C.Company = D.Company
AND I.Invoice = D.Invoice
AND I.Vendor = D.Vendor
AND I.Suffix = D.Suffix
INNER JOIN CostCenters CC
ON D.Dis_Acct_Unit = CC.CostCenter
INNER JOIN APVenMast V ON I.Vendor = V.Vendor
WHERE
D.Cancel_Seq = 0
AND I.Cancel_Seq = 0
AND I.Invoice_Dte >= Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY'))
AND I.Invoice_Dte < ToDate
AND V.Vendor_Group = '1 ' -- index help
GROUP BY
TRUNC(I.Invoice_Dte, 'YYYY'),
TRUNC(I.Invoice_Dte, 'MM'),
D.Dis_Acct_Unit,
I.Vendor,
V.Vendor_VName,
CASE
WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate
THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM'))/7 + 1
ELSE 0
END
), Months AS (
SELECT ADD_MONTHS(Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')), LEVEL - 1) Mo
FROM DUAL
CONNECT BY LEVEL <= MONTHS_BETWEEN(ToDate, Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')))
), Sections AS (
SELECT 1 Section, 1 StartUnit, 5 EndUnit FROM DUAL
UNION ALL SELECT 2, 0, ComparisonMonths FROM DUAL
UNION ALL SELECT 3, 1, 1 FROM DUAL WHERE IncludeYTD = 'Y'
), Vals AS (
SELECT LEVEL - 1 TimeUnit
FROM DUAL
CONNECT BY LEVEL <= (SELECT Max(EndUnit) FROM Sections) + 1
), TimeUnits AS (
SELECT S.Section, V.TimeUnit
FROM
Sections S
INNER JOIN Vals V
ON V.TimeUnit BETWEEN S.StartUnit AND S.EndUnit
), Names AS (
SELECT DISTINCT
M.Mo,
Coalesce(I.Vendor, '0') Vendor,
Coalesce(I.Vendor_VName, 'No Paid Invoices') Vendor_VName,
Coalesce(I.CostCenter, ' ') CostCenter
FROM
Months M
LEFT JOIN Invoices I
ON Least(ADD_MONTHS(M.Mo, -ComparisonMonths), TRUNC(M.Mo, 'YYYY')) < I.Mo
AND M.Mo >= I.Mo
WHERE
M.Mo >= FromDate
AND M.Mo < ToDate
)
SELECT
N.Mo,
N.CostCenter,
N.Vendor,
N.Vendor_VName VendorName,
T.Section,
T.TimeUnit,
Sum(I.To_Base_Amt) Amt
FROM
Names N
CROSS JOIN TimeUnits T
LEFT JOIN Invoices I
ON N.CostCenter = I.CostCenter
AND N.Vendor = I.Vendor
AND (
(
T.Section = 1 -- Weeks for current month
AND N.Mo = I.Mo
AND T.TimeUnit = I.WkNum
) OR (
T.Section = 2 -- Summary months
AND ADD_MONTHS(N.Mo, -T.TimeUnit) = I.Mo
) OR (
T.Section = 3 -- YTD
AND I.Mo BETWEEN TRUNC(N.Mo, 'YYYY') AND N.Mo
)
)
WHERE
N.Mo >= FromDate
AND N.Mo < ToDate
AND NOT (-- Only 4 weeks when a month is less than 28 days long
T.Section = 2
AND T.TimeUnit = 5
AND TRUNC(N.Mo + 28, 'MM') <> N.Mo
AND I.CostCenter IS NULL
) AND (
T.Section <> 1
OR IncludeWeekly = 'Y'
)
GROUP BY
N.Mo,
N.CostCenter,
N.Vendor,
N.Vendor_VName,
T.Section,
T.TimeUnit
) X;
COMMIT;
END;
CẬP NHẬT
Ngay cả sau khi biết tất cả về kế hoạch thực hiện Oracle và gợi ý (dịch SQL Server kiến thức của tôi), tôi vẫn không thể có được truy vấn để chạy một cách nhanh chóng trong SSRS cho đến khi tôi đã làm cho nó chạy trong hai bước, trước tiên hãy đặt kết quả bảng thực vào một số GLOBAL TEMPORARY TABLE
và sau đó thứ hai để trích xuất dữ liệu từ đó. DYNAMIC_SAMPLING
đã cho tôi một kế hoạch thực hiện tốt, sau đó tôi đã sao chép bằng cách sử dụng các gợi ý tham gia và truy cập. Đây là SP cuối cùng (nó không thể là một hàm vì trong Oracle bạn không thể làm DML trong một hàm khi hàm đó được gọi bên trong câu lệnh SELECT):
Thỉnh thoảng tôi thề là bỏ qua các gợi ý tham gia của tôi như swap_join_inputs
và no_swap_join_inputs
nhưng từ đọc của tôi dường như Oracle chỉ bỏ qua gợi ý khi chúng thực sự không thể được sử dụng hoặc bạn đang làm điều gì sai. May mắn thay, các bảng được hoán đổi một cách thích hợp (như trong trường hợp của USE_NL(CC)
nó đáng tin cậy đặt bảng CC là đầu vào được hoán đổi, trái, mặc dù nó được nối lần cuối).
CREATE OR REPLACE
PROCEDURE VendorInvoicesSummary (
FromDate IN date,
ToDate IN date,
CostCenterList IN varchar2,
IncludeWeekly IN varchar2,
ComparisonMonths IN number,
IncludeYTD IN varchar2
)
AS
BEGIN
INSERT INTO InvoiceTemp (Yr, Mo, CostCenter, Vendor, WkNum, Amt) -- A global temporary table
SELECT /*+LEADING(C I D CC) USE_HASH(I D) USE_NL(CC)*/
TRUNC(I.Invoice_Dte, 'YYYY') Yr,
TRUNC(I.Invoice_Dte, 'MM') Mo,
D.Dis_Acct_Unit CostCenter,
I.Vendor,
CASE
WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate
THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM'))/7 + 1
ELSE 0
END WkNum,
Sum(D.To_Base_Amt) To_Base_Amt
FROM
ICCompany C
INNER JOIN APInvoice I
ON C.Company = I.Company
INNER JOIN APDistrib D
ON C.Company = D.Company
AND I.Invoice = D.Invoice
AND I.Vendor = D.Vendor
AND I.Suffix = D.Suffix
INNER JOIN (
SELECT Substr(REGEXP_SUBSTR(CostCenterList, '[^,]+', 1, LEVEL) || ' ', 1, 15) CostCenter
FROM DUAL
CONNECT BY LEVEL <= Length(CostCenterList) - Length(Replace(CostCenterList, ',', '')) + 1
) CC ON D.Dis_Acct_Unit = CC.CostCenter
WHERE
D.Cancel_Seq = 0
AND I.Cancel_Seq = 0
AND I.Invoice_Dte >= Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY'))
AND I.Invoice_Dte < ToDate
GROUP BY
TRUNC(I.Invoice_Dte, 'YYYY'),
TRUNC(I.Invoice_Dte, 'MM'),
D.Dis_Acct_Unit,
I.Vendor,
CASE
WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate
THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM'))/7 + 1
ELSE 0
END;
INSERT INTO InvoiceSummary (Mo, CostCenter, Vendor, VendorName, Section, TimeUnit, Amt)
SELECT
Mo,
CostCenter,
Vendor,
VendorName,
Section,
TimeUnit,
Amt
FROM (
WITH Months AS (
SELECT ADD_MONTHS(Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')), LEVEL - 1) Mo
FROM DUAL
CONNECT BY LEVEL <= MONTHS_BETWEEN(ToDate, Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')))
), Sections AS (
SELECT 1 Section, 1 StartUnit, 5 EndUnit FROM DUAL
UNION ALL SELECT 2, 0, ComparisonMonths FROM DUAL
UNION ALL SELECT 3, 1, 1 FROM DUAL WHERE IncludeYTD = 'Y'
), Vals AS (
SELECT LEVEL - 1 TimeUnit
FROM DUAL
CONNECT BY LEVEL <= (SELECT Max(EndUnit) FROM Sections) + 1
), TimeUnits AS (
SELECT S.Section, V.TimeUnit
FROM
Sections S
INNER JOIN Vals V
ON V.TimeUnit BETWEEN S.StartUnit AND S.EndUnit
), Names AS (
SELECT DISTINCT
M.Mo,
Coalesce(I.Vendor, '0') Vendor,
Coalesce(I.CostCenter, ' ') CostCenter
FROM
Months M
LEFT JOIN InvoiceTemp I
ON Least(ADD_MONTHS(M.Mo, -ComparisonMonths), TRUNC(M.Mo, 'YYYY')) <= I.Mo
AND I.Mo <= M.Mo
WHERE
M.Mo >= FromDate
AND M.Mo < ToDate
)
SELECT
N.Mo,
N.CostCenter,
N.Vendor,
Coalesce(V.Vendor_VName, 'No Paid Invoices') VendorName,
T.Section,
T.TimeUnit,
Sum(I.Amt) Amt
FROM
Names N
INNER JOIN APVenMast V ON N.Vendor = V.Vendor
CROSS JOIN TimeUnits T
LEFT JOIN InvoiceTemp I
ON N.CostCenter = I.CostCenter
AND N.Vendor = I.Vendor
AND (
(
T.Section = 1 -- Weeks for current month
AND N.Mo = I.Mo
AND T.TimeUnit = I.WkNum
) OR (
T.Section = 2 -- Summary months
AND ADD_MONTHS(N.Mo, -T.TimeUnit) = I.Mo
) OR (
T.Section = 3 -- YTD
AND I.Mo BETWEEN TRUNC(N.Mo, 'YYYY') AND N.Mo
)
)
WHERE
N.Mo >= FromDate
AND N.Mo < ToDate
AND V.Vendor_Group = '1 '
AND NOT (-- Only 4 weeks when a month is less than 28 days long
T.Section = 2
AND T.TimeUnit = 5
AND TRUNC(N.Mo + 28, 'MM') <> N.Mo
AND I.CostCenter IS NULL
) AND (
T.Section <> 1
OR IncludeWeekly = 'Y'
)
GROUP BY
N.Mo,
N.CostCenter,
N.Vendor,
V.Vendor_VName,
T.Section,
T.TimeUnit
) X;
COMMIT;
END;
này đã được một chặng đường dài, đau đớn đi xe, nhưng nếu có một điều tôi đã học được nó mà làm việc trong một cơ sở dữ liệu mà không thống kê được cập nhật đúng (mà tôi sẽ xem xét nhận được DBA của chúng tôi để thêm thậm chí mặc dù nhà cung cấp không quan tâm đến họ) có thể là một thảm họa thực sự cho những người muốn hoàn thành công việc trong một khoảng thời gian hợp lý.
là các thống kê của bạn được cập nhật? –
Không, như tôi đã nói nhà cung cấp không sử dụng số liệu thống kê. Họ làm dựa trên quy tắc cho mọi truy vấn trong DB này có nguồn gốc cổ đại. – ErikE