Sắp xếp sự cố tràn sang tempdb do varchar (max)


10

Trên máy chủ có 32 GB, chúng tôi đang chạy SQL Server 2014 SP2 với bộ nhớ tối đa 25 GB, chúng tôi có hai bảng, ở đây bạn tìm thấy cấu trúc đơn giản của cả hai bảng:

CREATE TABLE [dbo].[Settings](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [resourceId] [int] NULL,
    [typeID] [int] NULL,
    [remark] [varchar](max) NULL,
    CONSTRAINT [PK_Settings] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Resources](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [resourceUID] [int] NULL,
 CONSTRAINT [PK_Resources] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
GO

với các chỉ mục không phân cụm sau:

CREATE NONCLUSTERED INDEX [IX_UID] ON [dbo].[Resources]
(
    [resourceUID] ASC
)

CREATE NONCLUSTERED INDEX [IX_Test] ON [dbo].[Settings]
(
    [resourceId] ASC,
    [typeID] ASC
)

Cơ sở dữ liệu được cấu hình với compatibility level120.

Khi tôi chạy truy vấn này, có sự cố tràn tempdb. Đây là cách tôi thực hiện truy vấn:

exec sp_executesql N'
select r.id,remark
FROM Resources r
inner join Settings on resourceid=r.id
where resourceUID=@UID
ORDER BY typeID',
N'@UID int',
@UID=38

Nếu không chọn [remark]trường, không có sự cố tràn. Phản ứng đầu tiên của tôi là sự cố tràn xảy ra do số lượng hàng ước tính thấp trên toán tử vòng lặp lồng nhau.

Vì vậy, tôi thêm 5 cột thời gian và 5 cột số nguyên vào bảng cài đặt và thêm chúng vào câu lệnh chọn của tôi. Khi tôi thực hiện truy vấn không có sự cố tràn.

Tại sao sự cố tràn chỉ xảy ra khi [remark]được chọn? Nó có lẽ có một cái gì đó để làm với thực tế rằng đây là một varchar(max). Tôi có thể làm gì để tránh đổ tempdb?

Thêm OPTION (RECOMPILE)vào truy vấn làm cho không có sự khác biệt.


Có thể bạn có thể thử select r.id, LEFT(remark, 512)(hoặc bất kỳ chiều dài chuỗi con hợp lý nào có thể).
mustaccio

@Forrest: Tôi đang cố gắng tái tạo dữ liệu cần thiết để mô phỏng vấn đề. Ngay từ cái nhìn đầu tiên, nó phải làm với ước tính thấp của vòng lặp lồng nhau. Trong dữ liệu giả của tôi, số lượng hàng ước tính cao hơn nhiều và không xảy ra sự cố tràn
Frederik Vanderhaegen

Câu trả lời:


10

Sẽ có một số cách giải quyết có thể có ở đây.

Bạn có thể tự điều chỉnh cấp bộ nhớ, mặc dù tôi có thể sẽ không đi theo con đường đó.

Bạn cũng có thể sử dụng CTE và TOP để đẩy sắp xếp thấp hơn, trước khi lấy cột có độ dài tối đa. Nó sẽ trông giống như dưới đây.

WITH CTE AS (
SELECT TOP 1000000000 r.ID, s.ID AS ID2, s.typeID
FROM Resources r
inner join Settings s on resourceid=r.id
where resourceUID=@UID
ORDER BY s.typeID
)
SELECT c.ID, ca.remark
FROM CTE c
CROSS APPLY (SELECT remark FROM dbo.Settings s WHERE s.id = c.ID2) ca(remark)
ORDER BY c.typeID

Bằng chứng về khái niệm dbfiddle ở đây . Dữ liệu mẫu vẫn sẽ được đánh giá cao!

Nếu bạn muốn đọc một phân tích xuất sắc của Paul White, hãy đọc ở đây.


7

Tại sao sự cố tràn chỉ xảy ra khi [nhận xét] được chọn?

Sự cố tràn xảy ra khi bạn đưa vào cột đó vì bạn không nhận được cấp bộ nhớ đủ lớn cho dữ liệu chuỗi lớn được sắp xếp.

Bạn không nhận được cấp bộ nhớ đủ lớn vì số lượng hàng thực tế nhiều hơn gấp 10 lần số lượng hàng ước tính (1.302 thực tế so với 126 ước tính).

Tại sao ước tính tắt? Tại sao SQL Server nghĩ chỉ có một hàng trong dbo. Cài đặt với resourceid38?

Nó có thể là một vấn đề thống kê, mà bạn có thể kiểm tra bằng cách chạy DBCC SHOW_STATISTICS('dbo.Settings', 'IX_Test')và xem số đếm cho bước biểu đồ đó. Nhưng kế hoạch thực hiện dường như chỉ ra rằng các số liệu thống kê đã đầy đủ và cập nhật nhất có thể.

Vì số liệu thống kê không giúp ích gì, nên đặt cược tốt nhất của bạn có lẽ là viết lại truy vấn - điều mà Forrest đã đề cập trong câu trả lời của anh ấy.


3

Đối với tôi, có vẻ như wheremệnh đề trong truy vấn đang đưa ra vấn đề và là nguyên nhân của các ước tính thấp, ngay cả khi OPTION(RECOMPILE)được sử dụng.

Tôi đã tạo một số dữ liệu thử nghiệm và cuối cùng đã đưa ra hai giải pháp, lưu trữ IDtrường từ resourcesmột biến (nếu nó luôn là duy nhất) hoặc bảng tạm thời, nếu chúng ta có thể có nhiều hơn một ID.

Hồ sơ kiểm tra cơ sở

SET NOCOUNT ON
DECLARE @i int= 1;
WHILE @i <= 10000
BEGIN
INSERT INTO [dbo].[Settings]([resourceId],[typeID],remark)
VALUES(@i,@i,'KEPT THESE VALUES OUT BECAUSE IT WOULD CLUTTER THE EXAMPLES, VALUES OVER 8000 Chars entered here'); -- 23254 character length on each value
INSERT INTO  [dbo].[Resources](resourceUID)
VALUES(@i);
SET @i += 1;
END

Chèn các giá trị 'Tìm kiếm', để có được kết quả gần đúng tương tự như OP (1300 bản ghi)

INSERT INTO  [dbo].[Settings]([resourceId],[typeID],remark)
VALUES(38,38,'KEPT THESE VALUES OUT BECAUSE IT WOULD CLUTTER THE EXAMPLES, VALUES OVER 8000 Chars entered here')
GO 1300

Thay đổi số liệu thống kê và cập nhật để phù hợp với OP

ALTER DATABASE StackOverflow SET COMPATIBILITY_LEVEL = 120;
UPDATE STATISTICS settings WITH FULLSCAN;
UPDATE STATISTICS resources WITH FULLSCAN;

Truy vấn gốc

exec sp_executesql N'
select r.id
FROM Resources r
inner join Settings on resourceid=r.id
where resourceUID=@UID
ORDER BY typeID',
N'@UID int',
@UID=38

Ước tính của tôi thậm chí còn tồi tệ hơn , với một hàng ước tính, trong khi 1300 được trả lại. Và như OP đã nêu, không có vấn đề gì nếu tôi thêmOPTION(RECOMPILE)

Một điều quan trọng cần lưu ý là khi chúng ta thoát khỏi mệnh đề where, các ước tính là chính xác 100%, điều này được mong đợi vì chúng ta đang sử dụng tất cả dữ liệu trong cả hai bảng.

Tôi đã buộc các chỉ mục chỉ để đảm bảo rằng chúng tôi sử dụng các chỉ mục giống như trong truy vấn trước đó, để chứng minh luận điểm

exec sp_executesql N'
select r.id,remark
FROM Resources r with(index([IX_UID]))
inner join Settings WITH(INDEX([IX_Test])) 
on resourceid=r.id
ORDER BY typeID',
N'@UID int',
@UID=38

Như mong đợi, ước tính tốt .

Vì vậy, những gì chúng ta có thể thay đổi để có được ước tính tốt hơn nhưng vẫn tìm kiếm các giá trị của chúng tôi?

NẾU @UID là duy nhất, như trong ví dụ OP đã đưa ra, chúng ta có thể đặt đơn idđược trả về từ resourcesmột biến, sau đó tìm kiếm biến đó bằng TÙY CHỌN (RECOMPILE)

DECLARE @UID int =38 , @RID int;
SELECT @RID=r.id from 
Resources r where resourceUID = @UID;

SELECT @uid, remark 
from Settings 
where resourceId = @uid 
Order by typeID
OPTION(RECOMPILE);

Ước tính chính xác 100%.

Nhưng nếu có nhiều tài nguyênUID trong tài nguyên thì sao?

thêm một số dữ liệu thử nghiệm

INSERT INTO Resources(ResourceUID)
VALUES (38);
go 50

Điều này có thể được giải quyết với một bảng tạm thời

CREATE TABLE #RID (id int)
DECLARE @UID int =38 
INSERT INTO #RID
SELECT r.id 
from 
Resources r where resourceUID = @UID

SELECT @uid, remark 
from Settings  s
INNER JOIN #RID r
ON r.id =s.resourceId
Order by typeID
OPTION(RECOMPILE)

DROP TABLE #RID

Một lần nữa với ước tính chính xác .

Điều này đã được thực hiện với bộ dữ liệu của riêng tôi, YMMV.


Được viết bằng sp_executesql

Với một biến

exec sp_executesql N'
DECLARE  @RID int;
    SELECT @RID=r.id from 
    Resources r where resourceUID = @UID;

    SELECT @uid, remark 
    from Settings 
    where resourceId = @uid 
    Order by typeID
    OPTION(RECOMPILE);',
N'@UID int',
@UID=38

Với một bảng tạm thời

exec sp_executesql N'

CREATE TABLE #RID (id int)

INSERT INTO #RID
SELECT r.id 
from 
Resources r where resourceUID = @UID

SELECT @uid, remark 
from Settings  s
INNER JOIN #RID r
ON r.id =s.resourceId
Order by typeID
OPTION(RECOMPILE)

DROP TABLE #RID',
N'@UID int',
@UID=38

Vẫn đúng 100% ước tính trong bài kiểm tra của tôi

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.