一、查詢設計的建議
在一些情況下,查詢結構使優化器不能選擇最好的處理策略。知道何時發生這種情況以及如何避免它是很重要的。這里主要介紹如下幾點:
- 在小的結果集上操作;
- 有效使用索引;
- 避免優化器提示;
- 使用域和參照完整性;
- 避免資源密集型的查詢;
- 減少網絡傳輸數量;
- 減少事務開銷;
二、在小結果集上操作
為了改進查詢性能,應限制操作的數據量,包括列數和行數。在小結果集上操作減少了查詢消耗的資源數量並且增進索引效率。在小結果集上操作應當遵循以下原則:
- 限制選擇列表中的列數;
- 使用高選擇性的WHERE子句來限制返回的行數;
1、限制選擇列表中的列數
在SELECT語句的選擇列表中使用最小的列集,不要使用輸出結果集中不需要的列。例如,不要使用SELECT * 返回所有的里。SELECT * 使覆蓋索引無效。
來看查詢,在Name上的索引本身的查詢很快。
SELECT Name FROM PersonTenThousand
而SELECT * 要訪問基本表:
SELECT * FROM PersonTenThousand
如圖所示:選擇列表中的列越少,查詢性能越好。同時太多列還增加了網絡上的數據傳輸,從而進一步降低性能。
2、使用高選擇性的WHERE子句
在WHERE子句中引用的列的選擇性控制着列上索引的使用。從表中請求大量的行可能不能從索引中得到。因為書簽查找的開銷。
大部分時候,最終用戶一次只關注有限數量的行。因此,應該設計數據庫應用程序隨着用戶瀏覽數據而增量地請求數據。返回大的數據集代價很高,而且這些數據不能作為一個整體使用。
三、有效地使用索引
一些有效使用索引應該遵循的查詢設計原則:
- 避免不可參數化的搜索條件;
- 避免在WHERE子句列上使用算數運算符;
- 避免WHERE子句列上的函數;
1、避免不可參數化的搜索條件
優化器從索引中獲益的能力取決於搜索條件的選擇性,從而也就取決於WHERE子句中引用的列的選擇性。在WHERE子句中的列上的搜索斷言確定列上的索引操作是否可以進行。
可參數化的搜索條件一般允許SQLServer在索引中查找一行並讀取改行(或搜索條件保持為真的相鄰范圍內的行)。而不可參數化的搜索條件一般阻止優化器使用WHERE子句中引用列上的索引。排除搜索條件一般不允許SQL Server執行可使用參數的搜索條件所支持的索引查找操作,這主要是由於索引上的數據是排序的原因確定的。例如,!=條件要求掃描所有行以確定匹配的行。
類型 | 搜索條件 |
可參數化 | 包含條件=、>、>=、<、<=、BETWEEN、某些LIKE(xxx%) |
不可參數化 | 排除條件<>、!=、!>、!<、NOT EXISTS、NOT IN、NOT LIKE IN、OR、某些Like(%xxx) |
1、BETWEEN vs IN/OR
考慮如下使用IN的搜索條件查詢
使用BETWEEN...AND...查詢
從執行計划上看,兩個執行計划相同。
但是更仔細地查看執行計划就會從他們的檢索機制中發現差別:
IN搜索 BETWEEN...AND搜索
從上面兩張圖片的對比可以看出,SQL Server將包含4個值的IN條件解釋為4個OR條件。同時聚集索引比訪問4次。這與IO統計輸出的數字也對應。而BETWEEN...AND被解釋為>=和<=條件,只訪問一次聚集索引。所以,BETWEEN代替IN搜索條件將這個查詢的邏輯讀數量從8降低到2,掃描數量也從4降低到1。盡管兩個查詢都是聚集索引列上的查找,優化器使用BETWEEN子句定位一系列行比IN子句快得多。如果能夠在IN/OR和BETWEEN條件中選擇,那么始終選擇BETWEEN條件,因為它一般比IN/OR有效的多。實際上應該進一步使用>=和<=的組合來代替BETWEEN子句。
2、LIKE條件
使用LIKE搜索條件時,如果可能,嘗試在WHERE子句中使用一個或多個前導字符。在LIKE子句中使用前導字符使優化器能夠將LIKE條件轉換有效使用索引的條件。LIKE條件中前導字符的數量越大,優化器就越能更好地確定有效的索引。如果在LIKE條件中使用一個通配符作為前導字符串將阻止優化器執行索引上的查找,它將掃描整個表。
考慮如下搜索:
SELECT Phone FROM PersonTenThousand WHERE Phone LIKE '86135%'
從上圖可以看出,SQL Server查詢優化器將LIKE條件轉化為一個等價的>=和<的條件組合。
例如,可以用如下搜索代替LIKE。
SELECT Phone FROM PersonTenThousand WHERE Phone >= '86135' AND Phone < '86136'
再看邏輯讀數量:
而如果是如下搜索:
SELECT Phone FROM PersonTenThousand WHERE Phone LIKE '%011'
雖然,也是使用了索引,但是已經是全索引掃描了。這點從邏輯讀(20次)上就能夠看出來:
3、!<條件 vs >=條件
盡管!<和>=搜索條件都檢索相同的結果集,但是他們可能執行不同的內部操作。但SQL Server查詢優化器並不是什么都不做的,它會把!<轉換為>=搜索條件。
如下面的搜索:
SELECT Phone FROM PersonTenThousand WHERE Phone !< '86135'
看它的解釋i計划如下:
雖然SQL Server查詢優化器在許多情況下自動優化查詢語法以改進性能,但是不應該依賴它。從一開始就編寫高校的查詢是好習慣。
2、避免WHERE子句列上的算數運算符
在WHERE子句中的列上使用算數運算符可以阻止優化器使用該列上的索引,Age列上有非聚集索引。
考慮如下語句:
SELECT Age FROM PersonTenThousand WHERE Age * 2 = 36
執行計划如下:
如果將SQL語句改成這樣子:
SELECT Age FROM PersonTenThousand WHERE Age = 36 / 2
則執行計划如下:
比較明顯的一個是索引掃描,一個是索引查找。比如說明,如果WHERE子句寫的是Age = 18也是索引查找。那么為什么在左邊的算數運算符要索引掃掃描呢?
SQL Server對每條記錄都執行了一次Age * 2乘法運算。比如上表有1萬條記錄,實際上是執行了1萬次 x * 2 = 18?的判斷。
因此,為了高效地使用索引並改進查詢性能,要避免在WHERE子句中的列上使用算數運算符。
3、避免WHERE子句列上的函數
和算數運算符一樣,WHERE子句列上的函數也傷害查詢性能,理由與第二點相同。
1、SUBSTRING vs LIKE
在下面這條SELECT語句中,使用SUBSTRING將阻止列上索引的使用。
SELECT NAME FROM PersonTenThousand WHERE SUBSTRING(Name,1,1) = '冀'
執行計划如下:
如果將SQL子句改為LIKE實現:
SELECT NAME FROM PersonTenThousand WHERE Name LIKE '冀%'
執行計划如下:
同樣的理由,對於在WHERE子句的函數,SQLServer對每一條記錄都執行了一次。
2、日期部分(Date Part)比較
SQL Server可以將日期和時間數據作為獨立的字段或兩者組合的DATETIME字段存儲。但是,可能需要在一個字段中保存數據和時間,而有時候你只需要日期,這通常意味着必須應用一個轉換函數來從DATETIME數據類型中提取日期部分。這么做同樣阻止優化器選擇該列上的索引。
假設我們需要讀取Birthday在2007年10月的所有,可以執行如下SELECT語句:
SELECT Birthday FROM PersonTenThousand WHERE DATEPART(yy,Birthday) = 2007 AND DATEPART(MM,Birthday) = 10
執行計划如下:
列上的DATEPART函數阻止了優化器較好地利用該列上的索引,因此作了一次索引掃描。
日期部分比較可以不使用DATETIME列上的函數完成,如下所示:
SELECT Birthday FROM PersonTenThousand WHERE Birthday >= '2007-10-01' AND Birthday < '2007-11-01'
其執行計划如下:
這使優化器高效地利用了索引列。
因此,為了使優化器能考慮WHERE子句中引用的列上的索引,要始終避免索引列上的函數。這增進了索引的效率,可以改進查詢性能。
四、避免優化器提示
SQL Server基於開銷的優化器根據當前表/索引結構和數據,動態確定查詢的處理策略。通常比優化器更聰明通常很困難,所以一般建議在書寫SQL語句時避免使用優化器提示子句。
1、連接提示
連接類型摘要參考表:
連接類型 | 索引列上的索引 | 連接表的一般大小 | 預排序的JOIN子句 |
嵌套循環 | 內部表上必須有,外部表上最好有 | 小 | 可選 |
合並 | 兩個表必須有,最有狀態是兩個表上都有聚集或覆蓋索引 | 大 | 是 |
Hash | 內部表沒有索引 | 任意,最優狀態是內部表大,外部表小 | 否 |
可以用上表中的連接提示指示SQL Server使用特定的連接類型:
連接類型 | 連接提示 |
嵌套循環 | LOOP JOIN |
合並 | MERGE JOIN |
Hash | HASH JOIN |
為了理解連接提示對性能的影響,考慮如下SELECT語句。
SELECT * FROM Province INNER JOIN PersonTenThousand ON Province.Id = PersonTenThousand.PId
其執行計划如下:
有時候我們可能有我們自己的理由使用我們指定的連接方式,我們可以將哈希匹配更改為嵌套循環:
SELECT * FROM Province INNER JOIN PersonTenThousand ON Province.Id = PersonTenThousand.PId OPTION(LOOP JOIN)
其執行計划如下:
但是從執行統計來看,使用了提示花費的時間卻更多:
使用連接提示 不使用連接提示
可以看到,有連接提示的查詢花費的時間長於沒有提示的查詢,它還增加了CPU的開銷。連接提示強制優化器忽略自己的優化策略而使用查詢指定的策略。連接提示一般對查詢性能有害,因為:
- 提示阻止了自動參數化;
- 阻止優化器動態決定表的連接范圍;
2、索引提示
前面提到過,在WHERE子句列上使用運算符阻止優化器選擇該列上的索引。為了改進性能,可以重寫查詢,不使用WHERE子句上的算數運算符,如對應的例子中所示。作為替代,甚至可以考慮用一個索引提示強制優化器使用該列上的索引。但是,大部分時候,避免索引提示讓優化器動態工作更好一些。
來看一下查詢語句,由於返回結果集較多,因此如果使用非聚集索引,可能書簽查找更加消耗資源。所以優化器選擇直接掃描聚集表:
SELECT * FROM PersonTenThousand WHERE Age > 20
執行計划與IO統計
統計如下:
強制使用索引的效果:
SELECT * FROM PersonTenThousand WITH (INDEX (IX_Age)) WHERE Age > 20
執行計划:
統計如下:
從執行計划的相對開銷和邏輯讀來看,很明顯,使用索引提示的查詢確實損害了性能。由於返回結果集較多,使用非聚集索引書簽書簽查找需要消耗較大資源。
一般情況下,讓優化器為查詢選擇最佳的索引策略,不要使用索引提示來忽略優化器的行為。而且,不使用索引提示使優化器能夠動態地隨着數據的隨時變化而確定最佳的索引策略。
五、使用域參照完整性
域和參照完整性幫助定義和強制列的有效值,維護數據庫的完整性。這通過列/表的約束來實現。
數據訪問通常是查詢執行中開銷最大的操作,避免冗余的數據訪問能幫助優化器減少查詢執行時間。域和參照完整性幫助SQL Server 2008優化器分析有效的數據值而不需要物理訪問數據,這減少了查詢時間。
1、非空約束
非空列約束用於定義特定咧不能輸入Null值從而實現域完整性。SQL Server在運行時強制這一事實以維護該列的域參照完整性。而且,定義非空列約束幫助優化器在查詢中該列上使用ISNULL函數時生成一個有效的處理策略。
SELECT Name FROM Person WHERE Name < 'B' OR Name >= 'C'
上面的查詢本意是,返回Name不等於'B'的行。但是,如果Name列上沒有非空約束,那么這個SQL查詢就漏掉了Name為Null的行。
正確的寫法是:
SELECT Name FROM Person WHERE Name < 'B' OR Name >= 'C' OR Name IS NULL
加了OR Name IS NULL會造成查詢的開銷更大,如下圖所示:
但是,當數據未知時,也許不能設定為默認值,這時候出現Null是不可避免的,但是要盡可能減少這種情況。
當不可避免地要處理Null值時,記住,可以通過過濾索引來從索引中刪除Null值(http://www.cnblogs.com/kissdodog/p/3158701.html),從而改進索引的性能。SQL Server 2008引入的稀疏列提供另外一個幫助你處理Null值的選擇。稀疏列首先針對的是更高效地保存Null值,從而減少空間和性能的損失。
2、聲明參照完整性
聲明參照完整性用於定義父表和子表之間的參照完整性(主外鍵關系)。主外鍵約束能夠幫助SQL Server 2008優化器改進性能。
考慮如下查詢:
SELECT PersonTenThousand.Id,Province.Id FROM PersonTenThousand INNER JOIN Province ON PersonTenThousand.PId = Province.Id
如果Province與PersonTenThousand有外鍵約束的情況下,SQL語句可改寫為
SELECT PersonTenThousand.Id,PersonTenThousand.PId FROM PersonTenThousand
第二條SELECT語句的執行計划被高度優化,不需要訪問Province父表。有了聲明參照完整性,優化器確定子表中的每個記錄在父表中都包含對應的記錄。因此,父表和子表的JOIN子句在第二條SELECT語句中是冗余的。
域和參照完整性是件好東西,它們不僅確保數據完整性而且還改進性能。子表的外鍵列應該非空,否則,子表可能有些行在父表中沒有出現,這將不能再前一個查詢中阻止優化器訪問主表。
六、避免資源密集型查詢
許多數據庫功能可以使用各種查詢技術來實現。應該采取的方法是使用非常資源友好並基於集合的查詢技術。可以用於減少查詢覆蓋的一些技術有:
- 避免數據類型轉換;
- 使用EXISTS代替COUNT(*)來驗證數據存在;
- 使用UNION ALL代替UNION;
- 為聚合和排序操作使用索引;
- 避免批查詢中的局部變量;
- 小心地命名存儲過程;
1、避免數據類型轉換
SQL Server自動將數據從一種數據類型轉換成另一種,這個過程被稱為隱含數據類型轉換。盡管隱含數據類型轉換很有用,但是它增加了查詢優化器的開銷。為了改進性能,使用與要比較的列相同數據類型的變量/常量。
2、使用EXISTS代替COUNT(*)驗證數據存在
常見的數據庫需求之一是驗證一組數據是否存在,比如登錄查詢COUNT(Id),再判斷是否大於0。如:
SELECT COUNT(Id) FROM PersonTenThousand WHERE Name = '公良閏'
使用COUNT(Id)驗證數據的存在是高度資源密集的,因為COUNT(Id)必須掃描表中的所有行。而EXISTS只需要掃描並且在第一個匹配的EXISTS條件的記錄處停止。為了改進性能,使用EXISTS代替COUNT(*)方法。
IF EXISTS ( SELECT Id FROM PersonTenThousand WHERE NAME = '公良閏' ) SELECT 1 ELSE SELECT 0
使用EXISTS是找到一條匹配的就停止,而COUNT()所有行都要掃描一次,因此EXISTS在很多情況下性能要由於COUNT(),比如要查找的記錄較前或者數據庫數據量非常大都是使用EXISTS較為有利。
3、使用UNION ALL代替UNION
可以使用UNION子句連接多條SELECT語句的結果集,從最終的結果集刪除重復並且在每個查詢上有效地運行DISTINCT。如何參與UNION子句的SELECT語句的結果集互相排斥,或者允許最終結果集中有重復的行,則使用UNION ALL代替UNION。這避免了偵測和刪除重復的開銷,改進性能。
SELECT * FROM PersonTenThousand WHERE NAME LIKE '%岳' UNION SELECT * FROM PersonTenThousand WHERE NAME LIKE '%角'
可以看到,在上面的例子中(使用UNION),優化器合並兩條SELECT語句結果的同時處理重復,因為結果集互相排斥,可以使用UNION ALL代替UNION。使用UNION ALL子句避免偵測重復的開銷從而增進性能。
SELECT * FROM PersonTenThousand WHERE NAME LIKE '%岳' UNION ALL SELECT * FROM PersonTenThousand WHERE NAME LIKE '%角'
執行計划對比如下:
優化器足夠智能,可以發現何時兩個查詢產生完全不同的列表,在這些時候可以選擇一個有效的UNION ALL操作。
4、為聚合和排序操作使用索引
一般來說,聚合函數如MIN、MAX能從對應列上的索引中獲益。列上沒有索引,優化器就必須掃描基本表(聚集索引),檢索所有行並執行改組(包含所有行)之上的流集合以識別最小/最大值,如下:
SELECT MIN(InCome) FROM PersonTenthousand
執行計划如下:
創建索引:
CREATE INDEX IX_InCome ON PersonTenThousand (InCome ASC)
再執行同樣的語句,執行計划如下:
使用非聚集索引,邏輯讀大大下降。
相似地,在ORDER BY子句中引用的列上創建索引能幫助優化器快速地組織結果集,因為在索引中列值被預先排序好。GROUP BY子句的內部實現也首先排序列值,因為排序的列值使相鄰的匹配值可以快速地集合。因此和ORDER BY子句類似,GROUP BY子句也從該子句所引用的列值優先排序中獲益。
5、小心地命名存儲過程
存儲過程的名稱很重要,不應該使用前綴sp_來命名存儲過程。開發人員常常為其存儲過程添加sp_前綴以便簡單地識別存儲過程。但是,SQL Server判斷帶有這個sp_前綴的存儲過程可能是存在於master數據庫之中的系統存儲過程。當帶有sp_前綴的存儲過程被提交執行時,SQL Server按照以下順序查找循環存儲過程:
- 在master數據庫中;
- 根據提供的限定符(數據庫名或所有者)在當前數據庫中;
- 如果未指定架構,在當前數據庫中使用dbo作為架構;
因此,盡管用戶創建的具有sp_前綴的存儲過程存在於當前數據庫,但是仍然首先查找master數據庫。甚至在存儲過程以數據庫名稱限定時,仍然如此。
存儲過程在第一次執行將其執行計划添加到過程緩沖,隨后的執行重用緩沖的執行計划除非需要計划的重編譯。
注意,在SQL Server嘗試在過程緩沖中定位存儲過程的計划之前出發SP_CacheMiss事件。SP:CacheMiss事件是由於SQL Server在master數據庫中查找存儲過程而發生,盡管存儲過程的本次執行已經正確地使用用戶數據庫名稱限定。
sp_前綴的這種特征在創建一個名稱與現有的系統存儲過程相同的存儲過程時變得更加有趣。
CREATE PROC sp_addmessage @param1 NVARCHAR(25) AS PRINT '@param1 = ' + @param1 GO EXEC DataExample.dbo.sp_addmessage 'DataExample'
這個用戶自定義存儲過程的執行導致master數據庫中的系統存儲過程sp_addmessage的執行。輸出如下:
消息 8114,級別 16,狀態 5,過程 sp_addmessage,第 0 行
從數據類型 varchar 轉換為 int 時出錯。
看到,每次都執行master里面的那個sp_addmessage這個存儲過程了。
請不要試圖在這個存儲過程上執行兩次DROP PROC語句,在第二次執行時,master數據庫中的系統存儲過程將被卸載。
這就是不要為存儲過程使用sp_前綴的原因。
七、減少網絡傳輸數量
數據庫應用程序往往執行多個查詢以實現數據庫操作。除了優化單個查詢的性能之外,優化批的性能也很重要。為了減少多次網絡傳輸的開銷,考慮一下技術:
- 同時執行多個查詢;
- 使用SET NOCOUNT;
1、同時執行多個查詢
將一組查詢作為批或存儲過程同時提交是可取的方式。除了減少數據應用程序和服務器之間的網絡傳輸,存儲過程還提供多種性能和管理的好處。這意味着應用程序中的代碼必須能夠處理多個結果集,還意味着T-SQL代碼可能需要處理XML數據或其他大的數據集,而不是單行插入或更新。
2、使用SET NOCOUNT
在執行一個批或存儲過程時,還必須考慮一個事實。在批或存儲過程中的每個查詢執行之后,服務器報告所影響的行數。
(<Number> row(s) affected)
這個信息返回給數據庫應用程序並增加網絡開銷。使用T-SQL語句SET NOCOUNT來避免這個開銷,如下所示。
SET NOCOUNT ON <SQL> SET NOCOUNT OFF
注意,SET NOCOUNT語句不會造成任何存儲過程的重編譯問題。
八、降低事務開銷
SQL Server中的每個操作查詢被作為一個原子操作來進行,這樣可使數據庫表的狀態保持一致性。SQL Server自動進行這一工作,並且這不能被禁用。如果一個一致性的狀態到另一個一致性狀態的轉換需要多個數據庫查詢,則跨越這些查詢的原子性必須使用顯示定義的數據庫事務來維護。每個原子操作的新舊狀態被維護於事務日志(磁盤)中以確保持久性,這保證了院子操作的結果在成功完成之后不會丟失。原子操作在其執行期間使數據庫所與其他數據操作隔離。根據事務的特性,對降低事務開銷有兩個總體的建議:
- 減少日志開銷;
- 減少鎖開銷;
1、減少日志開銷
數據庫查詢可能由多個數據操縱查詢組成。如果每個查詢分別維護原子性,那么在事務日志磁盤上要進行太多的磁盤寫入操作以維護每個原子操作的持久性。因為磁盤活動與內存和CPU活動相比非常慢,過多的磁盤活動增加數據功能的執行時間。
創建一個測試表:
CREATE TABLE t1(c1 TINYINT)
插入一千行的操作(每插入一行,寫一次日志):
--插入1萬行 DECLARE @Count INT = 1; WHILE @Count <= 10000 BEGIN INSERT INTO t1 VALUES(@Count % 256); SET @Count = @Count + 1; END
每條Insert語句的執行都是原子操作,SQL Server將為其每次操作寫入事務日志。
減少日志磁盤寫操作數量的一個簡單方法是將操作查詢包含在一個明確的日志當中(插一千行寫一次日志),如
--插入1千Count INT = 1; BEGIN TRANSACTION WHILE @Count <= 1000 BEGIN INSERT INTO t1 VALUES(@Count % 256); SET @Count = @Count + 1; END COMMIT
定義的事務范圍(BEGIN TRANSACTION和COMMIT命令之間)將原子性擴展到包含在事務中的多條INSERT語句。這減少了日志磁盤寫操作數量並改進數據庫功能的性能。
為了測試這一條理論,在每個WHILE循環前后運行如下T-SQL命令
DBCC SQLPERF(LOGSPACE)
最佳的方法是處理數據集而不是單個行。WHILE循環可能是個天生開銷很大的操作,就像游標一樣。所以,運行一個避免使用WHILE循環而代之於基於數據集的方法將會更好。
SELECT TOP 1000 IDENTITY(INT,1,1) AS n INTO #Tally FROM Master.dbo.SysColumns sc1,Master.dbo.SysColumns sc2 BEGIN TRANSACTION INSERT INTO dbo.t1(c1) SELECT(n % 256) FROM #Tally; COMMIT TRANSACTION DROP TABLE #Tally
運行這個查詢並在前后使用DBCC SQLPERF()函數,將會顯示日志已用空間的增長少於4%,並且,它運行時間幾乎是瞬間完成。
但是要注意一點,在日志中包含太多的數據操縱查詢,事務的持續時間將加長。在這時候,所有試圖訪問事務中引用資源的其他查詢將被阻塞。
2、減少鎖開銷
默認情況下,所有4種SQL語句(SELECT、INSERT、UPDATE和DELETE)都使用數據庫鎖來將其工作與其他SQL語句隔離。這種鎖管理增加了查詢的性能開銷。查詢性能可以通過請求更少的鎖來改進。而且,其他查詢的性能也因為獲得自己的鎖所需要等待的時間較短而得到改進。
默認情況下,SQL Server可以提供行級鎖,對於工作於大量行的查詢,在所有單獨的行上請求行鎖為鎖管理進程增加了很大的開銷。可以減小鎖的粒度來減少鎖開銷,比如,使用頁面或表級別鎖。SQL Server考慮鎖的開銷來動態地進行鎖的升級。因此,一般來說,沒有必要手工提升鎖的級別。但是,如果有必要,可以使用如下的鎖提示來編程控制查詢的並發性。
SELECT * FROM <TableName> WITH(PAGLOCK) --使用頁級鎖
相似地,默認情況下,SQL Server為SELECT語句使用於INSERT、UPDATE和DELETE語句不通的鎖。這使SELECT語句可以讀取沒有被修改的數據。在某些情況下,數據可能相當靜態,不會經受太多修改。這種情況下,可以使用以下方法之一來減少SELECT語句的鎖開銷。
將數據庫標識為READ_ONLY(只讀),如下所示。
ALTER DATABASE <DatabaseName> SET READ_ONLY
這使用戶能夠從數據庫中檢索數據,但是阻止他們修改數據。這個設置立刻生效。如果偶然需要修改數據庫,它可以暫時地被轉換為READ_WRITE(讀寫)模式:
ALTER DATABASE <DatabaseName> SET READ_WRITE <Database modifications> ALTER DATABASE <DatabaseName> SET READ_ONLY
在一個文件組中放置特定表,並且標識該文件組為只讀:
--添加一個具有一個文件的新的文件組到數據庫中 ALTER DATABASE DataExample ADD FILEGROUP ReadOnlyFileGroup ALTER DATABASE DataExample ADD FILE(NAME=ReadOnlyFile,FILENAME='C:\adw_1.ndf') TO FILEGROUP ReadOnlyFileGroup --在新文件組上創建特定表 CREATE TABLE T1(C1 INT,C2 INT) ON ReadOnlyFileGroup CREATE CLUSTERED INDEX I1 ON T1(C1) INSERT INTO T1 VALUES(1,1) --或者,將現有表移動到新文件組 CREATE CLUSTERED INDEX I1 ON T1(C1) WITH DROP_EXISTING ON ReadOnlyFileGroup --設置文件組屬性為只讀 ALTER DATABASE DataExample MODIFY FILEGROUP ReadOnlyFileGroup READONLY
這使得可以只限制在特定文件組上的表的訪問為只讀,而保持其他文件組上表的數據訪問為讀寫狀態。這個文件組設置立即生效。如果特定表偶爾需要修改,則對應文件組的屬性可以臨時被轉換為讀寫模式。
阻止SELECT語句請求鎖
SELECT * FROM <TableName> WITH(NOLOCK)
這阻止該SELECT語句請求任何鎖,只適用於SELECT語句。盡管NOLOCK提示不能直接用在操作查詢(INSERT、UPDATE、DELETE)中應用的表上,但是它可以用在操作查詢的數據檢索部分。
DELETE PersonTenThousand FROM PersonTenThousand WITH(NOLOCK) INNER JOIN Province WITH(NOLOCK) ON PersonTenThousand.PId = Province.Id AND Province = 0