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

news/2025/1/7 9:15:32/文章来源:https://www.cnblogs.com/chenshibao/p/18653249
  1. 使用 EXISTS 替代 IN
    EXISTS 子查询通常比 IN 子查询更高效,特别是在子查询返回大量结果时。
  • 示例:

      -- 使用 INSELECT * FROM OrdersWHERE UserId IN (SELECT UserId FROM Users WHERE IsVIP = 1);-- 使用 EXISTSSELECT * FROM Orders oWHERE EXISTS (SELECT 1 FROM Users u WHERE u.UserId = o.UserId AND u.IsVIP = 1);
    
  1. 使用 JOIN 替代 IN
    JOIN 操作通常比 IN 子查询更高效,特别是在复杂的查询中。
  • 示例:

      -- 使用 INSELECT * FROM OrdersWHERE UserId IN (SELECT UserId FROM Users WHERE IsVIP = 1);-- 使用 JOINSELECT o.*FROM Orders oJOIN Users u ON u.UserId = o.UserIdWHERE u.IsVIP = 1;
    
  1. 使用 NOT EXISTS 替代 NOT IN
    NOT EXISTS 子查询通常比 NOT IN 子查询更高效,特别是在子查询返回大量结果或包含 NULL 值时。
  • 示例

      -- 使用 NOT INSELECT * FROM OrdersWHERE UserId NOT IN (SELECT UserId FROM Users WHERE IsVIP = 1);-- 使用 NOT EXISTSSELECT * FROM Orders oWHERE 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 INSELECT * FROM OrdersWHERE UserId NOT IN (SELECT UserId FROM Users WHERE IsVIP = 1);-- 使用 LEFT JOINSELECT o.*FROM Orders oLEFT JOIN Users u ON u.UserId = o.UserId AND u.IsVIP = 1WHERE 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 OrdersWHERE OrderDate IN (SELECT OrderDate FROM Orders WHERE UserId = 1);-- 使用 JOIN 或 EXISTSSELECT o.*FROM Orders oJOIN Orders o2 ON o.OrderDate = o2.OrderDateWHERE o2.UserId = 1;
    
  1. 处理 NULL 值**
    NOT IN 子查询在子查询结果包含 NULL 值时会返回空结果集。可以使用 NOT EXISTS 或 LEFT JOIN 来避免这个问题。
  • 示例

      -- 使用 NOT INSELECT * FROM OrdersWHERE UserId NOT IN (SELECT UserId FROM Users WHERE IsVIP IS NULL OR IsVIP = 1);-- 使用 NOT EXISTSSELECT * FROM Orders oWHERE 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 OrdersWHERE UserId IN (SELECT UserId FROM Users WHERE IsVIP = 1);SET STATISTICS TIME OFF;
    
  4. 使用 EXISTS 子查询

     -- 使用 EXISTS 子查询SET STATISTICS TIME ON;SELECT * FROM Orders oWHERE 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 oJOIN Users u ON u.UserId = o.UserIdWHERE 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 OrdersWHERE UserId IN (SELECT UserId FROM Users WHERE IsVIP = 1);SET STATISTICS TIME OFF;
    
  4. 使用 EXISTS 子查询

     -- 使用 EXISTS 子查询SET STATISTICS TIME ON;SELECT * FROM Orders oWHERE 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 oJOIN Users u ON u.UserId = o.UserIdWHERE u.IsVIP = 1;SET STATISTICS TIME OFF;
    
  6. 使用表值参数

     -- 定义表值参数类型CREATE TYPE dbo.UserIdTableType AS TABLE (UserId INT);-- 创建存储过程CREATE PROCEDURE GetOrdersForVIPUsers@UserIds dbo.UserIdTableType READONLYASBEGINSELECT o.*FROM Orders oJOIN @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 值问题,通常更高效。
    • 确保索引:为查询列创建合适的索引,提高查询性能。
    • 避免复杂查询:确保子查询中的操作尽可能简单,避免复杂的计算或排序操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/864370.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PCIe扫盲——Base Limit寄存器详解

上一篇文章介绍了Type0型配置空间Header中的BAR的作用和用法,但是PCIe中的桥设备(Switch和Root中的P2P)又是如何判断某一请求(Request)是否属于自己或者自己的分支下的设备的呢?(定义范围) 这实际上是通过Type1型配置空间Header中的Base和Limit寄存器来实现的,这篇文章…

用.NET X64 Native AOT编写的操作系统

https://blog.csdn.net/sd7o95o/article/details/1331911601.前言很多人想学习下.Net前沿技术和核心技术,这里推荐一个适合大家学习的案例,用.NET X64 Native AOT编写一个操作系统。 2.概述MOOS(To Make My Own Operating System Project)是一个采用.NET x64 Native AOT技术…

Win32汇编学习笔记05

定位关键点3种方法: 过程函数 api 字符串 但是不确定用要哪一种方法,可以3种方法都用一下,因为在不同的程序,实用的方法是不一样的 窗口程序看控件信息 1.通过OD去看还可以用 spy ++ 查看还可以用vs2019 ,打开软件,找到对应控件直接看属性一般拿句柄没用,因为按钮是响应 WM_COM…

2025年,勇敢探索,才能突破困境

新年第一篇文章,不聊技术,聊聊今年的计划,以及未来的发展趋势。 在24年的第一篇文章中,我用“苟住求活”这个词形容了我当时的判断,如今回过头再看2024年,大家都过的很挣扎。经济环境进一步恶化,就业机会越发的稀少,降本增效降薪裁员,是去年很多人的真实经历。 今年的…

Java集合 —— LinkedList详解(源码)

在学习LinkedList之前先来了解一下链表链表 概念链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序通过链表中的指针链接次序实现的图中的1、2、3、4、5都是结构体,称为结点;结构体包含所存的数据和下一结点的地址。顺序表中的地址是连续的,而链表中的…

菜单

扫雷菜单准备写一个基于C的扫雷游戏,这是第一篇,内容:扫雷的主菜单 思路 显示菜单->用户选择->判断用户选项 实现 显示菜单 首先在main函数内显示菜单,菜单显示部分实现在 MainMenu 函数内 int main(void) {while (true){int iChoose = -1;MainMenu();//加载菜单} } …

如何解决数据库扩容后宝塔面板显示旧容量的问题?

当您完成数据库扩容后,发现宝塔面板仍然显示旧的容量,这可能是由于系统未能及时刷新磁盘信息或配置文件未更新所致。为了确保宝塔面板正确显示新的磁盘容量,请参考以下详细步骤进行处理:确认磁盘扩容成功:首先,确保磁盘扩容操作已经成功完成。可以通过命令行工具如lsblk或…

20241312《计算机基础与程序设计》课程总结

20241312《计算机基础与程序设计》课程总结 第一周作业: 简要内容:①课程概论②工业革命与浪潮之巅③信息与信息安全④计算机系统概论⑤计算机安全⑥计算的限制⑦计算思维 二维码: 第三周作业: 简要内容:①数字分类与计数法 ②位置计数法③进制转换④模拟数据与数字数据⑤…

云服务器根目录扩容后磁盘空间未增加

问题描述: 我已经升级了云服务器的配置,但根目录的空间大小并未增加。请帮我检查并解决这个问题。 详情回答: 您好!感谢您使用我们的云服务器服务。根据您的描述,您已经升级了云服务器的配置,但根目录的空间大小并未增加。这种情况通常是由于磁盘挂载或分区设置不当引起的…

如何解决BT面板无法登录的问题?

当您遇到无法登录BT面板的情况时,可能是由多种原因引起的。以下是一些常见的排查步骤和解决方案,帮助您快速解决问题:检查用户名和密码:首先,确认您使用的用户名和密码是否正确。默认情况下,BT面板的登录用户名通常是 admin 或 cp,密码则是您在安装时设置的初始密码。如…

响应式设计进阶 - 修改网站手机版模板的实用方法与技巧

随着移动设备使用的增加,确保网站在小屏幕设备上有良好的用户体验变得至关重要。以下是关于如何修改网站手机版模板的一些基本指导和高级技巧:理解响应式设计理念响应式设计是一种使网页能够在不同设备上自适应的技术。它通过使用流式网格布局、弹性图片和媒体查询等手段,确…

Mingw64下载及各版本区别

地址:https://github.com/niXman/mingw-builds-binaries/releases 一般来说下这个就行