<p>sql語句能進行資料的操縱以及資料的定義,能夠為使用者帶來極大的方便。本文會提到 52 個 SQL 語句效能最佳化策略。有需要的小夥伴,建議收藏。 </p>
<p><img src="https://img.php.cn/upload/article/202103/24/2021032416152970237.jpg" style="max-width:90%" style="max-width:90%" border="0" vspace="0" title="" alt="" style="width: 800px; height: 320px;"></p>
<p><span style="font-size: 18px;"><strong>SQL 語句效能最佳化策略</strong></span></p>
<p>1、對查詢進行最佳化,應盡量避免全表掃描,首先應考慮在<code>WHERE</code> 及<code> ORDER BY</code> 涉及的列上建立索引。 </p>
<p>2、應盡量避免在 WHERE 子句中對欄位進行 NULL 值判斷,建立表格時NULL 是預設值,但大多時候應該使用<code> NOT NULL</code>,或使用一個特殊的值,如<code>0 </code>,<code>-1</code>作為預設值。 <br></p>
<p>3、應盡量避免在 WHERE 子句中使用 != 或 <> 運算子。 MySQL 只有對下列運算子使用索引:<code><</code>,<code><=</code>,<code>=</code>,<code>></code>,<code>> ;=</code>,<code>BETWEEN</code>,<code>IN</code>,以及某些時候的<code> LIKE</code>。 <br></p>
<p>4、應盡量避免在 WHERE 子句中使用 OR 來連接條件,否則將導致引擎放棄使用索引而進行全表掃描,可以使用 UNION 合併查詢。 <br></p>
<p>5、IN 和 NOT IN 也要慎用,否則會導致全表掃描。對於連續的數值,能用 BETWEEN 就不要用 IN。 <br></p>
<p>6、下面的查詢也會導致全表掃描:</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:sql;toolbar:false">select id from t where name like‘%abc%’//用到索引</pre><div class="contentsignin">登入後複製</div></div><p>或</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:sql;toolbar:false">select id from t where name like‘%abc’//若要提高效率,可以考虑全文检索</pre><div class="contentsignin">登入後複製</div></div><p>7、如果在 WHERE 子句中使用參數,也會導致全表掃描。 </p><p>8、應盡量避免在 WHERE 子句中對欄位進行表達式運算和函數運算。 <br/></p><p>9、很多時候用 <code>EXISTS </code>取代 IN 是個好的選擇。 <br/></p><p>10、索引固然可以提高對應的 SELECT 的效率,但同時也降低了 <code>INSERT </code>及 <code>UPDATE </code>。因為 INSERT 或 UPDATE 時有可能會重建索引,一個表的索引數最好不要超過 6 個。 <br/></p><p>11、應盡可能的避免更新<code>clustered </code>索引資料列, 因為clustered 索引資料列的順序就是表記錄的實體儲存順序,一旦該列值改變將導致整個表記錄的順序的調整,會耗費相當大的資源。 <br/></p><p>12、盡量使用數字型,若只含數值資訊的欄位盡量不要設計為字元型,這會降低查詢和連接的效能,並會增加儲存。 <br/></p><p>13、盡可能的使用<code> varchar</code>, <code>nvarchar</code> 取代<code> char</code>, <code>nchar</code>。因為首先長字段存儲空間小,可以節省存儲空間,對於查詢來說,在一個相對較小的字段內搜尋效率顯然要高些。 <br/></p><p>14、最好不要使用返回所有:<code>select from t</code> ,用特定的字段列表代替 “*”,不要返回用不到的任何字段。 <br/></p><p>15、盡量避免向客戶端傳回大資料量,若資料量過大,應考慮對應需求是否合理。 <br/></p><p>16、使用表的別名(Alias):當在SQL 語句中連接多個表時,請使用表的別名並將別名前綴於每個<code> Column</code> 上。這樣一來,就可以減少解析時間並減少那些由 Column 歧義引起的語法錯誤。 <br/></p><p>17、使用「暫存表」暫存中間結果 。 <br/></p><p>簡化 SQL 語句的重要方法就是採用暫存表暫存中間結果。將臨時結果暫存在臨時表,後面的查詢就在<code>tempdb </code>中了,這可以避免程式中多次掃描主表,也大大減少了程式執行中“共享鎖”阻塞“更新鎖” ,減少了阻塞,提高了並發性能。 <br/></p><p>18、一些 SQL 查詢語句應加上 <code>nolock</code>,讀取、寫入是會互相阻塞的,為了提高並發效能。對於一些查詢,可以加上 nolock,這樣讀的時候可以允許寫,但缺點是可能讀到未提交的髒資料。 <br/></p><p>使用 nolock 有3個原則:</p><ul style="list-style-type: disc;"><li><p>查詢的結果用於「插、刪除、改」的不能加 nolock;<br/>
</p></li><li><p>查詢的表屬於頻繁發生頁分裂的,慎用nolock ;</p></li><li><p>使用臨時表一樣可以保存“資料前影”,起到類似Oracle 的undo 表空間的功能,能採用臨時表提高並發性能的,不要用nolock。 </p></li></ul><p>19、常見的簡化規則如下:不要有超過 5 個以上的表格連接(<code>JOIN</code>),考慮使用臨時表或表格變數存放中間結果。少用子查詢,視圖嵌套不要過深,一般視圖嵌套不要超過 2 個為宜。 </p><p>20、將需要查詢的結果預先計算好放在表中,查詢的時候再<code>Select</code>#。 </p><p>21、用 OR 字句可以分解成多个查询,并且通过 <code>UNION </code>连接多个查询。他们的速度与是否使用索引有关,如果查询需要用到联合索引,用 <code>UNION all </code>执行的效率更高。多个 OR 的字句没有用到索引,改写成 <code>UNION</code> 的形式再试图与索引匹配。<br/></p><p>22、在IN后面值的列表中,将出现最频繁的值放在最前面,出现得最少的放在最后面,减少判断次数。<br/></p><p>23、尽量将数据的处理工作放在服务器上,如使用存储过程。存储过程是编译好、优化过、并且被组织到一个执行规划、且存储在数据库中的 SQL 语句,是控制流语言的集合,速度当然快。反复执行的动态 SQL,可以使用临时存储过程,该过程(临时表)被放在 <code>Tempdb </code>中。</p><p>24、当服务器的内存够多时,<strong>配制线程数量 = 最大连接数+5</strong>,这样能发挥最大的效率;否则使用配制线程数量< 最大连接数,启用 SQL SERVER 的线程池来解决,如果还是数量 = 最大连接数+5,严重的损害服务器的性能。<br/></p><p>25、查询的关联同写的顺序 :<br/></p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:sql;toolbar:false">select a.personMemberID, * from chineseresume a,personmember b where personMemberID = b.referenceid and a.personMemberID = 'JCNPRH39681' (A = B, B = '号码')
select a.personMemberID, * from chineseresume a,personmember b where a.personMemberID = b.referenceid and a.personMemberID = 'JCNPRH39681' and b.referenceid = 'JCNPRH39681' (A = B, B = '号码', A = '号码')
select a.personMemberID, * from chineseresume a,personmember b where b.referenceid = 'JCNPRH39681' and a.personMemberID = 'JCNPRH39681' (B = '号码', A = '号码')</pre><div class="contentsignin">登入後複製</div></div><p>26、尽量使用 EXISTS 代替 <code>select count(1)</code> 来判断是否存在记录。count 函数只有在统计表中所有行数时使用,而且 <code>count(1)</code> 比 <code>count(*) </code>更有效率。</p><p>27、尽量使用 “>=”,不要使用 “>”。<br/></p><p>28、索引的使用规范:<br/></p><ul style="list-style-type: disc;"><li><p>索引的创建要与应用结合考虑,建议大的<code> OLTP 表</code>不要超过 6 个索引;<br/>
</p></li><li><p>尽可能的使用索引字段作为查询条件,尤其是聚簇索引,必要时可以通过 <code>index index_name </code>来强制指定索引;</p></li><li><p>避免对大表查询时进行 <code>table scan</code>,必要时考虑新建索引;</p></li><li><p>在使用索引字段作为条件时,如果该索引是联合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用;</p></li><li><p>要注意索引的维护,周期性重建索引,重新编译存储过程。 </p></li></ul><p>29、下列 SQL 条件语句中的列都建有恰当的索引,但执行速度却非常慢:<br/></p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:sql;toolbar:false">SELECT * FROM record WHERE substrINg(card_no, 1, 4) = '5378' --13秒
SELECT * FROM record WHERE amount/30 < 1000 --11秒
SELECT * FROM record WHERE convert(char(10), date, 112) = '19991201' --10秒</pre><div class="contentsignin">登入後複製</div></div><p>分析: WHERE 子句中对列的任何操作结果都是在 SQL 运行时逐列计算得到的,因此它不得不进行表搜索,而没有使用该列上面的索引。如果这些结果在查询编译时就能得到,那么就可以被 SQL 优化器优化,使用索引,避免表搜索,因此将 SQL 重写成下面这样:</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:sql;toolbar:false">SELECT * FROM record WHERE card_no like '5378%' -- < 1秒
SELECT * FROM record WHERE amount < 1000*30 -- < 1秒
SELECT * FROM record WHERE date = '1999/12/01' -- < 1秒</pre><div class="contentsignin">登入後複製</div></div><p>30、当有一批处理的插入或更新时,用批量插入或批量更新,绝不会一条条记录的去更新。</p>
<p>31、在所有的存储过程中,能够用 SQL 语句的,绝不用循环去实现。<br></p>
<p>32、选择最有效率的表名顺序(只在基于规则的优化器中有效): </p>
<p>Oracle 的解析器按照从右到左的顺序处理 FROM 子句中的表名,FROM 子句中写在最后的表(基础表 driving table)将被最先处理,在 FROM 子句中包含多个表的情况下,必须选择记录条数最少的表作为基础表。<strong>如果有 3 个以上的表连接查询,那就需要选择交叉表(intersection table)作为基础表</strong>,交叉表是指那个被其他表所引用的表。</p>
<p>33、提高<code> GROUP BY</code> 语句的效率,可以通过将不需要的记录在 GROUP BY 之前过滤掉。<br></p>
<p>34、<strong>SQL 语句用大写</strong>,因为 Oracle 总是先解析 SQL 语句,把小写的字母转换成大写的再执行。</p>
<p>35、别名的使用,别名是大型数据库的应用技巧,就是表名、列名在查询中以一个字母为别名,查询速度要比建连接表快 1.5 倍。<br></p>
<p>36、避免死锁,在你的存储过程和触发器中访问同一个表时总以相同的顺序;事务应尽可能地缩短,减少数据量的涉及;永远不要在事务中等待用户输入。<br></p>
<p>37、避免使用临时表,除非有需要,可以使用表变量代替。大多数时候(99%),表变量驻扎在内存中,因此速度比临时表更快,临时表驻扎在 TempDb 数据库中,因此临时表上的操作需要跨数据库通信,速度自然慢。<br></p>
<p>38、最好不要使用触发器:<br></p>
<ul style="list-style-type: disc;">
<li><p>触发,执行一个触发器事件本身就是一个耗费资源的过程;<br>
</p></li>
<li><p>如果能够使用约束实现的,尽量不要使用触发器;</p></li>
<li><p>不要为不同的触发事件(<code>Insert、Update 和 Delete</code>)使用相同的触发器;</p></li>
<li><p>不要在觸發器中使用交易型程式碼。 </p></li>
</ul>
<p>39、索引建立規則: <br></p>
<ul style="list-style-type: disc;">
<li><p>#表的主鍵、外鍵必須有索引; <br>
</p></li>
<li><p>資料量超過300 的表應該有索引; </p></li>
<li><p>經常與其他表連接的表,在連接欄位上應該建立索引; </p></li>
<li><p>經常出現在WHERE 子句中的字段,特別是大表的字段,應該建立索引; </p></li>
<li><p>索引應該建在選擇性高的字段上; </p></li>
<li><p>索引應該建在小字段上,對於大的文字字段甚至超長字段,不要建立索引; </p></li>
<li> <p>複合索引的建立需要仔細分析,盡量考慮用單字段索引代替; </p>
</li>
<li><p>#正確選擇複合索引中的主列字段,一般是選擇性較好的字段; </p></li>
<li><p>複合索引的幾個欄位是否經常同時以AND 方式出現在 WHERE 子句中?單字段查詢是否極少甚至沒有?如果是,則可以建立複合索引;否則考慮單一字段索引; </p></li>
<li><p>如果複合索引中包含的欄位經常單獨出現在 WHERE 子句中,則分解為多個單一字段索引; </p></li>
<li><p>如果複合索引所包含的字段超過3 個,那麼仔細考慮其必要性,減少複合的字段; </p></li>
<li>##如果既有單一欄位索引,又有這幾個欄位上的複合索引,一般可以刪除複合索引; <p></p>
</li>
<li>頻繁進行資料運算的表,不要建立太多的索引; <p></p>
</li>
<li>刪除無用的索引,避免對執行計畫造成負面影響; <p></p>
</li>
<li>表上建立的每個索引都會增加儲存開銷,索引對於插入、刪除、更新操作也會增加處理上的開銷。另外,過多的複合索引,在有單一欄位索引的情況下,一般都是沒有存在價值的;相反,還會降低資料增加刪除時的效能,特別是對頻繁更新的表來說,負面影響更大。 <p></p>
</li>
<li>盡量不要對資料庫中某個含有大量重複的值的欄位建立索引。 <p></p>
</li>
</ul>40、MySQL 查詢最佳化總結:<p><br></p>
<ul style="list-style-type: disc;">
<li>使用慢查詢日誌去發現慢查詢,使用執行計畫去判斷查詢是否正常運行,總是去測試你的查詢看看是否他們運行在最佳狀態下。 <p>
<br></p>
</li>
<li>久而久之效能會變化,避免在整個表上使用<p>count(*)<code>,它可能鎖住整張表,使查詢保持一致以便後續相似的查詢可以使用查詢緩存,在適當的情況下使用</code>GROUP BY<code> 而不是</code>DISTINCT<code>,在WHERE、GROUP BY 和ORDER BY 子句中使用有索引的列,保持索引簡單,不在多個索引中包含同一個列。 </code>
<br></p>
</li>
<li>有時候MySQL 會使用錯誤的索引,對於這個情況使用<p>USE INDEX<code>,檢查使用</code>SQL_MODE=STRICT<code> 的問題,對於記錄數小於5的索引字段,在UNION 的時候使用</code>LIMIT<code>不是用OR。 </code>
<br></p>
</li>
<li>為了避免在更新前SELECT,使用<p>INSERT ON DUPLICATE KEY<code> 或</code>INSERT IGNORE<code>;不要用UPDATE 去實現,不要使用MAX;使用索引欄位和</code>ORDER BY<code>子句</code>LIMIT M<code>,N 實際上可以減緩查詢在某些情況下,有節制地使用,在WHERE 子句中使用UNION 代替子查詢,在重新啟動的MySQL,記得來溫暖你的資料庫,以確保資料在記憶體和查詢速度快,考慮持久連接,而不是多個連接,以減少開銷。 </code>
<br></p>
</li>
<li>基準查詢,包括使用伺服器上的負載,有時一個簡單的查詢可以影響其他查詢,當負載增加在伺服器上,使用<p>SHOW PROCESSLIST<code> 查看慢查詢和有問題的查詢,在開發環境中產生的鏡像資料中測試的所有可疑的查詢。 </code>
<br></p>
</li>
</ul>41、MySQL 備份過程:<p><br></p>
<ul style="list-style-type: disc;">
<li>從二級複製伺服器上備份;<p>
<br></p>
</li>
<li>在進行備份期間停止複製,以避免在資料依賴和外鍵約束上出現不一致;<p></p>
</li>
<li>徹底停止MySQL,從資料庫文件進行備份;<p></p>
</li>
<li>如果使用MySQL dump 進行備份,請同時備份二進位日誌文件,確保複製沒有中斷;<p></p>
</li>
<li>不要信任LVM快照,這很可能產生資料不一致,將來會給你帶來麻煩;<p></p>
</li>
<li>為了更容易進行單表恢復,如果資料是與其他表隔離的,以表為單位導出數據。 <p></p>
</li>
<li>當使用<p>mysqldump<code> 時請使用</code>–opt<code>;</code></p>
</li>
<li>在備份之前檢查和優化表;<p></p>
</li>
<li><p>為了更快的進行導入,在導入時暫時禁用外鍵約束和唯一性檢測;</p></li>
<li><p>在每一次備份後計算資料庫,表以及索引的尺寸,以便能夠監控資料尺寸的成長;</p></li>
<li><p>定期執行備份。 </p></li>
</ul>
<p>42、查詢緩衝不會自動處理空格,因此,在寫SQL 語句時,應盡量減少空格的使用,尤其是在SQL 首和尾的空格(因為查詢緩衝不會自動截取首尾空格)。 <br></p>
<p>43、member 用 mid 做標準進行分錶方便查詢麼?一般的業務需求中基本上都是以 username 為查詢依據,正常應是 username 做 hash 取模來分錶。而分錶的話 MySQL 的 <code>partition</code> 功能就是乾這個的,對程式碼是透明的;在程式碼層面去實現貌似是不合理的。 </p>
<p>44、我們應該為資料庫裡的每張表都設定一個ID 做為其主鍵,而且最好的是一個INT 型的(建議使用<code>UNSIGNED</code>),並設定上自動增加的<code>AUTO_INCREMENT</code> 標誌。 <br></p>
<p>45、在所有的預存程序和觸發器的開始設定 <code>SET NOCOUNT ON</code>,在結束時設定 <code>SET NOCOUNT OFF</code>。無需在執行預存程序和觸發器的每個語句後向客戶端發送 <code>DONE_IN_PROC</code> 訊息。 <br></p>
<p>46、MySQL 查詢可以啟用高速查詢快取。這是提高資料庫效能的有效MySQL優化方法之一。當同一個查詢被執行多次時,從快取中提取資料和直接從資料庫中返回資料快很多。 <br></p>
<p>47、<code>EXPLAIN SELECT</code> 查詢用來追蹤檢視效果:<br></p>
<p>使用EXPLAIN 關鍵字可以讓你知道MySQL 是如何處理你的SQL語句的。這可以幫你分析你的查詢語句或是表格結構的效能瓶頸。 EXPLAIN 的查詢結果也會告訴你你的索引主鍵被如何利用的,你的資料表是如何被搜尋和排序的。 <br></p>
<p>48、只要一行資料時使用LIMIT 1 :<br></p>
<p>當你查詢表格的某些時候,你已經知道結果只會有一個結果,但因為你可能需要去fetch遊標,或是你也許會去檢查回傳的記錄數。在這種情況下,加上 <code>LIMIT 1 </code>可以增加效能。這樣一來,MySQL 資料庫引擎會在找到一筆資料後停止搜索,而不是繼續往後查少下一筆符合記錄的資料。 </p>
<p>49、選擇表合適儲存引擎: <br></p>
<p><strong>myisam:</strong>在應用程式時以讀取和插入操作為主,只有少量的更新和刪除,並且對事務的完整性,並發性要求不是很高的。 <br></p>
<p><strong>InnoDB:</strong>交易處理,以及在並發條件下要求資料的一致性。除了插入和查詢外,包括許多的更新和刪除。 (InnoDB 有效降低刪除和更新導致的鎖定)。 </p>
<p>對於支援事務的InnoDB類型的表來說,影響速度的主要原因是<code>AUTOCOMMIT</code> 預設設定是開啟的,而且程式沒有明確調用BEGIN 開始事務,導致每插入一條都自動提交,嚴重影響了速度。可以在執行 SQL 之前呼叫 begin,多條 SQL 形成一個事物(即使 autocommit 開啟也可以),將大大提高效能。 </p>
<p>50、最佳化表格的資料類型,選擇合適的資料類型: <br></p>
<p><strong>#原則:</strong>更小通常更好,簡單就好,所有欄位都得有預設值,盡量避免NULL。 MySQL可以很好的支援大數據量的訪問,但是一般來說,資料庫中的表越小,在它上面執行的查詢就會越快。因此,在建立表格的時候,為了獲得更好的效能,我們可以將表中欄位的寬度設得盡可能小。 </p>
<p>同樣的,如果可以的話,我們應該使用<code>MEDIUMINT </code>而不是<code>BIGIN </code>來定義整數字段,應該盡量把字段設定為<code>NOT NULL</code>,這樣將來執行查詢的時候,資料庫就不用去比較NULL 值。 </p>
<p>對於某些文字字段,例如“省份”或“性別”,我們可以將它們定義為 <code>ENUM </code>類型。因為在 MySQL 中,ENUM 類型被當作數值資料來處理,而數值型資料被處理的速度比文字類型快得多。這樣,可以提高資料庫的效能。 <br></p>
<p>51、字串資料類型:char, varchar, text 。 <br></p>
<p>52、任何對列的操作都會導致表格掃描,它包括資料庫函數、計算表達式等等,查詢時要盡可能將操作移至等號右邊。 </p>
<p>推薦教學:《<a href="https://www.php.cn/mysql-tutorials.html" target="_self">MySQL教學</a>》</p>