sql性能优化,如何优化 in/not in这类关键字的语句?

  1. 使用 EXISTS 替代 IN
    EXISTS 子查询通常比 IN 子查询更高效,特别是在子查询返回大量结果时。
  • 示例:

    -- 使用 IN
    SELECT * FROM Orders
    WHERE UserId IN (SELECT UserId FROM Users WHERE IsVIP = 1);

    -- 使用 EXISTS
    SELECT * FROM Orders o
    WHERE EXISTS (SELECT 1 FROM Users u WHERE u.UserId = o.UserId AND u.IsVIP = 1);

  1. 使用 JOIN 替代 IN
    JOIN 操作通常比 IN 子查询更高效,特别是在复杂的查询中。
  • 示例:

    -- 使用 IN
    SELECT * FROM Orders
    WHERE UserId IN (SELECT UserId FROM Users WHERE IsVIP = 1);

    -- 使用 JOIN
    SELECT o.*
    FROM Orders o
    JOIN Users u ON u.UserId = o.UserId
    WHERE u.IsVIP = 1;

  1. 使用 NOT EXISTS 替代 NOT IN
    NOT EXISTS 子查询通常比 NOT IN 子查询更高效,特别是在子查询返回大量结果或包含 NULL 值时。
  • 示例

    -- 使用 NOT IN
    SELECT * FROM Orders
    WHERE UserId NOT IN (SELECT UserId FROM Users WHERE IsVIP = 1);

    -- 使用 NOT EXISTS
    SELECT * FROM Orders o
    WHERE NOT EXISTS (SELECT 1 FROM Users u WHERE u.UserId = o.UserId AND u.IsVIP = 1);

  1. 使用 LEFT JOIN 替代 NOT IN
    LEFT JOIN 操作通常比 NOT IN 子查询更高效,特别是在处理 NULL 值时。
  • 示例:

    -- 使用 NOT IN
    SELECT * FROM Orders
    WHERE UserId NOT IN (SELECT UserId FROM Users WHERE IsVIP = 1);

    -- 使用 LEFT JOIN
    SELECT o.*
    FROM Orders o
    LEFT JOIN Users u ON u.UserId = o.UserId AND u.IsVIP = 1
    WHERE u.UserId IS NULL;

  1. 确保索引**
    确保 IN 和 NOT IN 子查询中使用的列上有合适的索引。
  • 示例:

    CREATE INDEX idx_users_userid ON Users(UserId);
    CREATE INDEX idx_users_isvip ON Users(IsVIP);

  1. 避免使用 IN 进行范围查询**
    IN 关键字不适合进行范围查询,特别是在子查询返回大量结果时。
  • 示例:

    -- 避免这种写法
    SELECT * FROM Orders
    WHERE OrderDate IN (SELECT OrderDate FROM Orders WHERE UserId = 1);

    -- 使用 JOIN 或 EXISTS
    SELECT o.*
    FROM Orders o
    JOIN Orders o2 ON o.OrderDate = o2.OrderDate
    WHERE o2.UserId = 1;

  1. 处理 NULL 值**
    NOT IN 子查询在子查询结果包含 NULL 值时会返回空结果集。可以使用 NOT EXISTS 或 LEFT JOIN 来避免这个问题。
  • 示例

    -- 使用 NOT IN
    SELECT * FROM Orders
    WHERE UserId NOT IN (SELECT UserId FROM Users WHERE IsVIP IS NULL OR IsVIP = 1);

    -- 使用 NOT EXISTS
    SELECT * FROM Orders o
    WHERE NOT EXISTS (SELECT 1 FROM Users u WHERE u.UserId = o.UserId AND (u.IsVIP IS NULL OR u.IsVIP = 1));

示例:综合优化

假设我们有一个订单表 Orders 和一个用户表 Users,我们希望查询所有属于 VIP 用户的订单。以下是使用 IN、EXISTS 和 JOIN 的比较和优化示例。

  1. 创建表和索引

    CREATE TABLE Users (
    UserId INT PRIMARY KEY,
    Name NVARCHAR(100),
    IsVIP BIT
    );

    CREATE TABLE Orders (
    OrderId INT PRIMARY KEY,
    UserId INT,
    OrderDate DATE,
    TotalAmount DECIMAL(10, 2)
    );

    CREATE INDEX idx_users_isvip ON Users(IsVIP);
    CREATE INDEX idx_orders_userid ON Orders(UserId);

  2. 插入示例数据

    INSERT INTO Users (UserId, Name, IsVIP)
    VALUES
    (1, 'Alice', 1),
    (2, 'Bob', 0),
    (3, 'Charlie', 1);

    INSERT INTO Orders (OrderId, UserId, OrderDate, TotalAmount)
    VALUES
    (101, 1, '2023-10-01', 100.00),
    (102, 2, '2023-10-02', 200.00),
    (103, 3, '2023-10-03', 150.00);

  3. 使用 IN 子查询

    -- 使用 IN 子查询
    SET STATISTICS TIME ON;
    SELECT * FROM Orders
    WHERE UserId IN (SELECT UserId FROM Users WHERE IsVIP = 1);
    SET STATISTICS TIME OFF;

  4. 使用 EXISTS 子查询

    -- 使用 EXISTS 子查询
    SET STATISTICS TIME ON;
    SELECT * FROM Orders o
    WHERE EXISTS (SELECT 1 FROM Users u WHERE u.UserId = o.UserId AND u.IsVIP = 1);
    SET STATISTICS TIME OFF;

  5. 使用 JOIN 操作

    -- 使用 JOIN 操作
    SET STATISTICS TIME ON;
    SELECT o.*
    FROM Orders o
    JOIN Users u ON u.UserId = o.UserId
    WHERE u.IsVIP = 1;
    SET STATISTICS TIME OFF;

解释

  1. 使用 EXISTS 和 JOIN:
    • EXISTS 子查询和 JOIN 操作通常比 IN 子查询更高效,特别是在子查询返回大量结果时。
    • EXISTS 子查询只检查是否存在符合条件的记录。
    • JOIN 操作通过连接表来获取符合条件的记录。
  2. 索引:
    • 确保 UserId 和 IsVIP 列上有合适的索引,以提高查询性能。
    • CREATE INDEX idx_users_isvip ON Users(IsVIP);
    • CREATE INDEX idx_orders_userid ON Orders(UserId);

示例:完整优化

以下是一个完整的示例,展示了如何在 SQL 查询中应用上述优化策略。

  1. 创建表和索引

    CREATE TABLE Users (
    UserId INT PRIMARY KEY,
    Name NVARCHAR(100),
    IsVIP BIT
    );

    CREATE TABLE Orders (
    OrderId INT PRIMARY KEY,
    UserId INT,
    OrderDate DATE,
    TotalAmount DECIMAL(10, 2)
    );

    CREATE INDEX idx_users_isvip ON Users(IsVIP);
    CREATE INDEX idx_orders_userid ON Orders(UserId);

  2. 插入示例数据

    INSERT INTO Users (UserId, Name, IsVIP)
    VALUES
    (1, 'Alice', 1),
    (2, 'Bob', 0),
    (3, 'Charlie', 1);

    INSERT INTO Orders (OrderId, UserId, OrderDate, TotalAmount)
    VALUES
    (101, 1, '2023-10-01', 100.00),
    (102, 2, '2023-10-02', 200.00),
    (103, 3, '2023-10-03', 150.00);

  3. 使用 IN 子查询

    -- 使用 IN 子查询
    SET STATISTICS TIME ON;
    SELECT * FROM Orders
    WHERE UserId IN (SELECT UserId FROM Users WHERE IsVIP = 1);
    SET STATISTICS TIME OFF;

  4. 使用 EXISTS 子查询

    -- 使用 EXISTS 子查询
    SET STATISTICS TIME ON;
    SELECT * FROM Orders o
    WHERE EXISTS (SELECT 1 FROM Users u WHERE u.UserId = o.UserId AND u.IsVIP = 1);
    SET STATISTICS TIME OFF;

  5. 使用 JOIN 操作

    -- 使用 JOIN 操作
    SET STATISTICS TIME ON;
    SELECT o.*
    FROM Orders o
    JOIN Users u ON u.UserId = o.UserId
    WHERE u.IsVIP = 1;
    SET STATISTICS TIME OFF;

  6. 使用表值参数

    -- 定义表值参数类型
    CREATE TYPE dbo.UserIdTableType AS TABLE (UserId INT);

    -- 创建存储过程
    CREATE PROCEDURE GetOrdersForVIPUsers
    @UserIds dbo.UserIdTableType READONLY
    AS
    BEGIN
    SELECT o.*
    FROM Orders o
    JOIN @UserIds u ON u.UserId = o.UserId;
    END;

    -- 调用存储过程
    DECLARE @UserIds dbo.UserIdTableType;
    INSERT INTO @UserIds (UserId)
    SELECT UserId FROM Users WHERE IsVIP = 1;

    EXEC GetOrdersForVIPUsers @UserIds;

解释

  1. 使用 EXISTS 和 JOIN:

    • EXISTS 子查询和 JOIN 操作通常比 IN 子查询更高效,特别是在子查询返回大量结果时。
    • EXISTS 子查询只检查是否存在符合条件的记录。
    • JOIN 操作通过连接表来获取符合条件的记录。
  2. 索引:

    • 确保 UserId 和 IsVIP 列上有合适的索引,以提高查询性能。
    • CREATE INDEX idx_users_isvip ON Users(IsVIP);
    • CREATE INDEX idx_orders_userid ON Orders(UserId);
  3. 表值参数:

    • 使用表值参数可以减少子查询的开销,特别是在子查询返回的结果集较大时。
  4. 实际性能测试:

    • 使用 SET STATISTICS TIME ON 和 SET STATISTICS TIME OFF 查看查询的执行时间。

运行结果示例

-- 使用 IN 子查询
Table 'Users'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server 执行时间:
CPU 时间 = 0 毫秒,经过时间 = 1 毫秒。

-- 使用 EXISTS 子查询
Table 'Users'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server 执行时间:
CPU 时间 = 0 毫秒,经过时间 = 1 毫秒。

-- 使用 JOIN 操作
Table 'Users'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server 执行时间:
CPU 时间 = 0 毫秒,经过时间 = 1 毫秒。

总结

  • 优化 IN 和 NOT IN 查询:

    • 使用 EXISTS 替代 IN:通常更高效。
    • 使用 JOIN 替代 IN:通常更高效。
    • 使用 NOT EXISTS 替代 NOT IN:避免 NULL 值问题,通常更高效。
    • 使用 LEFT JOIN 替代 NOT IN:避免 NULL 值问题,通常更高效。
    • 确保索引:为查询列创建合适的索引,提高查询性能。
    • 避免复杂查询:确保子查询中的操作尽可能简单,避免复杂的计算或排序操作。
posted @ 2025-01-05 12:35  似梦亦非梦  阅读(1440)  评论(0)    收藏  举报