相似查询不同的执行计划和性能

问题描述 投票:0回答:0

这是我用来在来自 200 个不同城市的客户表上生成 1000 万行的脚本。 (在台式电脑上用了 11 分钟)

*最后的重要提示

    -- Crear la tabla Clientes
CREATE TABLE Clientes (
    id_cliente BIGINT PRIMARY KEY IDENTITY(1, 1),
    id_ciudad INT,
    nombre NVARCHAR(100),
    email NVARCHAR(320),
    ciudad nvarchar(100)
);


-- Crear la tabla Clientes_Ciudad
CREATE TABLE Clientes_Ciudad (
    id_ciudad INT PRIMARY KEY IDENTITY(1, 1),
    ciudad NVARCHAR(100) UNIQUE
);
go 

alter PROCEDURE [dbo].[PopulateData]
AS
BEGIN
    SET NOCOUNT ON;

    -- Crear la tabla de números con 10 millones de registros
    WITH
    L0 AS (SELECT 1 AS c 
            FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),
                        (1),(1),(1),(1),(1),(1),(1),(1)) AS D(c)),
    L1 AS (SELECT 1 AS c FROM L0 CROSS JOIN L0 AS B),
    L2 AS (SELECT 1 AS c FROM L1 CROSS JOIN L1 AS B),
    L3 AS (SELECT 1 AS c FROM L2 CROSS JOIN L2 AS B),
    L4 AS (SELECT 1 AS c FROM L3 CROSS JOIN L3 AS B),
    Nums AS (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n FROM L4)
    SELECT TOP 10000000 n INTO #Numbers FROM Nums;

    -- Inserción de ciudades
    INSERT INTO Clientes_Ciudad (ciudad)
    SELECT N'ciudad_' + RIGHT('00000' + CAST(n AS NVARCHAR(5)), 5)
    FROM #Numbers
    WHERE n <= 10000;

    -- Calcular la cantidad de clientes por ciudad
    DECLARE @current_city_id INT = 1;
    DECLARE @city_increment INT = 1;
    DECLARE @clients_in_city INT = 1000;

    CREATE TABLE #CityClients (city_id INT PRIMARY KEY, client_start INT, client_end INT);

    WHILE @current_city_id <= 10000
    BEGIN
        INSERT INTO #CityClients (city_id, client_start, client_end)
        VALUES (@current_city_id, @clients_in_city * (@current_city_id - 1) + 1, @clients_in_city * @current_city_id);

        SET @current_city_id = @current_city_id + 1;
        SET @clients_in_city = @clients_in_city + 500 * @city_increment;
        SET @city_increment = @city_increment + 1;
    END

    -- Inserción de clientes
    INSERT INTO clientes (id_ciudad, nombre, email,ciudad)
    SELECT
        CC.city_id,
        N'cliente_' + RIGHT('0000000' + CAST(N.n AS NVARCHAR(7)), 7),
        N'email_' + CAST(NEWID() AS NVARCHAR(36)) + N'@example.com',
        N'ciudad_' + RIGHT('00000' + CAST(n AS NVARCHAR(5)), 5)
    FROM #Numbers AS N
    INNER JOIN #CityClients AS CC
        ON N.n BETWEEN CC.client_start AND CC.client_end;

    -- Crear índices
    CREATE INDEX idx_clientes_id_ciudad ON clientes (id_ciudad);
    CREATE INDEX idx_clientes_ciudad_nombre_email ON clientes (id_ciudad) INCLUDE (nombre, email);
    CREATE INDEX idx_clientes_ciudad ON Clientes


        -- Eliminar las tablas temporales
    DROP TABLE #Numbers;
    DROP TABLE #CityClients;
END
GO

-- Ejecuta el stored procedure para poblar las tablas
EXEC [dbo].[PopulateData]
GO
delete Clientes_Ciudad where id_ciudad>10000
go

我做了一些消毒,但结果几乎一样。

问题是为什么这两个查询执行不同。为什么第二个执行计划使用Nested Loop(Inner Join),如何避免?

    declare @ciudad_id int;
SELECT @ciudad_id= id_ciudad FROM Clientes_Ciudad WHERE ciudad = N'Ciudad_00199';

SELECT id_cliente, nombre, email 
FROM clientes
WHERE id_ciudad = @ciudad_id;

SELECT id_cliente, nombre, email
FROM clientes   
WHERE  id_ciudad =(SELECT id_ciudad FROM Clientes_Ciudad WHERE ciudad = N'Ciudad_00199');

这是我得到的执行计划。如果我在其中一个查询中将

ciudad = N'Ciudad_00199'
更改为不同的查询,结果是相同的。

注意

从头开始。

我向一位朋友证明这两个查询的表现略有不同,前者更胜一筹,事实确实如此。

SELECT id_cliente, nombre, email 
FROM clientes
WHERE id_ciudad = 18;
  

SELECT id_cliente, nombre, email
FROM clientes a INNER JOIN Clientes_Ciudad b ON a.id_ciudad = b.id_ciudad
WHERE b.ciudad = N'Ciudad_00018';

但是,当我比较这个时,两者表现相同。令我惊讶

SELECT id_cliente, nombre, email
FROM clientes   
WHERE  id_ciudad =(SELECT id_ciudad FROM Clientes_Ciudad WHERE ciudad = N'Ciudad_00017');


SELECT id_cliente, nombre, email
FROM clientes a INNER JOIN Clientes_Ciudad b ON a.id_ciudad = b.id_ciudad
WHERE b.ciudad = N'Ciudad_00018';

所以我首先编写了用于获取变量的脚本,并获得了我想要的结果和性能,然后来到这里帮助我了解发生了什么以及如何在单个查询中而不是脚本中执行它(只是为了了解它是否是可以的,没有特殊需要)

declare @ciudad_id int;
SELECT @ciudad_id= id_ciudad FROM Clientes_Ciudad WHERE ciudad = N'Ciudad_00015';

SELECT id_cliente, nombre, email 
FROM clientes
WHERE id_ciudad = @ciudad_id;
  

SELECT id_cliente, nombre, email
FROM clientes   
WHERE  id_ciudad =(SELECT id_ciudad FROM Clientes_Ciudad WHERE ciudad = N'Ciudad_00017');

引擎比我聪明,我知道在

WHERE  id_ciudad =
之后你只能接受一个标量值(或null),那么为什么它使用嵌套循环,我如何强制其他方式?

sql sql-server sql-execution-plan
© www.soinside.com 2019 - 2024. All rights reserved.