如何优化执行时间过长的存储过程?

s.b*_*ody 4 t-sql sql-server stored-procedures

我编写了一个存储过程来生成随机SMS记录/事件.

插入120万行时,查询需要数百分钟

exec insert_random_sms 1200000
Run Code Online (Sandbox Code Playgroud)

我以"程序"的方式编写了存储过程.但是,从我看来,SQL在这方面效率不高.

create proc insert_random_sms 
    @number_of_records int
as
begin
    declare @cnt int = 0;   -- loop counter
    declare @phone_id int;
    declare @dest_id int;

    while (@cnt < @number_of_records)
    begin
        declare @charge int = rand() * 100; -- will generate a random charge value between 0 and 100.
        declare @tarrif_plan int = round(rand() * 5, 0); 

        select top 1 @phone_id = phone_no 
        from tbl_phone_agenda 
        order by newid();

        select top 1 @dest_id = phone_no 
        from tbl_phone_agenda 
        order by newid();

        insert into tbl_sms (phone_id, dest_id, charge, tarrif_plan) 
        values (@phone_id, @dest_id, @charge, 
                convert(nvarchar(50), @tarrif_plan));

        set @cnt += 1;
    end
end
go
Run Code Online (Sandbox Code Playgroud)

优化此存储过程的方法是什么?

Gar*_*thD 7

我喜欢用于生成x个记录的方法是堆叠CTE方法(已经阅读了Aaron Bertrand的文章,他将Itzik Ben-Gan归功于堆叠CTE方法):

WITH N1 (N) AS 
(   SELECT 1 
    FROM (VALUES 
            (1), (1), (1), (1), (1), 
            (1), (1), (1), (1), (1)
        ) n (Number)
),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
N4 (N) AS (SELECT 1 FROM N3 AS N1 CROSS JOIN N3 AS N2)
SELECT COUNT(*)
FROM N4
Run Code Online (Sandbox Code Playgroud)

这只是从10行开始,并保持交叉连接,直到在上面的情况下有100,000,000行.这将是我如何生成你的行,

当你使用一组为基础的方法,你可以不再使用RAND()它自己,因为它是一个运行时间常数,以获得每个你需要组合排一个新的评估RAND()NEWID()这是每行都是唯一的,所以下面将产生0到100之间的随机数,每行不同:

SELECT  CAST(ROUND(RAND(CHECKSUM(NEWID())) * 100, 0) AS INT)
Run Code Online (Sandbox Code Playgroud)

我要做的下一件事是将所有的phonenumbers放入临时表中,以便它们具有顺序ID(这将用于随机分配):

CREATE TABLE #Phone
(
    ID INT IDENTITY NOT NULL PRIMARY KEY,
    PhoneNo VARCHAR(50) NOT NULL
);
INSERT #Phone (PhoneNo)
SELECT PhoneNo 
FROM tbl_phone_agenda;
Run Code Online (Sandbox Code Playgroud)

所以你的最终查询将是

CREATE PROC insert_random_sms @number_of_records IN
AS
BEGIN
    CREATE TABLE #Phone
    (
        ID INT IDENTITY NOT NULL PRIMARY KEY,
        PhoneNo VARCHAR(50) NOT NULL
    );
    INSERT #Phone (PhoneNo)
    SELECT PhoneNo 
    FROM tbl_phone_agenda;

    -- NEEDED SO WE KNOW WHAT NUMBER TO GENERATE A RANDOM
    -- NUMBER IN THE RIGHT RANGE LATER
    DECLARE @PhoneCount INT = (SELECT COUNT(*) FROM #Phone);

    WITH N1 (N) AS 
    (   SELECT 1 
        FROM (VALUES 
                (1), (1), (1), (1), (1), 
                (1), (1), (1), (1), (1)
            ) n (Number)
    ),
    N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
    N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
    N4 (N) AS (SELECT 1 FROM N3 AS N1 CROSS JOIN N3 AS N2)

    INSERT tbl_sms (phone_id, dest_id, charge, tarrif_plan) 
    SELECT  TOP (@number_of_records)
            p.PhoneNo,
            d.PhoneNo,
            Charge = CAST(ROUND(RAND(CHECKSUM(NEWID())) * 100, 0) AS INT),
            tarrif_plan = CAST(ROUND(RAND(CHECKSUM(NEWID())) * 5, 0) AS INT)
    FROM    N4
            INNER JOIN #Phone p
                ON p.ID = CAST(CEILING(RAND(CHECKSUM(NEWID())) * @PhoneCount) AS INT)
            INNER JOIN #Phone d
                ON d.ID = CAST(CEILING(RAND(CHECKSUM(NEWID())) * @PhoneCount) AS INT)

END
Run Code Online (Sandbox Code Playgroud)

在我的测试中,大约需要20-30秒才能生成120万条记录,查看100,000个电话号码.