SQL Server 致程式設計師(容易忽略的錯誤)

發表於2015-01-29

概述

因為每天需要稽核程式設計師釋出的SQL語句,所以收集了一些程式設計師的一些常見問題,還有一些平時收集的其它一些問題,這也是很多人容易忽視的問題,在以後收集到的問題會補充在文章末尾,歡迎關注,由於收集的問題很多是針對於生產資料,測試且資料量比較大,這裡就不把資料共享出來了,大家理解意思就行。

步驟

大小寫

大寫T-SQL 語言的所有關鍵字都使用大寫,規範要求。

使用“;”

使用“;”作為 Transact-SQL 語句終止符。雖然分號不是必需的,但使用它是一種好的習慣,對於合併操作MERGE語句的末尾就必須要加上“;”

(cte表表示式除外)

資料型別

避免使用ntext、text 和 image 資料型別,用 nvarchar(max)、varchar(max) 和 varbinary(max)替代

後續版本會取消ntext、text 和 image 該三種型別

查詢條件不要使用計算列

例如year(createdate)=2014,使用createdate>=’ 20140101’ and createdate<=’ 20141231’來取代。

—使用計算列查詢(走的是索引掃描)

—不使用計算列(走的是索引查詢)

對比兩個查詢顯然絕大部分情況下走索引查詢的查詢效能要高於走索引掃描,特別是查詢的資料庫不是非常大的情況下,索引查詢的消耗時間要遠遠少於索引掃描的時間,如果想詳細瞭解索引的體系結構可以檢視了我前面寫的幾篇關於聚集、非聚集、堆的索引體系機構的文章。

請參看:http://www.cnblogs.com/chenmh/p/3780221.html

請參看:http://www.cnblogs.com/chenmh/p/3782397.html

建表時欄位不允許為null

發現很多人在建表的時候不會注意這一點,在接下來的工作中當你需要查詢資料的時候你往往需要在WHERE條件中多加一個判斷條件IS NOT NULL,這樣的一個條件不僅僅增加了額外的開銷,而且對查詢的效能產生很大的影響,有可能就因為多了這個查詢條件導致你的查詢變的非常的慢;還有一個比較重要的問題就是允許為空的資料可能會導致你的查詢結果出現不準確的問題,接下來我們就舉個例子討論一下。


–增加整形欄位可以這樣寫
ALTER TABLE TABLE_NAME ADD COLUMN_NAME INT NOT NULL DEFAULT(0)

–增加字元型欄位可以這樣寫
ALTER TABLE TABLE_NAME ADD COLUMN_NAME NVARCHAR(50) NOT NULL DEFAULT(”)

分組統計時避免使用count(*)


實際情況customerid=3是沒有訂單的,數量應該是0,但是結果是1,count()裡面的欄位是左連線右邊的表欄位,如果你用的是主表欄位結果頁是錯誤的。

子查詢的表加上表別名

大家發現下面語句有沒有什麼問題,查詢結果是怎樣呢?


正確查詢結果下查詢出的結果是沒有customerid為3的值

為什麼結果會這樣呢?

大家仔細看應該會發現子查詢的orders表中沒有Customerid欄位,所以SQL取的是Customer表的Customerid值作為相關子查詢的匹配欄位。

所以我們應該給子查詢加上表別名,如果加上表別名,如果欄位錯誤的話會有錯誤標示

正確的寫法:

建立自增列時單獨再給自增列新增唯一約束

查詢時一定要制定欄位查詢

l  查詢時一定不能使用”*”來代替欄位來進行查詢,無論你查詢的欄位有多少個,就算欄位太多無法走索引也避免瞭解析”*”帶來的額外消耗。

l  查詢欄位值列出想要的欄位,避免出現多餘的欄位,欄位越多查詢開銷越大而且可能會因為多列出了某個欄位而引起查詢不走索引。

建立測試資料庫

建立索引

查詢測試

由於建的索引‘IX1_Customer’沒有包含ModifiedDate欄位,所以需要通過鍵查詢去聚集索引中獲取該欄位的值

由於查詢語句中沒有對ModifiedDate欄位進行查詢,所以只走索引查詢就可以查詢到需要的資料,所以建議在查詢語句中列出你需要的欄位而不是為了方便用*來查詢所有的欄位,如果真的

需要查詢所有的欄位也同樣建議把所有的欄位列出來取代‘*’。

使用儲存過程的好處

  1. 減少網路通訊量。呼叫一個行數不多的儲存過程與直接呼叫SQL語句的網路通訊量可能不會有很大的差別,可是如果儲存過程包含上百行SQL語句,那麼其效能絕對比一條一條的呼叫SQL語句要高得多。
  2. 執行速度更快。有兩個原因:首先,在儲存過程建立的時候,資料庫已經對其進行了一次解析和優化。其次,儲存過程一旦執行,在記憶體中就會保留一份這個儲存過程快取計劃,這樣下次再執行同樣的儲存過程時,可以從記憶體中直接呼叫。
  3. 更強的適應性:由於儲存過程對資料庫的訪問是通過儲存過程來進行的,因此資料庫開發人員可以在不改動儲存過程介面的情況下對資料庫進行任何改動,而這些改動不會對應用程式造成影響。
  4. 布式工作:應用程式和資料庫的編碼工作可以分別獨立進行,而不會相互壓制。
  5. 更好的封裝移植性。
  6. 安全性,它們可以防止某些型別的 SQL 插入攻擊。

判斷一條查詢是否有值

 理解TRUNCATE和DELETE的區別

在第四行記錄有一個lop_delete_rows,lcx_heap的刪除操作日誌記錄

TRUNCATE操作沒有記錄刪除日誌操作

主要的原因是因為TRUNCATE操作不會啟用觸發器,因為TRUNCATE操作不會記錄各行的日誌刪除操作,所以當你需要刪除一張表的資料時你需要考慮是否應該如有記錄日誌刪除操作,而不是根據個人的習慣來操作。

事務的理解

1.簡單的事務提交

2.TRY…CATCH

TRY…CATCH不會返回物件錯誤或者欄位錯誤等型別的錯誤

想詳細瞭解TRY…CATCH請參考http://www.cnblogs.com/chenmh/articles/4012506.html

3.開啟XACT_ABORT

所以我們應該根據自己的需求選擇正確的事務。

修改欄位NOT NULL的過程

 條件欄位的先後順序

你平時在寫T_SQL語句的時候WHERE條件後面的欄位的先後順序你有注意嗎?

如果這是你的寫的查詢語句

我現在根據你的查詢語句建立一條索引

分別執行三條查詢語句

執行計劃分別為

從上面三天查詢語句可以看出,只有第一條語句走的是索引查詢,另外兩條語句走的是索引掃描,而我們從欄位的名稱應該可以看的出OID欄位應該是該表的一個外來鍵欄位也是經常會被用作查詢的欄位。

接下來我們重新換一下索引順序

依然執行前面的三條查詢語句分析執行計劃

分析執行計劃前面兩條查詢語句都走的是索引查詢,第三條查詢的是索引掃描,而根據一般單獨用第三條查詢的業務應該不會常見,所以現在一條索引解決了兩個常用查詢的索引需求,避免了建兩條索引的必要(所以當你建索引的時候索引的順序很重要,一般把查詢最頻繁的欄位設第一個欄位,可以避免建多餘的索引)。

為什麼要把這個問題提出來呢,因為平時有遇到程式設計師在寫查詢語句的時候對於同一個查詢條件每次的寫法都不一樣,往往是根據自己想到哪個欄位就寫哪個欄位先,這樣的習慣往往是不好的,就好比上面的例子如果別人看到你的查詢條件建一個索引也是這樣寫的話往往一個表會出現很多多餘的索引(或許有人會說DBA建好索引的順序就好了,這裡把這個因素排除吧),像後面的那個索引就解決了兩個查詢的需求。

所以這裡我一般是這樣規定where條件的,對於經常用作查詢的欄位放在第一個位置(比如上面例子的OID),其它的欄位根據表的實際欄位順序排列,這樣往往你的查詢語句走索引的概率會更大。

 理解外連線

看到這結果是不是有點疑惑,我在連線條件裡面寫了TA.stats<>’1’,為什麼結果還會查詢出。

接下來我們換一種寫法吧!

接下來我就解釋一下原因:對於外連線,連線條件不會改變主表的資料,即不會刪減主表的資料

對於上面的查詢主表是orders,所以無論你在連線條件on裡面怎樣設定主表的條件都不影響主表資料的輸出,影響主表資料的輸出只在where條件裡,where條件影響最後資料的輸出。而對於附表Customer 的條件就應該寫在連線條件(on)裡而不是where條件裡,這裡說的是外連線(包括左連線和右連線)。

對於inner join就不存在這種情況,無論你的條件是寫在where後面還是on後面都是一樣的,但是還是建議寫在where後面。

謂詞型別要與欄位型別對齊

1.謂詞型別與欄位型別不一致

由於定義表的phone欄位型別是字元型,而上面的查詢條件phone寫成了整形,導致執行計劃走了索引掃描,且執行計劃select也有提示。

2.謂詞型別與欄位型別一致

第二種查詢phone謂詞型別與欄位型別一致,所以查詢走了索引查詢

在日常的語句編寫過程中需要注意這類問題,這將直接影響效能。

總結

後面收集到類似的問題會補充在文章的末尾,文章持續更新中….,歡迎關注討論。

相關文章