SQLSERVR語句 in和exists哪個效率高本人測試證明
最近很多人讨論in和exists哪個效率高,今天就自己測試一下
我使用的是客戶的資料庫GPOSDB(已經有資料)
環境:SQLSERVER2005 Windows7
我的測試條件:兩個表作連接配接根據VC_IC_CardNO字段,查出CT_InhouseCard表中的VC_IC_CardNO(卡号)在CT_FuelingData表中存在的記錄
前提:某些人可能在SQL語句中有多個in,或者多個exists,這些情況很難測試效率的,因為大家的條件都不相同
例如下面兩個SQL語句
1 SELECT OrderNo, SiteCode, AreaCode
2 FROM SchedulingProgram
3 WHERE AreaCode IN ( 'P', 'M' ) AND SiteCode IN ( SELECT SiteCode
4 FROM EnvBasicInfo
5 WHERE cityiD = 31 ) AND OrderNo NOT IN (
6 SELECT OrderNo
7 FROM KK_DeliveryinfoTmp )
上面SQL語句IN裡面有IN和NOT IN
1 SELECT OrderNo, SiteCode, AreaCode
2 FROM SchedulingProgram
3 WHERE ( AreaCode IN ( 'P', 'M' ) AND SiteCode IN ( SELECT SiteCode
4 FROM EnvBasicInfo
5 WHERE cityiD = 31 )
6 ) AND NOT EXISTS ( SELECT OrderNo
7 FROM KK_DeliveryinfoTmp
8 WHERE KK_DeliveryinfoTmp.OrderNo = SchedulingProgram.OrderNo )
上面的SQL語句IN裡面又有NOT EXISTS
這樣的情況很難測試同等條件下IN語句和EXISTS語句的效率
還有一個非SARG運算符
在《SQLSERVER企業級平台管理實踐》的第424頁裡提到:
SQLSERVER對篩選條件(search argument/SARG)的寫法有一定的建議
對于不使用SARG運算符的表達式,索引是沒有用的,SQLSERVER對它們很難使用比較優化的做法。非SARG運算符包括
NOT、<>、NOT EXISTS、NOT IN、NOT LIKE和内部函數,例如:Convert、Upper等
是以當您的表中有索引并且SQL語句包含非SARG運算符,那麼當測試SQL語句的執行時間的時候肯定相差很大,
因為有些SQL語句走索引,有些SQL語句不走索引
建表腳本
注意:兩個表中都有索引!!
CT_FuelingData表
1 USE [GPOSDB]
2 GO
3 /****** 對象: Table [dbo].[CT_FuelingData] 腳本日期: 08/24/2013 11:00:34 ******/
4 SET ANSI_NULLS ON
5 GO
6 SET QUOTED_IDENTIFIER ON
7 GO
8 SET ANSI_PADDING ON
9 GO
10 CREATE TABLE [dbo].[CT_FuelingData](
11 [RecordNO] [int] IDENTITY(1,1) NOT NULL,
12 [I_FD_StationNo] [int] NOT NULL,
13 [VC_FD_No] [varchar](50) NOT NULL,
14 [VC_FD_Cardno] [varchar](50) NOT NULL,
15 [I_FD_CardStatus] [int] NULL,
16 [LI_FD_CTC] [bigint] NOT NULL,
17 [I_FD_TypeCode] [int] NULL,
18 [I_FD_PumpID] [int] NOT NULL,
19 [VC_FD_OilType] [varchar](50) NULL,
20 [DE_FD_Volume] [decimal](18, 2) NULL,
21 [DE_FD_Price] [decimal](18, 2) NULL,
22 [DE_FD_Amount] [decimal](18, 2) NULL,
23 [I_FD_Point] [decimal](10, 2) NULL,
24 [D_FD_DateTime] [datetime] NOT NULL,
25 [VC_FD_GroupNo] [varchar](50) NULL,
26 [D_FD_GroupDate] [datetime] NULL,
27 [DE_FD_CardAmount] [decimal](18, 2) NULL,
28 [DE_FD_VolumeTotals] [decimal](18, 2) NULL,
29 [DE_FD_AmountTotals] [decimal](18, 2) NULL,
30 [I_FD_ISSend] [int] NULL,
31 [VC_FD_CardMoneyauthFile] [varchar](50) NULL,
32 [D_Month] [datetime] NULL,
33 CONSTRAINT [PK_CT_FuelingData_1] PRIMARY KEY CLUSTERED
34 (
35 [VC_FD_No] ASC
36 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
37 ) ON [PRIMARY]
38
39 GO
40 SET ANSI_PADDING OFF
CT_InhouseCard表
1 USE [GPOSDB]
2 GO
3 /****** 對象: Table [dbo].[CT_InhouseCard] 腳本日期: 08/24/2013 10:59:58 ******/
4 SET ANSI_NULLS ON
5 GO
6 SET QUOTED_IDENTIFIER ON
7 GO
8 SET ANSI_PADDING ON
9 GO
10 CREATE TABLE [dbo].[CT_InhouseCard](
11 [RecordNO] [int] IDENTITY(1,1) NOT NULL,
12 [VC_IC_CardNO] [varchar](50) NOT NULL,
13 [VC_IC_PhysicalNO] [varchar](50) NULL,
14 [I_IC_CardType] [int] NULL,
15 [VC_IC_UserName] [varchar](50) NULL,
16 [VC_IC_JobNO] [varchar](50) NULL,
17 [VC_IC_UserID] [varchar](50) NULL,
18 [VC_IC_Password] [varchar](50) NULL,
19 [DE_IC_CardAmount] [decimal](18, 2) NULL,
20 [DE_IC_AppendAmount] [decimal](18, 2) NULL,
21 [DE_IC_ConsumerAmount] [decimal](18, 2) NULL,
22 [I_IC_ISLost] [int] NULL,
23 [D_IC_UsedDateTime] [datetime] NULL,
24 [D_IC_UselifeDateTime] [datetime] NULL,
25 [I_IC_IssueStationNO] [int] NULL,
26 [VC_IC_IssuerNO] [varchar](50) NULL,
27 [D_IC_IssueDateTime] [datetime] NULL,
28 [D_IC_LastUpdateDateTime] [datetime] NULL,
29 [I_IC_CardStatus] [int] NULL,
30 [VC_IC_Remark] [varchar](256) NULL,
31 CONSTRAINT [PK_CT_InhouseCard] PRIMARY KEY CLUSTERED
32 (
33 [VC_IC_CardNO] ASC
34 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
35 ) ON [PRIMARY]
36
37 GO
38 SET ANSI_PADDING OFF
測試腳本
因為這個是客戶的資料庫,本來裡面已經有資料了,是以在測試之前先更新兩個表的統計資訊,以做到公正
1 USE [GPOSDB]
2 GO
3 UPDATE STATISTICS CT_FuelingData
4 UPDATE STATISTICS CT_InhouseCard
5 GO
IN語句
1 USE [GPOSDB]
2 GO
3 DBCC DROPCLEANBUFFERS
4 GO
5 DBCC FREEPROCCACHE
6 GO
7 SET STATISTICS IO ON
8 GO
9 SET STATISTICS TIME ON
10 GO
11 SET STATISTICS PROFILE ON
12 GO
13 SELECT * FROM [dbo].[CT_FuelingData] WHERE [VC_FD_Cardno] IN (SELECT [VC_IC_CardNO] FROM [dbo].[CT_InhouseCard])
EXISTS語句
1 USE [GPOSDB]
2 GO
3 DBCC DROPCLEANBUFFERS
4 GO
5 DBCC FREEPROCCACHE
6 GO
7 SET STATISTICS IO ON
8 GO
9 SET STATISTICS TIME ON
10 GO
11 SET STATISTICS PROFILE ON
12 GO
13 SELECT *
14 FROM [dbo].[CT_FuelingData]
15 WHERE EXISTS ( SELECT [VC_IC_CardNO]
16 FROM [dbo].[CT_InhouseCard]
17 WHERE [dbo].[CT_FuelingData].[VC_FD_Cardno] = [dbo].[CT_InhouseCard].[VC_IC_CardNO] )
測試結果
1 SQL Server 執行時間:
2 CPU 時間 = 0 毫秒,占用時間 = 2 毫秒。
3 SQL Server 分析和編譯時間:
4 CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
5
6 SQL Server 執行時間:
7 CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
8 SQL Server 分析和編譯時間:
9 CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
10
11 SQL Server 執行時間:
12 CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
13 SQL Server 分析和編譯時間:
14 CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
15
16 SQL Server 執行時間:
17 CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
18 SQL Server 分析和編譯時間:
19 CPU 時間 = 31 毫秒,占用時間 = 67 毫秒。
20
21 (167 行受影響)
22 表 'Worktable'。掃描計數 0,邏輯讀取 0 次,實體讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 實體讀取 0 次,lob 預讀 0 次。
23 表 'CT_FuelingData'。掃描計數 1,邏輯讀取 31 次,實體讀取 1 次,預讀 64 次,lob 邏輯讀取 0 次,lob 實體讀取 0 次,lob 預讀 0 次。
24 表 'CT_InhouseCard'。掃描計數 1,邏輯讀取 2 次,實體讀取 1 次,預讀 0 次,lob 邏輯讀取 0 次,lob 實體讀取 0 次,lob 預讀 0 次。
25
26 (4 行受影響)
27
28 SQL Server 執行時間:
29 CPU 時間 = 16 毫秒,占用時間 = 192 毫秒。
1 SQL Server 分析和編譯時間:
2 CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
3
4 SQL Server 執行時間:
5 CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
6 SQL Server 分析和編譯時間:
7 CPU 時間 = 0 毫秒,占用時間 = 34 毫秒。
8
9 (167 行受影響)
10 表 'Worktable'。掃描計數 0,邏輯讀取 0 次,實體讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 實體讀取 0 次,lob 預讀 0 次。
11 表 'CT_FuelingData'。掃描計數 1,邏輯讀取 31 次,實體讀取 1 次,預讀 64 次,lob 邏輯讀取 0 次,lob 實體讀取 0 次,lob 預讀 0 次。
12 表 'CT_InhouseCard'。掃描計數 1,邏輯讀取 2 次,實體讀取 1 次,預讀 0 次,lob 邏輯讀取 0 次,lob 實體讀取 0 次,lob 預讀 0 次。
13
14 (4 行受影響)
15
16 SQL Server 執行時間:
17 CPU 時間 = 0 毫秒,占用時間 = 163 毫秒。
大家可以看到除了執行時間有一點差别,IO是一樣的
因為資料量比較大,是以兩個查詢都用到了Worktable(中間表)來存儲中間結果
IN語句的執行計劃
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicGcq5yY5YjZ0UGO2ADMyUGNlhTY4QWY0MmM3kjY5EGN4czYk1yN1EDNxEDNy8CX4AzMxAjMvwVO1EzN1IzLcd2bsJ2Lc12bj5ycn9Gbi52YuAzcldWYtl2Lc9CX6MHc0RHaiojIsJye.jpg)
EXISTS語句的執行計劃
從執行計劃可以看到兩個SQL語句的開銷都是一樣的,而且大家都使用了右半連接配接(Right Semi Join)
至于什麼是半連接配接(Semi-join)大家可以看一下這篇文章:SQL Join的一些總結
總結
從上面實際的執行來比較,,IN語句和EXISTS語句基本上都是一樣的效率
如有不對的地方,歡迎大家來拍磚o(∩_∩)o