mssql temp文件个数不够导致的锁等待 解决方法

tempdb 数据库 - SQL Server | Microsoft Learn

创建tempdb临时数据文件_最佳实践_云数据库 RDS_云数据库 RDS for SQL Server-华为云


操作场景

tempdb是系统数据库,是一个全局资源,可供连接到SQL Server实例或SQL数据库的所有用户使用 。它是一个临时数据库,无法永久保存数据,作用是给实例中的各种请求处理中间数据,分为主数据文件(.mdf)、次要数据文件(.ndf)和日志文件(.ldf)。当服务重启的时候,tempdb会被重新创建。

tempdb数据库如果在设计上存在缺陷,会存在性能上的问题。尤其是tempdb数据库在一些高并发的场景,如果应用频繁地创建和销毁临时表,会导致实例卡顿从而影响业务。

微软官方建议将临时数据库的文件拆分成多个,一般与逻辑CPU个数相同,超过8个则使用8个数据文件,解决闩锁争用问题每次额外加4个文件。

 

USE master
go
SET NOCOUNT ON

DECLARE
    @time_lock int,
    @show_type int

SELECT
    @time_lock = 1,
    @show_type = 0    -- 0. block only   1. all process    2. block, if not block, show all process
    

-- ================================================
-- Get Lock spid
-- ================================================
DECLARE
    @level smallint,
    @rows int
SELECT
    @level = 0,
    @rows = 0

DECLARE @tb_block TABLE(
    ID int IDENTITY
        PRIMARY KEY,
    block_id smallint,
    spid smallint,
    blocked smallint,
    waittime bigint,
    level smallint,
    UNIQUE(
        spid, blocked, block_id)
        WITH(
            IGNORE_DUP_KEY = ON)
)
INSERT @tb_block(
    block_id, spid, blocked, waittime, level)
SELECT
    CASE
        WHEN blocked = spid OR blocked = 0 THEN spid
        ELSE 0 END, 
    spid, blocked, MAX(waittime), @level
FROM master.dbo.sysprocesses P WITH(NOLOCK)
WHERE spid > 50
    AND blocked > 0
GROUP BY spid, blocked

SELECT
    @rows = @rows + @@ROWCOUNT

IF @show_type = 1 OR (@show_type = 2 AND @rows = 0)
    INSERT @tb_block(
        block_id, spid, blocked, waittime, level)
    SELECT
        CASE
            WHEN blocked = spid OR blocked = 0 THEN spid
            ELSE 0 END, 
        spid, blocked, MAX(waittime), @level
    FROM master.dbo.sysprocesses P WITH(NOLOCK)
    WHERE spid > 50
    GROUP BY spid, blocked

INSERT @tb_block(
    block_id, spid, blocked, waittime, level)
SELECT
    CASE
        WHEN blocked = spid OR blocked = 0 THEN spid
        ELSE 0 END, 
    spid, blocked, MAX(waittime), @level
FROM master.dbo.sysprocesses P WITH(NOLOCK)
WHERE spid > 50
    AND spid IN(
            SELECT blocked FROM @tb_block)
GROUP BY spid, blocked

SELECT
    @rows = @rows + @@ROWCOUNT

WHILE @rows > 0
BEGIN
    SELECT
        @level = @level + 1

    UPDATE A SET
        level = @level,
        block_id = B.block_id
    FROM @tb_block A, @tb_block B
    WHERE A.blocked = B.spid
        AND A.level = 0
        AND A.block_id = 0
        AND B.level = @level - 1
        AND B.block_id > 0

    SELECT
        @rows = @@ROWCOUNT
END

-- ================================================
-- only keep waittime >= @time_lock
-- ================================================
DELETE A
FROM @tb_block A
WHERE NOT EXISTS(
        SELECT * FROM @tb_block
        WHERE block_id = A.block_id
            AND waittime >= @time_lock * 1000)

-- ================================================
-- Get SQL Script
-- ================================================
IF OBJECT_ID(N'tempdb..#LockSQL') IS NOT NULL
    DROP TABLE #LockSQL

CREATE TABLE #LockSQL(
    EventType nvarchar(30), 
    spid int,
    sql nvarchar(4000),
    id int IDENTITY
        PRIMARY KEY)

DECLARE 
    @spid smallint,
    @sql nvarchar(4000)

DECLARE CUR_Lock CURSOR FORWARD_ONLY READ_ONLY LOCAL
FOR
SELECT DISTINCT 
    spid
FROM @tb_block
OPEN CUR_Lock
FETCH CUR_Lock INTO @spid
WHILE @@FETCH_STATUS = 0
BEGIN
    SELECT
        @sql = N'
INSERT #LockSQL(
    EventType, spid, sql) 
EXEC(N''DBCC INPUTBUFFER(' + RTRIM(@spid) + N')
WITH NO_INFOMSGS'')
'
    EXEC sp_executesql @sql

    IF @@ROWCOUNT > 0
    BEGIN
        UPDATE A SET 
            spid = @spid
        FROM #LockSQL A
        WHERE IDENTITYCOL = @@IDENTITY
    END

    FETCH CUR_Lock INTO @spid
END
CLOSE CUR_Lock
DEALLOCATE CUR_Lock

-- ================================================
-- Result
-- ================================================
;WITH
BLOCK AS(
    -- Block spid
    SELECT 
        L.ID,
        P.sql_handle,

        L.block_id,
        L.spid , 
        L.blocked, 

        P.waittype,
        waittime = CONVERT(bigint, P.waittime), 
        lastwaittype = CONVERT(nchar(32), LEFT(P.lastwaittype, LEN(P.lastwaittype) - 1)),
        waitresource = RTRIM(CONVERT(nvarchar(256), P.waitresource)),
        DbName = DB.name,
        hostname = RTRIM(CONVERT(nvarchar(256), P.hostname)),
        program_name = RTRIM(CONVERT(nvarchar(256), P.program_name)),
        hostprocess = CONVERT(nchar(10), P.hostprocess),
        loginame = CONVERT(nvarchar(256), P.loginame),  
        P.login_time, 
        nt_domain = RTRIM(CONVERT(nvarchar(256), P.nt_domain)),
        P.net_address,
        P.open_tran,  
        P.status, 
        cputime = P.cpu
    FROM @tb_block L
        INNER JOIN master.dbo.sysprocesses P WITH(NOLOCK)
            ON L.spid = P.spid
        INNER JOIN sys.databases DB WITH(NOLOCK)
            ON P.dbid = DB.database_id
),
BLSQL1 AS(
    -- block sql - 1
    SELECT
        spid,
        sql
    FROM #LockSQL
),
BLSQL2_PL AS(
    SELECT DISTINCT
        P.spid,
        P.sql_handle, P.stmt_start, P.stmt_end
    FROM master.dbo.sysprocesses P WITH(NOLOCK), @tb_block L
    WHERE P.spid = L.spid
        AND P.sql_handle > 0x
),
BLSQL2 AS(
    SELECT
        PL.spid,
        PL.sql_handle,
        sql_text = PT.text,
        sql_current =  SUBSTRING(
                    PT.text,
                    (PL.stmt_start / 2) + 1,
                    CASE PL.stmt_end
                        WHEN - 1 THEN LEN(PT.text)
                        ELSE (PL.stmt_end - PL.stmt_start) / 2 + 1
                    END)
    FROM BLSQL2_PL PL
        CROSS APPLY sys.dm_exec_sql_text(sql_handle) PT
),
BLPATH AS(
    SELECT 
        ID, block_id, spid, blocked, level,
        path = CONVERT(varchar(4000), RIGHT(1000 + block_id, 3))
    FROM @tb_block A
    WHERE NOT EXISTS(
            SELECT * FROM @tb_block
            WHERE block_id = A.block_id
                AND A.blocked = spid)
    UNION ALL
    SELECT
        A.ID, A.block_id, A.spid, A.blocked, A.level,
        path = CONVERT(varchar(4000), B.path + RIGHT(1000 + A.spid, 3))
    FROM @tb_block A, BLPATH B
    WHERE A.block_id = B.block_id
        AND A.blocked = B.spid
        AND B.blocked <> B.spid
)
SELECT
    Path = SPACE(BLPATH.level * 2) + N'|-' + RTRIM(level) + N'-',
    BLOCK.block_id,
    BLOCK.spid , 
    BLOCK.blocked, 

    BLOCK.waittype,
    waittimeFMT = RTRIM(BLOCK.waittime / 3600000)
        + ':'+ RIGHT(100 + (BLOCK.waittime / 60000) % 60, 2) 
        + ':'+ RIGHT(100 + (BLOCK.waittime / 1000) % 60, 2) 
        + '.'+ RIGHT(1000 + BLOCK.waittime % 1000, 3), 
    BLOCK.waittime, 
    BLOCK.lastwaittype,
    BLOCK.waitresource,
    BLOCK.DbName,
    BLOCK.hostname,
    BLOCK.program_name,
    BLOCK.hostprocess,
    BLOCK.loginame,
    BLOCK.login_time, 
    BLOCK.nt_domain,
    BLOCK.net_address,
    BLOCK.open_tran,  
    BLOCK.status, 
    BLOCK.cputime,

    BLSQL1.sql,

    BLSQL2.sql_text,
    BLSQL2.sql_current
FROM BLOCK
    INNER JOIN BLPATH
        ON BLOCK.ID = BLPATH.ID
    LEFT JOIN BLSQL1
        ON BLOCK.spid = BLSQL1.spid
    LEFT JOIN BLSQL2
        ON BLOCK.spid = BLSQL2.spid
            AND BLOCK.sql_handle = BLSQL2.sql_handle        
ORDER BY BLPATH.path
GO

增加个数
ALTER DATABASE [tempdb] ADD FILE ( NAME = N'temp5', FILENAME = N'K:\SQLTEMP\tempdb5.ndf', SIZE = 8MB, FILEGROWTH = 100MB)

GO


SELECT 
    name AS FileName, 
    size*1.0/128 AS FileSizeinMB,
    CASE max_size 
        WHEN 0 THEN 'Autogrowth is off.'
        WHEN -1 THEN 'Autogrowth is on.'
        ELSE 'Log file will grow to a maximum size of 2 TB.'
    END AS Autogrowth,
    growth AS 'GrowthValue',
    'GrowthIncrement' = 
        CASE
            WHEN growth = 0 THEN 'Size is fixed and will not grow.'
            WHEN growth > 0 AND is_percent_growth = 0 
                THEN 'Growth value is in 8-KB pages.'
            ELSE 'Growth value is a percentage.'
        END
FROM tempdb.sys.database_files
WHERE type=0
GO    

select COUNT(*) as cpu_num from sys.dm_os_schedulers where is_online=1

  

posted @ 2025-12-14 10:58  Tag  阅读(1)  评论(0)    收藏  举报