編寫SQL需要註意的細節 Checklist總結

来源:http://www.cnblogs.com/shouce/archive/2016/03/21/5300520.html
-Advertisement-
Play Games

本周技術研究部(TRD)的一名DBA 對我們編寫SQL時的一些問題,進行了彙報講演,以下是來自它的腳本,我在它講演的基礎上寫出了自己想表述的,以便於大家相互交流學習。


本周技術研究部(TRD)的一名DBA 對我們編寫SQL時的一些問題,進行了彙報講演,以下是來自它的腳本,我在它講演的基礎上寫出了自己想表述的,以便於大家相互交流學習。

/*
--註意:準備數據(可略過,非常耗時)
CREATE TABLE CHECK1_T1
(
    ID INT,
    C1 CHAR(8000)
)

CREATE TABLE CHECK1_T2
(
    ID INT,
    C1 CHAR(8000)
)

DECLARE @I INT
SET @I=1
WHILE @I<=10000
 BEGIN
    INSERT INTO CHECK1_T1 SELECT @I,'C1'
    INSERT INTO CHECK1_T2 SELECT 10000+@I,'C1'
    
    SET @I=@I+1
 END

CREATE TABLE CHECK2_T1
(
    ID INT,
    C1 CHAR(8000)
)

DECLARE @I INT
SET @I=1
WHILE @I<=10000
 BEGIN
    INSERT INTO CHECK2_T1 SELECT @I,'C1'
    
    SET @I=@I+1
 END

INSERT INTO CHECK2_T1 VALUES(10001,'C2')

INSERT INTO CHECK2_T1 VALUES(10002,'C1')

CREATE TABLE CHECK3_T1
(
    ID INT,
    C1 CHAR(7000)
)

CREATE TABLE CHECK3_T2
(
    ID INT,
    C1 CHAR(7000)
)

DECLARE @I INT
SET @I=1
WHILE @I<=20000
 BEGIN
    IF @I%2 =0 
        BEGIN
            INSERT INTO CHECK3_T1 SELECT @I,'C1'
        END
    ELSE
        BEGIN
            INSERT INTO CHECK3_T1 SELECT @I,'C2'
        END
    
    IF @I%100=0
        BEGIN
            INSERT INTO CHECK3_T2 SELECT @I,'C1'
            INSERT INTO CHECK3_T2 SELECT @I+50000,'C2'    
        END    
    SET @I=@I+1
 END


CREATE TABLE CHECK4_T1
(
    ID INT,
    C1 CHAR(500),
)

DECLARE @I INT
SET @I=1
WHILE @I<=500000
 BEGIN
    IF @I%100000 =0 
        BEGIN
            INSERT INTO CHECK4_T1 SELECT @I,'C2'
        END
    ELSE
        BEGIN
            INSERT INTO CHECK4_T1 SELECT @I,'C1'
        END
        
    SET @I=@I+1
 END
CREATE NONCLUSTERED INDEX NCIX_C1 ON CHECK4_T1(C1)

CREATE TABLE CHECK5_T1
(
    ID INT,
    C1 CHAR(10),
)


DECLARE @I INT
SET @I=1
WHILE @I<=10000
 BEGIN
    INSERT INTO CHECK5_T1 SELECT @I,'C1'
    IF @I%2=0
    BEGIN
        INSERT INTO CHECK5_T1 SELECT @I,'C1'
    END        
    SET @I=@I+1
 END


*/
--=====================================
--1、    Union all 代替 Union
 
DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE 

--測試一:(26s) 執行計劃:表掃描->排序->合併聯接
SELECT ID,C1 FROM CHECK1_T1  --1W條數據
UNION 
SELECT ID,C1 FROM CHECK1_T2  --1W條數據

--測試二: (4s)  執行計劃:表掃描->表掃描串聯
SELECT ID,C1 FROM CHECK1_T1  --1W條數據
UNION ALL
SELECT ID,C1 FROM CHECK1_T2  --1W條數據

--總結:測試一中的union 排序和去重合併是相當耗時的,如果不要此功能,大數據時最好加上ALL

--=====================================
--2、    Exists 代替 Count(*)
DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE 

----測試一:  (7s) 執行計劃:表掃描-> 流聚合-> 計算矢量
 DECLARE @COUNT INT
 SELECT @COUNT=COUNT(*) FROM CHECK2_T1 WHERE C1='C1'  --1W條數據
 IF @COUNT>0
    BEGIN   
        PRINT 'S'
    END
----測試二:  (0s) 執行計劃:常量掃描/表掃描-> 嵌套迴圈-> 計算標量
 IF EXISTS(SELECT 1 FROM CHECK2_T1 WHERE C1='C1')  --1W條數據
    BEGIN
        PRINT 'S'
    END
    
--總結:判斷是否存在,用Exist即可,沒必要用COUNT(*)將表的所有記錄統計出來,掃描一次
    
--=====================================
--3、    IN(Select COL1 From Table)的代替方式
DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE 

--測試一: (3s)執行計劃:表掃描 -> 哈希匹配 
SELECT ID,C1 FROM CHECK3_T2  --400行
WHERE ID IN (SELECT ID FROM CHECK3_T1 WHERE C1='C1')  --2W行

--測試二:(1s)執行計劃:表掃描-> 並行度 -> 點陣圖 -> 排序 -> 合併聯接 -> 並行度
SELECT A.ID,A.C1 FROM CHECK3_T2 A  
INNER  JOIN CHECK3_T1 B ON A.ID=B.ID WHERE B.C1='C1'  

--測試三:(3s)執行計劃:表掃描-> 哈希匹配 
SELECT A.ID,A.C1 FROM CHECK3_T2 A
WHERE EXISTS (SELECT 1 FROM CHECK3_T1 B WHERE B.ID=A.ID AND B.C1='C1')

--總結:能用INNER JOIN 儘量用它,SQL SERVER在查詢時會將關聯表進行優化

--=====================================
--4、    Not Exists 代替 Not In
--測試一:(8s) 執行計劃:表掃描-> 嵌套迴圈 -> 哈希匹配
SELECT ID,C1 FROM CHECK3_T1  --2W行
WHERE ID NOT IN (SELECT ID FROM CHECK3_T2 WHERE C1='C1')  --400行

--測試二:(4s) 執行計劃:表掃描-> 哈希匹配
SELECT A.ID,A.C1 FROM CHECK3_T1 A
WHERE NOT EXISTS (SELECT 1 FROM CHECK3_T2 B WHERE B.ID=A.ID AND B.C1='C1')

--總結:儘量不使用NOT IN ,因為會調用嵌套迴圈,建議使用NOT EXISTS代替NOT IN

--=====================================
--5、    避免在條件列上使用任何函數

DROP TABLE CHECK4_T1
 
CREATE NONCLUSTERED INDEX NCIX_C1 ON CHECK4_T1(C1) --加上非聚集索引

---測試一:(4s)執行計劃: 索引掃描
SELECT * FROM CHECK4_T1 WHERE RTRIM(C1)='C2'

---測試二:(0s)執行計劃: 索引查找
SELECT * FROM CHECK4_T1 WHERE C1='C2'

--總結:where條件里對索引欄位使用了函數,會使索引查找變成索引掃描,從而查詢效率大幅下降

--=====================================
--6、    用sp_executesql執行動態sql
 
DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE 
 
CREATE PROC UP_CHECK5_T1 (
  @ID INT
)
AS
    SET NOCOUNT ON

    DECLARE @count INT,
            @sql   NVARCHAR(4000)

    SET @sql = 'SELECT @count=count(*) FROM CHECK5_T1 WHERE ID = @ID'

    EXEC sp_executesql @sql,
                       N'@count INT OUTPUT, @ID int',
                       @count OUTPUT,
                       @ID

    PRINT @count 

    
CREATE PROC UP_CHECK5_T2 (
  @ID INT
)
AS
    SET NOCOUNT ON

    DECLARE @sql NVARCHAR(4000)

    SET @sql = 'DECLARE @count INT;SELECT @count=count(*) FROM CHECK5_T1 WHERE ID = ' + CAST(@ID AS VARCHAR(10)) + ';PRINT @count'

    EXEC(@sql) 


---測試一:瞬時
DECLARE @N INT
SET @N=1
WHILE @N<=1000
BEGIN
    EXEC UP_CHECK5_T1 @N
    SET @N=@N+1
END

---測試二:2s
DECLARE @N INT
SET @N=1
WHILE @N<=1000
BEGIN
    EXEC UP_CHECK5_T2 @N
    SET @N=@N+1
END

CREATE CLUSTERED INDEX CIX_ID ON CHECK5_T1(ID)

DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE 

--查看緩存計劃
SELECT a.size_in_bytes                                                                               '占用位元組數',
       total_elapsed_time / execution_count                                                          '平均時間',
       total_logical_reads / execution_count                                                         '邏輯讀',
       usecounts                                                                                     '重用次數',
       SUBSTRING(d.text, (statement_start_offset / 2) + 1, ((CASE statement_end_offset
                                                               WHEN -1 THEN DATALENGTH(text)
                                                               ELSE statement_end_offset
                                                             END - statement_start_offset) / 2) + 1) '語句'
FROM   sys.dm_exec_cached_plans a
       CROSS apply sys.dm_exec_query_plan(a.plan_handle) c,
       sys.dm_exec_query_stats b
       CROSS apply sys.dm_exec_sql_text(b.sql_handle) d
WHERE  a.plan_handle = b.plan_handle
ORDER  BY total_elapsed_time / execution_count DESC; 

--總結:通過執行下麵緩存計劃可以看出,第一種完全使用了緩存計劃,查詢達到了很好的效果;
--而第二種則將緩存計劃浪費了,導致緩存很快被占滿,這種做法是相當不可取的

--=====================================
--7、    Left Join 的替代法
--測試一 執行計劃:表掃描 -> 哈希匹配
SELECT A.ID,A.C1 FROM CHECK3_T1 A   --2W行
LEFT JOIN CHECK3_T2 B ON A.ID=B.ID WHERE B.C1='C1'  --400行

--測試二 執行計劃:表掃描 -> 哈希匹配
SELECT A.ID,A.C1 FROM CHECK3_T1 A 
RIGHT JOIN CHECK3_T2 B ON A.ID=B.ID WHERE a.C1='C1'

--測試三 執行計劃:表掃描 -> 哈希匹配
SELECT A.ID,A.C1 FROM CHECK3_T1 A 
INNER JOIN CHECK3_T2 B ON A.ID=B.ID WHERE B.C1='C1'

--總結:三條語句,在執行計划上完全一樣,都是走的INNER JOIN的計劃,
--因為測試一和測試二中,WHERE語句都包含了LEFT 和RIGHT表的欄位,SQLSERVER若發現只要有這個表的欄位,則會自動按照INNER JOIN進行處理

--補充測試:(1s)執行計劃:表掃描-> 並行度 -> 點陣圖 -> 排序 -> 合併聯接 -> 並行度
SELECT A.ID,A.C1 FROM CHECK3_T2 A  --400行
INNER  JOIN CHECK3_T1 B ON A.ID=B.ID WHERE A.C1='C1'  --2W行
--總結:這裡有一個比較有趣的地方,若主表和關聯表數據差別很大時,走的執行計划走的另一條路

--=====================================
--8、    ON(a.id=b.id AND a.tag=3)
--測試一
SELECT A.ID,A.C1 FROM CHECK3_T1 A 
INNER JOIN CHECK3_T2 B ON A.ID=B.ID AND A.C1='C1'

--測試二
SELECT A.ID,A.C1 FROM CHECK3_T1 A 
INNER JOIN CHECK3_T2 B ON A.ID=B.ID WHERE A.C1='C1'

--總結:內連接:無論是左表和右表的篩選條件都可以放到WHERE子句中

--測試一
SELECT A.ID,A.C1,B.C1 FROM CHECK3_T1 A 
LEFT JOIN CHECK3_T2 B ON A.ID=B.ID AND B.C1='C1'

--測試二
SELECT A.ID,A.C1,B.C1 FROM CHECK3_T1 A 
LEFT JOIN CHECK3_T2 B ON A.ID=B.ID WHERE B.C1='C1'

--總結:左外連接:當右表中的過濾條件放入ON子句後和WHERE子句後的結果不一樣

--=====================================
--9、   賦值給變數,加Top 1
--測試一:(3s) 執行計劃:表掃描
DECLARE @ID INT
SELECT @ID=ID FROM CHECK1_T1 WHERE C1='C1'
SELECT @ID 

--測試二:(0s)執行計劃:表掃描-> 前幾行
DECLARE @ID INT
SELECT TOP 1 @ID=ID FROM CHECK1_T1 WHERE C1='C1'
SELECT @ID

--總結:給變數賦值最好都加上TOP 1,一從查詢效率上增強,二為了準確性,若表CHECK1_T1有多個值,則會取最後一條記錄賦給@ID

--=====================================
--10、   考慮是否適合用CASE語句
DECLARE @S INT=1
SELECT * FROM CHECK5_T1
WHERE C1=(CASE @S WHEN 1 THEN C1 ELSE 'C2' END)

SELECT * FROM CHECK5_T1
WHERE @S=1 OR C1='C2'


/*--=====================================
12、檢查語句是否需要Distinct.  執行計劃:表掃描-> 哈希匹配-> 並行度-> 排序
select distinct c1 from CHECK3_T1 

13、禁用Select *,指定具體列名
select c1 from CHECK4_T1
select * from CHECK4_T1

14、Insert into Table(*),指定具體的列名

15、Isnull,沒有必要的時候不要對欄位使用isnull,同樣會產生無法有效利用索引的問題,
    和避免在篩選列上使用函數同樣的原理。
    
16、嵌套子查詢,加上查詢條件,確保子查詢的結果集最小
--=====================================*/

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近開始學習hadoop,邊學邊記錄學習中遇到的問題。因為是心得不是教程,所以閱讀的人應具備基本的Linux知識和vim使用,學習過程不寫具體工具的使用,請自行百度。 基於centos7和jdk1.7,hadoop2.4.1的環境搭建(建議64位機子用我提供的2.3.0搭建,我用2.4.1遇到了些問
  • IFNULL(expr1,expr2) 如果expr1不是NULL,IFNULL()返回expr1,否則它返回expr2。IFNULL()返回一個數字或字元串值 具體用法如:現有學生表(tbl_student)和分數表(score),查詢學生表的所有欄位和學生相對於的英語成績(english_sco
  • 1. Question description: if you are setting the oracle client to add a local network service, you may see the dialogue that show you the message (orac
  • 線下的測試機器老是報錯,從errorlog里看到大量的4014錯誤 於是谷歌了一下,發現了一篇文章:https://www.mssqltips.com/sqlservertip/3538/fixing-sql-server-fatal-error-4014/ 大家知道現在的網卡內置功能都比較厲害,有
  • mysql添加欄位: 刪除欄位:
  • 在保密你的伺服器和數據,防備當前複雜的攻擊,SQL Server有你需要的一切。但在你能有效使用這些安全功能前,你需要理解你面對的威脅和一些基本的安全概念。這篇文章提供了基礎,因此你可以對SQL Server里的安全功能充分利用,不用在面對特定威脅,不能保護你數據的功能上浪費時間。 通常來說,你通過
  • 1.Question describe when you use account scott/tiger connect to oracle, you will see "the user account is locked", because you did't unlock the accoun
  • 引語:mysql作為資料庫的一大主力軍,到處存在於我們各種系統中,相信大家都不陌生!但是,你知道你能用不代表你知道細節,那我們就來盤點盤點其中一些我們平時不太註意的地方,一來為了有趣,二來為了不讓自己踩坑。 聲明:要想知道細節,那就去閱讀源碼,我實在沒那本事,只能從片面上來說一些事! 1. 不區分大
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...