轉載:https://blog.csdn.net/Dove_Knowledge/article/details/71439702
一、簡介
視圖(View)可以看作定義在SQL Server上的虛擬表.視圖正如其名字的含義一樣,是另一種查看數據的入口.常規視圖本身並不存儲實際的數據,而僅僅存儲一個Select語句和所涉及表的metadata。
視圖簡單理解如下:
通過視圖,客戶端不再需要知道底層table的表結構及其之間的關系。視圖提供了一個統一訪問數據的接口。
二、為什么要使用視圖(View)
從上面的圖中,我們不難發現,使用視圖將會得到如下好處:
- 視圖隱藏了底層的表結構,簡化了數據訪問操作
- 因為隱藏了底層的表結構,所以大大加強了安全性,用戶只能看到視圖提供的數據
- 使用視圖,方便了權限管理,讓用戶對視圖有權限而不是對底層表有權限進一步加強了安全性
- 視圖提供了一個用戶訪問的接口,當底層表改變后,改變視圖的語句來進行適應,使已經建立在這個視圖上客戶端程序不受影響
三、視圖(View)的分類
視圖在SQL中可以分為三類
- 普通視圖(Regular View)
- 索引視圖(Indexed View)
- 分割視圖(Partitioned View)
下面從這幾種視圖類型來談視圖。
1)普通視圖(Regular View)
普通視圖由一個Select語句所定義,視圖僅僅包含其定義和被引用表的metadata.並不實際存儲數據。MSDN中創建視圖的模版如下:
CREATE VIEW [ schema_name . ] view_name [ (column [ ,...n ] ) ]
[ WITH <view_attribute> [ ,...n ] ]
AS select_statement
[ WITH CHECK OPTION ] [ ; ]
<view_attribute> ::=
{
[ ENCRYPTION ]
[ SCHEMABINDING ]
[ VIEW_METADATA ] }
參數還是比較少的,現在解釋一下上面的參數:
ENCRYPTION:視圖是加密的,如果選上這個選項,則無法修改.創建視圖的時候需要將腳本保存,否則再也不能修改了
SCHEMABINDING:和底層引用到的表進行定義綁定。這個選項選上的話,則視圖所引用到的表不能隨便更改構架(比如列的數據類型),如果需要更改底層表構架,則先drop或者alter在底層表之上綁定的視圖.
VIEW_METADATA:這個是個很有意思的選項.正如這個選項的名稱所指示,如果不選擇,返回給客戶端的metadata是View所引用表的metadata,如果選擇了這個選項,則返回View的metadata.再通俗點解釋,VIEW_METADATA可以讓視圖看起來貌似表一樣。View的每一個列的定義等直接告訴客戶端,而不是所引用底層表列的定義。
WITH Check Option:這個選項用於更新數據做限制,下面會在通過視圖更新數據一節解釋.
當然了,創建視圖除了需要符合上面的語法規則之外,還有一些規則需要遵守:
- 在View中,除非有TOP關鍵字,否則不能用Order By子句(如果你一意孤行要用Order by,這里有個hack是使用Top 100 percent…..)
- View在每個Schema中命名必須獨一無二
- View嵌套不能超過32層(其實實際工作中誰嵌套超過兩層就要被打PP了-.-)
- Compute,compute by,INTO關鍵字不允許出現在View中
- View不能建立在臨時表上
- View不能對全文索引進行查詢
2)索引視圖(Indexed View)
在普通的視圖的基礎上,為視圖建立唯一聚集索引,這時這個視圖就變成了索引視圖.套用上面漫畫的公式:視圖+聚集索引=索引視圖
索引視圖可以看作是一個和表(Table)等效的對象!
SQL Server中的索引視圖和Oracle中的Materialized View是一個概念.想要理解索引視圖,必須先理解聚集索引。聚集索引簡單來說理解成主鍵,數據庫中中的數據按照主鍵的順序物理存儲在表中,就像新華字典,默認是按照ABCD….這樣的方式進行內容設置。ABCD….就相當於主鍵.這樣就避免了整表掃描從而提高了性能.因此一個表中只能有一個聚集索引。
對於索引視圖也是,為一個視圖加上了聚集索引后。視圖就不僅僅再是select語句和表的metadata了,索引視圖會將數據物理存在數據庫中,索引視圖所存的數據和索引視圖中所涉及的底層表保持同步。
理解了索引視圖的原理之后,我們可以看出,索引視圖對於LDAP這種大量數據分析和查詢來說,性能將會得到大幅提升。尤其是索引視圖中有聚合函數,涉及大量高成本的JOIN,因為聚合函數計算的結果物理存入索引視圖,所以當面對大量數據使用到了索引視圖之后,並不必要每次都進行聚合運算,這無疑會大大提升性能.
而同時,每次索引視圖所涉及的表進行Update,Insert,Delete操作之后,SQL Server都需要標識出改變的行,讓索引視圖進行數據同步.所以LDTP這類增刪改很多的業務,數據庫需要做大量的同步操作,這會降低性能。
談完了索引視圖的基本原理和好處與壞處之后,來看看在SQL Server中的實現:
在SQL Server中實現索引視圖是一件非常,簡單的事,只需要在現有的視圖上加上唯一聚集索引.但SQL Server對於索引視圖的限制卻使很多DBA對其並不青睞:
比如:
- 索引視圖涉及的基本表必須ANSI_NULLS設置為ON
- 索引視圖必須設置ANSI_NULLS和QUOTED_INDETIFIER為ON
- 索引視圖只能引用基本表
- SCHEMABINDING必須設置
- 定義索引視圖時必須使用Schema.ViewName這樣的全名
- 索引視圖中不能有子查詢
- avg,max,min,stdev,stdevp,var,varp這些聚合函數不能用
………………
還有更多…就不一一列舉了,有興趣的請自行Google之.
下面我來通過一個例子來說明索引視圖:
假設在adventureWorks數據庫中,我們有一個查詢:
SELECT p.Name,s.OrderQty
FROM Production.Product p
inner join Sales.SalesOrderDetail s
ON p.ProductID=s.ProductID
這個查詢的執行計划:
這時,我建立視圖並在這個視圖上建立唯一聚集索引:
--建立視圖
CREATE VIEW v_Test_IndexedView
WITH SCHEMABINDING
AS
SELECT p.Name,s.OrderQty,s.SalesOrderDetailID
FROM Production.Product p
inner join Sales.SalesOrderDetail s
ON p.ProductID=s.ProductID
GO
--在視圖上建立索引
CREATE UNIQUE CLUSTERED INDEX indexedview_test1
ON v_Test_IndexedView(SalesOrderDetailID)
接下來,套用劉謙的台詞:見證奇跡的時刻到了,我們再次執行之前的查詢:
從上面這個例子中,可以體會到索引視圖的強大威力,即使你的查詢語句中不包含這個索引視圖,查詢分析器會自動選擇這個視圖,從而大大的提高了性能.當然,這么強力的性能,只有在SQL SERVER企業版和開發版才有哦(雖然我見過很多SQL Server的開發人員讓公司掏着Enterprise版的錢,用着Express版的功能……)
3)分割視圖(Partitioned View)
分割視圖其實從微觀實現方式來說,整個視圖所返回的數據由幾個平行表(既是幾個表有相同的表結構,也就是列和數據類型,但存儲的行集合不同)進行UNION連接(對於UNION連接如果不了解,請看我之前的博文)所獲得的數據集.
分割視圖總體上可以分為兩種:
1.本地分割視圖(Local Partitioned View)
2.分布式分割視圖(Distributed Partitioned View)
因為本地分割視圖僅僅是為了和SQL Server 2005之前的版本的一種向后兼容,所以這里僅僅對分布式分割視圖進行說明.
分布式分割視圖其實是將由幾個由不同數據源或是相同數據源獲得的平行數據集進行連接所獲得的,一個簡單的概念圖如下:
上面的視圖所獲得的數據分別來自三個不同數據源的表,每一個表中只包含四行數據,最終組成了這個分割視圖.
使用分布式分割視圖最大的好處就是提升性能.比如上面的例子中,我僅僅想取得ContactID為8這位員工的信息,如果通過分布式視圖獲取的話,SQL Server可以非常智能的僅僅掃描包含ContactID為8的表2,從而避免了整表掃描。這大大減少了IO操作,從而提升了性能.
這里要注意的是,分布式分割視圖所涉及的表之間的主鍵不能重復,比如上面的表A ContactID是1-4,則表B的ContactID不能是2-8這個樣子.
還有一點要注意的是,一定要為分布式分割索引的主鍵加Check約束,從而讓SQL Server的查詢分析器知道該去掃描哪個表,下面來看個例子.
在微軟示例數據庫AdventureWorks數據庫,我通過ContactID從前100行和100-200行的數據分別存入兩個表,Employee100和Employee200,代碼如下:
--create Employee100
SELECT TOP 100 * INTO Employee100
FROM HumanResources.Employee
ORDER BY EmployeeID
--create Employee200
SELECT * INTO Employee200
FROM
(SELECT TOP 100 *
FROM HumanResources.Employee
WHERE EmployeeID NOT IN (SELECT TOP 100 EmployeeID FROM HumanResources.Employee ORDER BY EmployeeID)
ORDER BY HumanResources.Employee.EmployeeID)AS e
這時來建立分布式分割視圖:
CREATE VIEW v_part_view_test
AS
SELECT * FROM Employee100
UNION
SELECT * FROM Employee200
這時我們對這個索引進行查詢操作:
SELECT * FROM v_part_view_test
WHERE EmployeeID=105
下面是執行計划:
通過上圖可以看出,通過這種分割的方式,執行計划僅僅是掃描Employee200,從而避免了掃描所有數據,這無疑提升了性能.
所以,當你將不同的數據表之間放到不同的服務器或是使用RAID5磁盤陣列時,分布式分割視圖則進一步會提升查詢性能.
使用分布式分割視圖能夠在所有情況下都提升性能嗎?那妥妥的不可能.使用這種方式如果面對的查詢包含了聚合函數,尤其是聚合函數中還包含distinct。或是不加where條件進行排序.那絕對是性能的殺手。因為聚合函數需要掃描分布式分割視圖中所有的表,然后進行UNION操作后再進行運算.
四、通過視圖(View)更新數據
通過視圖更新數據是我所不推薦的.因為視圖並不能接受參數.我更推薦使用存儲過程來實現.
使用View更新數據和更新Table中數據的方式完全一樣(前面說過,View可以看作是一個虛擬表,如果是索引視圖則是具體的一張表)
通過視圖來更新數據需要注意以下幾點
1.視圖中From子句之后至少有一個用戶表
2.View的查詢無論涉及多少張表,一次只能更新其中一個表的數據
3.對於表達式計算出來的列,常量列,聚合函數算出來的列無法更新
4.Group By,Having,Distinct關鍵字不能影響到的列不能更新
這里說一下創建View有一個WITH Check Option選項,如果選擇這個選項,則通過View所更新的數據必須符合View中where子句所限定的條件,比如:
我創建一個View:
五、視圖(View)中的幾個小技巧
1.通過視圖名稱查到視圖的定義
SELECT * FROM sys.sql_modules
WHERE object_id=OBJECT_ID('視圖名稱')
2.前面說過,普通視圖僅僅存儲的是select語句和所引用表的metadata,當底層表數據改變時,有時候視圖中表的metadata並沒有及時同步,可以通過如下代碼進行手動同步。
EXEC sp_refreshview 視圖名稱
六、視圖(View)的最佳實踐
視圖(View)的最佳實踐
這是我個人一些經驗,歡迎補充
- 一定要將View中的Select語句性能調到最優(貌似是廢話,不過真理都是廢話…)
- View最好不要嵌套,如果非要嵌套,最多只嵌套一層
- 能用存儲過程和自定義函數替代View的,盡量不要使用View,存儲過程會緩存執行計划,性能更優,限制更少
- 在分割視圖上,不要使用聚合函數,尤其是聚合函數還包含了Distinct
- 在視圖內,如果Where子句能加在視圖內,不要加在視圖外(因為調用視圖會返回所有行,然后再篩選,性能殺手,如果你還加上了order by…..)
總結
文中對視圖的三種類型進行了詳解.每種視圖都有各自的使用范圍,使用得當會將性能提升一個檔次,而使用不當反而會拖累性能.
我想起一句名言:“Practice makes perfect”...多練習,多思考,一定會有收獲。