咱們作軟件開發的,大部分人都離不開跟數據庫打交道,特別是erp開發的,跟數據庫打交道更是頻繁,存儲過程動不動就是上千行,若是數據量大,人員流動大,那麼我麼還能保證下一段時間系統還能流暢的運行嗎?我麼還能保證下一我的能看懂我麼的存儲過程嗎?那麼我結合公司平時的培訓和平時我的工做經驗和你們分享一下,但願對你們有幫助。 html
要知道sql語句,我想咱們有必要知道sqlserver查詢分析器怎麼執行我麼sql語句的,我麼不少人會看執行計劃,或者用profile來監視和調優查詢語句或者存儲過程慢的緣由,可是若是咱們知道查詢分析器的執行邏輯順序,下手的時候就成竹在胸,那麼下手是否是有把握點呢? 程序員
一:查詢的邏輯執行順序 sql
(1) FROM < left_table> 數據庫
(3) < join_type> JOIN < right_table> (2) ON < join_condition> 編程
(4) WHERE < where_condition> 服務器
(5) GROUP BY < group_by_list> 網絡
(6) WITH {cube | rollup} 併發
(7) HAVING < having_condition> 函數
(8) SELECT (9) DISTINCT (11) < top_specification> < select_list> 工具
(10) ORDER BY < order_by_list>
標準的SQL 的解析順序爲:
(1).FROM 子句 組裝來自不一樣數據源的數據
(2).WHERE 子句 基於指定的條件對記錄進行篩選
(3).GROUP BY 子句 將數據劃分爲多個分組
(4).使用聚合函數進行計算
(5).使用HAVING子句篩選分組
(6).計算全部的表達式
(7).使用ORDER BY對結果集進行排序
二 執行順序:
1.FROM:對FROM子句中前兩個表執行笛卡爾積生成虛擬表vt1
2.ON:對vt1表應用ON篩選器只有知足< join_condition> 爲真的行才被插入vt2
3.OUTER(join):若是指定了 OUTER JOIN保留表(preserved table)中未找到的行將行做爲外部行添加到vt2 生成t3若是from包含兩個以上表則對上一個聯結生成的結果表和下一個表重複執行步驟和步驟直接結束
4.WHERE:對vt3應用 WHERE 篩選器只有使< where_condition> 爲true的行才被插入vt4
5.GROUP BY:按GROUP BY子句中的列列表對vt4中的行分組生成vt5
6.CUBE|ROLLUP:把超組(supergroups)插入vt6 生成vt6
7.HAVING:對vt6應用HAVING篩選器只有使< having_condition> 爲true的組才插入vt7
8.SELECT:處理select列表產生vt8
9.DISTINCT:將重複的行從vt8中去除產生vt9
10.ORDER BY:將vt9的行按order by子句中的列列表排序生成一個遊標vc10
11.TOP:從vc10的開始處選擇指定數量或比例的行生成vt11 並返回調用者
看到這裏,那麼用過linqtosql的語法有點類似啊?若是咱們咱們瞭解了sqlserver執行順序,那麼咱們就接下來進一步養成平常sql好習慣,也就是在實現功能同時有考慮性能的思想,數據庫是能進行集合運算的工具,咱們應該儘可能的利用這個工具,所謂集合運算實際就是批量運算,就是儘可能減小在客戶端進行大數據量的循環操做,而用SQL語句或者存儲過程代替。
3、只返回須要的數據
返回數據到客戶端至少須要數據庫提取數據、網絡傳輸數據、客戶端接收數據以及客戶端處理數據等環節,若是返回不須要的數據,就會增長服務器、網絡和客戶端的無效勞動,其害處是顯而易見的,避免這類事件須要注意:
A、橫向來看,
(1)不要寫SELECT *的語句,而是選擇你須要的字段。
(2)當在SQL語句中鏈接多個表時, 請使用表的別名並把別名前綴於每一個Column上.這樣一來,就能夠減小解析的時間並減小那些由Column歧義引發的語法錯誤。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
若有表table1(ID,col1)和table2 (ID,col2)
Select A.ID, A.col1, B.col2
-- Select A.ID, col1, col2 –不要這麼寫,不利於未來程序擴展
from table1 Ainner join table2 Bon A.ID=B.IDWhere …
B、縱向來看,
(1)合理寫WHERE子句,不要寫沒有WHERE的SQL語句。
(2)SELECT TOP N *--沒有WHERE條件的用此替代
|
四 :儘可能少作重複的工做
A、控制同一語句的屢次執行,特別是一些基礎數據的屢次執行是不少程序員不多注意的。
B、減小屢次的數據轉換,也許須要數據轉換是設計的問題,可是減小次數是程序員能夠作到的。
C、杜毫不必要的子查詢和鏈接表,子查詢在執行計劃通常解釋成外鏈接,多餘的鏈接錶帶來額外的開銷。
D、合併對同一表同一條件的屢次UPDATE,好比
UPDATE EMPLOYEESET FNAME='HAIWER'
WHERE EMP_ID=' VPA30890F' UPDATE EMPLOYEESET LNAME='YANG'
WHERE EMP_ID=' VPA30890F'
這兩個語句應該合併成如下一個語句
UPDATE EMPLOYEESET FNAME='HAIWER',LNAME='YANG' WHERE EMP_ID=' VPA30890F'
|
E、UPDATE操做不要拆成DELETE操做+INSERT操做的形式,雖然功能相同,可是性能差異是很大的。
5、注意臨時表和表變量的用法
在複雜系統中,臨時表和表變量很難避免,關於臨時表和表變量的用法,須要注意:
A、若是語句很複雜,鏈接太多,能夠考慮用臨時表和表變量分步完成。
B、若是須要屢次用到一個大表的同一部分數據,考慮用臨時表和表變量暫存這部分數據。
C、若是須要綜合多個表的數據,造成一個結果,能夠考慮用臨時表和表變量分步彙總這多個表的數據。
D、其餘狀況下,應該控制臨時表和表變量的使用。
E、關於臨時表和表變量的選擇,不少說法是表變量在內存,速度快,應該首選表變量,可是在實際使用中發現,
(1)主要考慮須要放在臨時表的數據量,在數據量較多的狀況下,臨時表的速度反而更快。
(2)執行時間段與預計執行時間(多長)
F、關於臨時表產生使用SELECT INTO和CREATE TABLE + INSERT INTO的選擇,通常狀況下,
SELECT INTO會比CREATE TABLE + INSERT INTO的方法快不少,
可是SELECT INTO會鎖定TEMPDB的系統表SYSOBJECTS、SYSINDEXES、SYSCOLUMNS,在多用戶併發環境下,容易阻塞其餘進程,
因此個人建議是,在併發系統中,儘可能使用CREATE TABLE + INSERT INTO,而大數據量的單個語句使用中,使用SELECT INTO。
6、子查詢的用法(1)
子查詢是一個 SELECT 查詢,它嵌套在 SELECT、INSERT、UPDATE、DELETE 語句或其它子查詢中。
任何容許使用表達式的地方均可以使用子查詢,子查詢可使咱們的編程靈活多樣,能夠用來實現一些特殊的功能。可是在性能上,
每每一個不合適的子查詢用法會造成一個性能瓶頸。若是子查詢的條件中使用了其外層的表的字段,這種子查詢就叫做相關子查詢。
相關子查詢能夠用IN、NOT IN、EXISTS、NOT EXISTS引入。 關於相關子查詢,應該注意:
(1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
SELECT TITLEFROM TITLES
WHERE NOT EXISTS
(SELECT TITLE_IDFROM SALES
WHERE TITLE_ID = TITLES.TITLE_ID)
能夠改寫成:
SELECT TITLE
FROM TITLESLEFT JOIN SALES
ON SALES.TITLE_ID = TITLES.TITLE_ID
WHERE SALES.TITLE_IDIS NULL
B、 若是保證子查詢沒有重複 ,IN、EXISTS的相關子查詢能夠用INNER JOIN 代替。好比:
SELECT PUB_NAME
FROM PUBLISHERS
WHERE PUB_IDIN
(SELECT PUB_ID
FROM TITLES
WHERE TYPE ='BUSINESS')
能夠改寫成:
SELECT A.PUB_NAME--SELECT DISTINCT A.PUB_NAME
FROM PUBLISHERS AINNER JOIN TITLES B
ON B.TYPE ='BUSINESS' AND
A.PUB_ID=B. PUB_ID
|
1
2
3
4
5
6
7
8
9
|
C、IN的相關子查詢用EXISTS代替,好比
SELECT PUB_NAMEFROM PUBLISHERS
WHERE PUB_IDIN
(SELECT PUB_IDFROM TITLESWHERE TYPE ='BUSINESS')
能夠用下面語句代替:
SELECT PUB_NAMEFROM PUBLISHERSWHERE EXISTS
(SELECT 1FROM TITLESWHERE TYPE ='BUSINESS' AND
PUB_ID= PUBLISHERS.PUB_ID)
D、不要用COUNT(*)的子查詢判斷是否存在記錄,最好用LEFT JOIN或者EXISTS,好比有人寫這樣的語句:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
SELECT JOB_DESCFROM JOBS
WHERE (SELECT COUNT(*)FROM EMPLOYEEWHERE JOB_ID=JOBS.JOB_ID)=0
應該改爲:
SELECT JOBS.JOB_DESCFROM JOBSLEFT JOIN EMPLOYEE
ON EMPLOYEE.JOB_ID=JOBS.JOB_ID
WHERE EMPLOYEE.EMP_IDIS NULL
SELECT JOB_DESCFROM JOBS
WHERE (SELECT COUNT(*)FROM EMPLOYEEWHERE JOB_ID=JOBS.JOB_ID)<>0
應該改爲:
SELECT JOB_DESCFROM JOBS
WHERE EXISTS (SELECT 1FROM EMPLOYEEWHERE JOB_ID=JOBS.JOB_ID)
|
創建索引後,並非每一個查詢都會使用索引,在使用索引的狀況下,索引的使用效率也會有很大的差異。只要咱們在查詢語句中沒有強制指定索引,
索引的選擇和使用方法是SQLSERVER的優化器自動做的選擇,而它選擇的根據是查詢語句的條件以及相關表的統計信息,這就要求咱們在寫SQL
語句的時候儘可能使得優化器可使用索引。爲了使得優化器能高效使用索引,寫語句的時候應該注意:
(1
1
2
3
4
5
6
7
8
9
|
A、不要對索引字段進行運算,而要想辦法作變換,好比
SELECT IDFROM TWHERE NUM/2=100
應改成:
SELECT IDFROM TWHERE NUM=100*2
SELECT IDFROM TWHERE NUM/2=NUM1
若是NUM有索引應改成:
SELECT IDFROM TWHERE NUM=NUM1*2
若是NUM1有索引則不該該改。
|
(2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
發現過這樣的語句:
SELECT 年,月,金額FROM 結餘表 WHERE 100*年+月=2010*100+10
應該改成:
SELECT 年,月,金額FROM 結餘表WHERE 年=2010AND月=10
B、 不要對索引字段進行格式轉換
日期字段的例子:
WHERE CONVERT(VARCHAR(10), 日期字段,120)='2010-07-15'
應該改成
WHERE日期字段〉='2010-07-15' AND 日期字段<'2010-07-16'
ISNULL轉換的例子:
WHERE ISNULL(字段,'')<>''應改成:WHERE字段<>''
WHERE ISNULL(字段,'')=''不該修改
WHERE ISNULL(字段,'F') ='T'應改成:WHERE字段='T'
WHERE ISNULL(字段,'F')<>'T'不該修改
|
(3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
C、 不要對索引字段使用函數
WHERE LEFT(NAME, 3)='ABC' 或者WHERE SUBSTRING(NAME,1, 3)='ABC'
應改成:WHERE NAME LIKE 'ABC%'
日期查詢的例子:
WHERE DATEDIFF(DAY, 日期,'2010-06-30')=0
應改成:WHERE 日期>='2010-06-30' AND 日期 <'2010-07-01'
WHERE DATEDIFF(DAY, 日期,'2010-06-30')>0
應改成:WHERE 日期 <'2010-06-30'
WHERE DATEDIFF(DAY, 日期,'2010-06-30')>=0
應改成:WHERE 日期 <'2010-07-01'
WHERE DATEDIFF(DAY, 日期,'2010-06-30')<0
應改成:WHERE 日期>='2010-07-01'
WHERE DATEDIFF(DAY, 日期,'2010-06-30')<=0
應改成:WHERE 日期>='2010-06-30'
|
D、不要對索引字段進行多字段鏈接
好比:
WHERE FAME+ '. '+LNAME='HAIWEI.YANG'
應改成:
WHERE FNAME='HAIWEI' AND LNAME='YANG'
八:多表鏈接的鏈接條件對索引的選擇有着重要的意義,因此咱們在寫鏈接條件條件的時候須要特別注意。
A、多表鏈接的時候,鏈接條件必須寫全,寧肯重複,不要缺漏。
B、鏈接條件儘可能使用匯集索引
C、注意ON、WHERE和HAVING部分條件的區別
ON是最早執行, WHERE次之,HAVING最後,由於ON是先把不符合條件的記錄過濾後才進行統計,它就能夠減小中間運算要處理的數據,按理說應該速度是最快的,WHERE也應該比 HAVING快點的,由於它過濾數據後才進行SUM,在兩個表聯接時才用ON的,因此在一個表的時候,就剩下WHERE跟HAVING比較了
1
2
3
4
|
考慮聯接優先順序:
(1)INNER JOIN
(2)LEFT JOIN (注:RIGHT JOIN 用LEFT JOIN 替代)
(3)CROSS JOIN
|
其它注意和了解的地方有:
A、在IN後面值的列表中,將出現最頻繁的值放在最前面,出現得最少的放在最後面,減小判斷的次數
B、注意UNION和UNION ALL的區別。--容許重複數據用UNION ALL好
C、注意使用DISTINCT,在沒有必要時不要用
D、TRUNCATE TABLE 與 DELETE 區別
E、減小訪問數據庫的次數
還有就是咱們寫存儲過程,若是比較長的話,最後用標記符標開,由於這樣可讀性很好,即便語句寫的不怎麼樣可是語句工整,C# 有region
sql我比較喜歡用的就是
--startof 查詢在職人數
sql語句
--end of
|
正式機器上咱們通常不能隨便調試程序,可是不少時候程序在咱們本機上沒問題,可是進正式系統就有問題,可是咱們又不能隨便在正式機器上操做,那麼怎麼辦呢?咱們能夠用回滾來調試咱們的存儲過程或者是sql語句,從而排錯。
BEGIN TRAN
UPDATE aSET 字段=''
ROLLBACK
|
做業存儲過程我通常會加上下面這段,這樣檢查錯誤能夠放在存儲過程,若是執行錯誤回滾操做,可是若是程序裏面已經有了事務回滾,那麼存儲過程就不要寫事務了,這樣會致使事務回滾嵌套下降執行效率,可是咱們不少時候能夠把檢查放在存儲過程裏,這樣有利於咱們解讀這個存儲過程,和排錯。