怎樣寫SQL語句可以提高資料庫的性能

来源:http://www.cnblogs.com/yzjey/archive/2017/12/11/8021940.html
-Advertisement-
Play Games

1、首先要搞明白什麼叫執行計劃? 執行計劃是資料庫根據SQL語句和相關表的統計信息作出的一個查詢方案,這個方案是由查詢優化器自動分析產生的,比如一條SQL語句如果用來從一個10萬條記錄的表中查1條記錄,那查詢優化器會選擇“索引查找”方式,如果該表進行了歸檔,當前只剩下5000條記錄了,那查詢優化器就 ...


  1、首先要搞明白什麼叫執行計劃?

  執行計劃是資料庫根據SQL語句和相關表的統計信息作出的一個查詢方案,這個方案是由查詢優化器自動分析產生的,比如一條SQL語句如果用來從一個10萬條記錄的表中查1條記錄,那查詢優化器會選擇“索引查找”方式,如果該表進行了歸檔,當前只剩下5000條記錄了,那查詢優化器就會改變方案,採用“全表掃描”方式。

  可見,執行計劃並不是固定的,它是“個性化的”。產生一個正確的“執行計劃”有兩點很重要:

  (1) SQL語句是否清晰地告訴查詢優化器它想乾什麼?

  (2) 查詢優化器得到的資料庫統計信息是否是最新的、正確的?

  2、統一SQL語句的寫法

  對於以下兩句SQL語句,程式員認為是相同的,資料庫查詢優化器認為是不同的。

  select * from dual

  Select * From dual

  其實就是大小寫不同,查詢分析器就認為是兩句不同的SQL語句,必須進行兩次解析。生成2個執行計劃。所以作為程式員,應該保證相同的查詢語句在任何地方都一致,多一個空格都不行!

  3、不要把SQL語句寫得太複雜

  我經常看到,從資料庫中捕捉到的一條SQL語句列印出來有2張A4紙這麼長。一般來說這麼複雜的語句通常都是有問題的。我拿著這2頁長的SQL語句去請教原作者,結果他說時間太長,他一時也看不懂了。可想而知,連原作者都有可能看糊塗的SQL語句,資料庫也一樣會看糊塗。

  一般,將一個SELECT語句的結果作為子集,然後從該子集中再進行查詢,這種一層嵌套語句還是比較常見的,但是根據經驗,超過3層嵌套,查詢優化器就很容易給出錯誤的執行計劃。因為它被繞暈了。像這種類似人工智慧的東西,終究比人的分辨力要差些,如果人都看暈了,我可以保證資料庫也會暈的。

  另外,執行計劃是可以被重用的,越簡單的SQL語句被重用的可能性越高。而複雜的SQL語句只要有一個字元發生變化就必須重新解析,然後再把這一大堆垃圾塞在記憶體里。可想而知,資料庫的效率會何等低下。

  4、使用“臨時表”暫存中間結果

  簡化SQL語句的重要方法就是採用臨時表暫存中間結果,但是,臨時表的好處遠遠不止這些,將臨時結果暫存在臨時表,後面的查詢就在TempDB中了,這可以避免程式中多次掃描主表,也大大減少了程式執行中“共用鎖”阻塞“更新鎖”,減少了阻塞,提高了併發性能。

  5、OLTP系統SQL語句必須採用綁定變數

  select * from orderheader where changetime > ‘2010-10-20 00:00:01’

  select * from orderheader where changetime > ‘2010-09-22 00:00:01’

  以上兩句語句,查詢優化器認為是不同的SQL語句,需要解析兩次。如果採用綁定變數

  select * from orderheader where changetime > @chgtime

  @chgtime變數可以傳入任何值,這樣大量的類似查詢可以重用該執行計划了,這可以大大降低資料庫解析SQL語句的負擔。一次解析,多次重用,是提高資料庫效率的原則。

  6、綁定變數窺測

  事物都存在兩面性,綁定變數對大多數OLTP處理是適用的,但是也有例外。比如在WHERE條件中的欄位是“傾斜欄位”的時候。

  “傾斜欄位”指該列中的絕大多數的值都是相同的,比如一張人口調查表,其中“民族”這列,90%以上都是漢族。那麼如果一個SQL語句要查詢30歲的漢族人口有多少,那“民族”這列必然要被放在WHERE條件中。這個時候如果採用綁定變數@nation會存在很大問題。

  試想如果@nation傳入的第一個值是“漢族”,那整個執行計劃必然會選擇表掃描。然後,第二個值傳入的是“布依族”,按理說“布依族”占的比例可能只有萬分之一,應該採用索引查找。但是,由於重用了第一次解析的“漢族”的那個執行計劃,那麼第二次也將採用表掃描方式。這個問題就是著名的“綁定變數窺測”,建議對於“傾斜欄位”不要採用綁定變數。

  7、只在必要的情況下才使用BEGIN TRAN

  SQL Server中一句SQL語句預設就是一個事務,在該語句執行完成後也是預設COMMIT的。其實,這就是BEGIN TRAN的一個最小化的形式,好比在每句語句開頭隱含了一個BEGIN TRAN,結束時隱含了一個COMMIT。

  有些情況下,我們需要顯式聲明BEGIN TRAN,比如做“插、刪、改”操作需要同時修改幾個表,要求要麼幾個表都修改成功,要麼都不成功。BEGIN TRAN可以起到這樣的作用,它可以把若幹SQL語句套在一起執行,最後再一起COMMIT。好處是保證了數據的一致性,但任何事情都不是完美無缺的。BEGIN TRAN付出的代價是在提交之前,所有SQL語句鎖住的資源都不能釋放,直到COMMIT掉。

  可見,如果BEGIN TRAN套住的SQL語句太多,那資料庫的性能就糟糕了。在該大事務提交之前,必然會阻塞別的語句,造成BLOCK很多。

  BEGIN TRAN使用的原則是,在保證數據一致性的前提下,BEGIN TRAN套住的SQL語句越少越好!有些情況下可以採用觸發器同步數據,不一定要用BEGIN TRAN。

  8、一些SQL查詢語句應加上NOLOCK

  在SQL語句中加NOLOCK是提高SQL Server併發性能的重要手段,在Oracle中並不需要這樣做,因為Oracle的結構更為合理,有UNDO表空間保存“數據前影”,該數據如果在修改中還未COMMIT,那麼你讀到的是它修改之前的副本,該副本放在UNDO表空間中。這樣,Oracle的讀、寫可以做到互不影響,這也是Oracle廣受稱贊的地方。SQL Server的讀、寫是會相互阻塞的,為了提高併發性能,對於一些查詢,可以加上NOLOCK,這樣讀的時候可以允許寫,但缺點是可能讀到未提交的臟數據。使用NOLOCK有3條原則。

  (1) 查詢的結果用於“插、刪、改”的不能加NOLOCK!

  (2) 查詢的表屬於頻繁發生頁分裂的,慎用NOLOCK!

  (3) 使用臨時表一樣可以保存“數據前影”,起到類似Oracle的UNDO表空間的功能,能採用臨時表提高併發性能的,不要用NOLOCK!

  9、聚集索引沒有建在表的順序欄位上,該表容易發生頁分裂

  比如訂單表,有訂單編號OrderID,也有客戶編號ContactID,那麼聚集索引應該加在哪個欄位上呢?對於該表,訂單編號是順序添加的,如果在OrderID上加聚集索引,新增的行都是添加在末尾,這樣不容易經常產生頁分裂。然而,由於大多數查詢都是根據客戶編號來查的,因此,將聚集索引加在ContactID上才有意義。而ContactID對於訂單表而言,並非順序欄位。

  比如“張三”的“contactid”是001,那麼“張三”的訂單信息必須都放在這張表的第一個數據頁上,如果今天“張三”新下了一個訂單,那該訂單信息不能放在表的最後一頁,而是第一頁!如果第一頁放滿了呢?很抱歉,該表所有數據都要往後移動為這條記錄騰地方。

  SQL Server的索引和Oracle的索引是不同的,SQL Server的聚集索引實際上是對錶按照聚集索引欄位的順序進行了排序,相當於Oracle的索引組織表。SQL Server的聚集索引就是表本身的一種組織形式,所以它的效率是非常高的。也正因為此,插入一條記錄,它的位置不是隨便放的,而是要按照順序放在該放的數據頁,如果那個數據頁沒有空間了,就引起了頁分裂。所以很顯然,聚集索引沒有建在表的順序欄位上,該表容易發生頁分裂。

  曾經碰到過一個情況,一位哥們的某張表重建索引後,插入的效率大幅下降了。估計情況大概是這樣的。該表的聚集索引可能沒有建在表的順序欄位上,該表經常被歸檔,所以該表的數據是以一種稀疏狀態存在的。比如張三下過20張訂單,而最近3個月的訂單隻有5張,歸檔策略是保留3個月數據,那麼張三過去的15張訂單已經被歸檔,留下15個空位,可以在INSERT發生時重新被利用。在這種情況下由於有空位可以利用,就不會發生頁分裂。但是查詢性能會比較低,因為查詢時必須掃描那些沒有數據的空位。

  重建聚集索引後情況改變了,因為重建聚集索引就是把表中的數據重新排列一遍,原來的空位沒有了,而頁的填充率又很高,插入數據經常要發生頁分裂,所以性能大幅下降。

  對於聚集索引沒有建在順序欄位上的表,是否要給與比較低的頁填充率?是否要避免重建聚集索引?是一個值得考慮的問題!

  10、加NOLOCK後查詢經常發生頁分裂的表,容易產生跳讀或重覆讀

  加NOLOCK後可以在“插、刪、改”的同時進行查詢,但是由於同時發生“插、刪、改”,在某些情況下,一旦該數據頁滿了,那麼頁分裂不可避免,而此時NOLOCK的查詢正在發生,比如在第100頁已經讀過的記錄,可能會因為頁分裂而分到第101頁,這有可能使得NOLOCK查詢在讀101頁時重覆讀到該條數據,產生“重覆讀”。同理,如果在100頁上的數據還沒被讀到就分到99頁去了,那NOLOCK查詢有可能會漏過該記錄,產生“跳讀”。

  上面提到的哥們,在加了NOLOCK後一些操作出現報錯,估計有可能因為NOLOCK查詢產生了重覆讀,2條相同的記錄去插入別的表,當然會發生主鍵衝突。

  11、使用LIKE進行模糊查詢時應註意

  有的時候會需要進行一些模糊查詢比如

  select * from contact where username like ‘%yue%’

  關鍵詞%yue%,由於yue前面用到了“%”,因此該查詢必然走全表掃描,除非必要,否則不要在關鍵詞前加%,

  12、數據類型的隱式轉換對查詢效率的影響

  SQL Server 2000的資料庫,我們的程式在提交SQL語句的時候,沒有使用強類型提交這個欄位的值,由SQL Server 2000自動轉換數據類型,會導致傳入的參數與主鍵欄位類型不一致,這個時候SQL Server 2000可能就會使用全表掃描。SQL Server 2005上沒有發現這種問題,但是還是應該註意一下。

  13、SQL Server表連接的三種方式

  (1) MERGE JOIN

  (2) NESTED LOOP JOIN

  (3) HASH JOIN

  SQL Server 2000只有一種JOIN方式——NESTED LOOP JOIN,如果A結果集較小,那就預設作為外表,A中每條記錄都要去B中掃描一遍,實際掃過的行數相當於A結果集行數×B結果集行數。所以如果兩個結果集都很大,那JOIN的結果很糟糕。

  SQL Server 2005新增了MERGE JOIN,如果A表和B表的連接欄位正好是聚集索引所在欄位,那麼表的順序已經排好,只要兩邊拼上去就行了,這種JOIN的開銷相當於A表的結果集行數加上B表的結果集行數,一個是加,一個是乘,可見MERGE JOIN的效果要比NESTED LOOP JOIN好多了。

  如果連接的欄位上沒有索引,那SQL Server 2000的效率是相當低的,而SQL Server 2005提供了HASH JOIN,相當於臨時給A,B表的結果集加上索引,因此SQL Server 2005的效率比SQL Server 2000有很大提高,我認為,這是一個重要的原因。

  總結一下,在表連接時要註意以下幾點:

  (1) 連接欄位儘量選擇聚集索引所在的欄位;

  (2) 仔細考慮WHERE條件,儘量減小A、B表的結果集;

  (3) 如果很多JOIN的連接欄位都缺少索引,而你還在用SQL Server 2000,趕緊升級吧!

  原文出處:http://www.cnblogs.com/shuzhengyi/archive/2011/02/12/1952481.html 
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原文地址: unity打開資源管理器主要通過C#自帶的OpenFileName腳本。 實現打開資源管理器並選擇文件的腳本。 ...
  • 使用C#讀取Chrome瀏覽器的本地書簽文件,當前文件在C盤下用戶文件夾\AppData\Local\Google\Chrome\User Data\Default\下的Bookmarks 打開這個文件我們發現就是一個json文本文件 為了方便讀取操作還是將json字元串轉換為對象這裡分析json結 ...
  • JWT是什麼 JWT全稱是Json Web Token,是一種用於雙方之間傳遞安全信息的簡潔的、URL安全的表述性聲明規範。JWT作為一個開放的標準( RFC 7519 ),定義了一種簡潔的,自包含的方法用於通信雙方之間以Json對象的形式安全的傳遞信息。因為數字簽名的存在,這些信息是可信的,JWT ...
  • 學習設計模式,一直沒有機會寫一個單例模式。今天在控制台應用程式,寫個簡單的例子,Hi與Hello。 public sealed class At { private static At instance = null; public static At Instance { get { if (in ...
  • 第一步:添加新項目 第二步:添加新的頁簽,註意,此頁簽是顯示到Outlook主界面的 第三步:添加自己想要的文本框以及按鈕 第四步:如果你想將此界面顯示到主界面的話,需要這樣設置:屬性裡面的RibbonType 設置為 Microsoft.Outlook.Explorer 運行後的結果: 獲取所有郵 ...
  • 演示產品源碼下載地址:http://www.jinhusns.com ...
  • delegate中文的意思為委托。在很久之前,Insus.NET有寫過一篇《用一個簡單的例子來演繹事件委托》http://www.cnblogs.com/insus/p/3732075.html 和另外一篇《涉及事件開發應用委托》http://www.cnblogs.com/insus/p/4148 ...
  • GetHashCode方法我的理解是做兩個對象的比較,每個對象,不管是值類型還是應用類型都提供這個基本函數,都可以去重寫它。GetHashTable通常用於HashTable、List<>、Dictionary等集合,如果兩個集合返回的哈希碼相同那麼這兩個集合不一定是同一個集合(也可能是兩個相等的集 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...