關於表變量是什麼(和表變量不是什麼),以及和臨時表的比較讓不少人很是困惑。雖然網上已經有了不少關於它們的文章,但我並無發現一篇比較全面的。在本篇文章中,咱們將探索表變量和臨時表是什麼(以及不是什麼),而後咱們經過使用臨時表和表變量對其解密。
表變量
表變量在SQL Server 2000中首次被引入,那麼,什麼是表變量呢?微軟在BOL (Declare @local_variable)中定義其爲一個類型爲表的變量。它的具體定義包括列定義,列名,數據類型和約束。而在表變量中可使用的約束包括主鍵約束,惟一約束,Null約束和Check約束(外鍵約束不能在表變量中使用).定義表變量的語句是和正常使用Create table定義表語句的子集。只是表變量經過DECLARE @local_variable 語句進行定義。
經過參考1能夠知道:
1) 表變量擁有特定做用域(在當前批處理語句中,但不在任何當前批處理語句調用的存儲過程和函數中),表變量在批處理結束後自動被清除。
2) 參考6中在"Recompilations Due to Certain Temporary Table Operations" 環節討論了臨時表在會致使存儲過程強制被重複編譯的各類緣由,但這些緣由並不適用於表變量。表變量和臨時表比起來會產生更少的存儲過程重編譯。
3) 針對表變量的事務僅僅在更新數據時生效,因此鎖和日誌產生的數量會更少。
4) 因爲表變量的做用域如此之小,並且不屬於數據庫的持久部分,因此事務回滾不會影響表變量。
表變量能夠在其做用域內像正常的表同樣使用。更確切的說,表變量能夠被當成正常的表或者表表達式同樣在select,delete,update,insert語句中使用。可是表變量不能在相似「SELECT select_list INTO table_variable」 這樣的語句中使用。而在SQL Server 2000中,表變量也不能被用於「INSERT INTO table_variable EXEC stored_procedure」這樣的語句中。
表變量不能作以下事情:
1.雖然表變量是一個變量,可是其不能賦值給另外一個變量。
2.check約束,默認值,和計算列不能引用自定義函數。
3.不能爲約束命名。
4.不能Truncate表變量
5.不能向標識列中插入顯式值(也就是說表變量不支持SET IDENTITY_INSERT ON)
臨時表
在深刻臨時表以前,咱們首先須要討論一下會話(Session),一個會話僅僅是一個客戶端到數據引擎的鏈接。在SQL Server Management Studio(SSMS)中,每個查詢窗口都會和數據庫引擎創建鏈接。一個應用程序能夠和數據庫創建一個或多個鏈接,除此以外,應用程序還可能創建鏈接後一直不釋放直到應用程序結束,也可能使用完釋放鏈接須要時創建鏈接。
那麼,什麼是臨時表?在BOL (CREATE TABLE)中,咱們能夠知道臨時表和以Create table語句建立的表有着相同的物理構成,但臨時表與正常的表不一樣之處有:
1) 臨時表的名字不能超過116個字符,這是因爲數據庫引擎爲了辨別不一樣會話創建不一樣的臨時表,因此會自動在臨時表的名字後附加一串
2) 局部臨時表(以「#」開頭命名的)做用域僅僅在當前的鏈接內,從在存儲過程當中創建局部臨時表的角度來看,局部臨時表會在下列狀況被Drop:
a.顯式調用DROP Table語句
b.當局部臨時表在存儲過程內被建立時,存儲過程結束也就意味着局部臨時表被DROP
c.當前會話結束,在會話內建立的全部局部臨時表都會被Drop
3) 全局臨時表(以「##」開頭命名的)在全部的會話內可見,因此在建立全局臨時表以前首先檢查其是否存在,不然若是已經存在,你將會獲得重複建立對象的錯誤.
a.全局臨時表會在建立其的會話結束後被DROP,其它會話將不能對全局臨時表進行引用。
b.引用是在語句級別進行,好比說下面例子:
i.創建新的查詢窗口,運行以下語句:
create table ##temp (RowID int)
ii.再次開啓一個新的查詢建立,使用以下語句每5秒中對全局臨時表進行引用
while 1=1 begin select * from ##temp waitfor delay '00:00:05' end
iii.回到第一個窗口,關閉窗口
iv.在下一個循環引用全局臨時表時,將產生錯誤
4) 不能對臨時表進行分區。
5) 不能對臨時表加外鍵約束
6) 臨時表內列的數據類型不能定義成沒有在TempDb中沒有定義自定義數據類型(自定義數據類型是數據庫級別的對象,而臨時表屬於TempDb),因爲TempDb在每次SQL Server重啓後會被自動建立,因此你必須使用startup stored procedure來爲TempDb建立自定義數據類型。你也能夠經過修改Model數據庫來達到這一目標。
7) XML列不能定義成XML集合的形式,除非這個集合已經在TempDb中定義
臨時表既能夠經過Create Table語句建立,也能夠經過」SELECT <select_list> INTO #table」語句建立。你還能夠針對臨時表使用」INSERT INTO #table EXEC stored_procedure」這樣的語句。
臨時表能夠擁有命名的約束和索引。可是,當兩個用戶在同一時間調用同一存儲過程時,將會產生」There is already an object named ‘<objectname>’ in the database」這樣的錯誤。因此最好的作法是不用爲創建的對象進行命名,而使用系統分配的在TempDb中惟一的。6
參考6談論了不少因爲臨時表而致使的存儲過程重編譯的緣由以及避免的方法。
誤區
誤區1.表變量僅僅在內存中。
誤區2.臨時表僅僅存儲在物理介質中
這兩種觀點都是明顯的誤區,在參考1的Q4節。表變量都是在TempDb數據庫中建立,由於表變量存儲的數據有可能超過物理內存。除此以外,咱們發現只要內存足夠,表變量和臨時表都會在內存中建立和處理。它們也一樣能夠在任什麼時候間被存入磁盤。
如何證實這點?請看下面代碼(在SQL Server 2000到2008中都有效)
-- make a list of all of the user tables currently active in the
-- TempDB database
if object_id('tempdb..#tempTables') is not null drop table #tempTables
select name into #tempTables from tempdb..sysobjects where type ='U'
-- prove that even this new temporary table is in the list.
-- Note the suffix at the end of it to uniquely identify the table across sessions.
select * from #tempTables where name like '#tempTables%'
GO
-- create a table variable
declare @MyTableVariable table (RowID int)
-- show all of the new user tables in the TempDB database.
select name from tempdb..sysobjects
where type ='U' and name not in (select name from #tempTables)
還有一些「證實」臨時表僅僅存在於內存中謬誤,下面我來指出其中一個:
注意表變量的名字是系統分配的,表變量的第一個字符」@」並非一個字母,因此它並非一個有效的變量名。系統會在TempDb中爲表變量建立一個系統分配的名稱,因此任何在sysobjects或sys.tables查找表變量的方法都會失敗。
正確的方法應該是我前面例子中的方法,我看到不少人使用以下查詢查表變量:
select * from sysobjects where name like'#tempTables%'
上述代碼看上去貌似很好用,但會產生多用戶的問題。你創建兩個鏈接,在第一個鏈接中建立臨時表,在第二個窗口中運行上面的語句能看到第一個鏈接建立的臨時表,若是你在第二個鏈接中嘗試操做這個臨時表,那麼可能會產生錯誤,由於這個臨時表不屬於你的會話。
誤區3.表變量不能擁有索引。
這個誤區也一樣錯誤。雖然一旦你建立一個表變量以後,就不能對其進行DDL語句了,這包括Create Index語句。然而你能夠在表變量定義的時候爲其建立索引)好比以下語句.
declare @MyTableVariable table (RowID intPRIMARY KEY CLUSTERED)
這個語句將會建立一個擁有彙集索引的表變量。因爲主鍵有了對應的彙集索引,因此一個系統命名的索引將會被建立在RowID列上。
下面的例子演示你能夠在一個表變量的列上建立惟一約束以及如何創建符合索引。
declare @temp TABLE ( RowID int NOT NULL, ColA int NOT NULL, ColB char(1)UNIQUE, PRIMARY KEY CLUSTERED(RowID, ColA))
1) SQL 並不能爲表變量創建統計信息,就像其能爲臨時表創建統計信息同樣。這意味着對於表變量,執行引擎認爲其只有1行,這也意味着針對表變量的執行計劃並非最優。雖然估計的執行計劃對於表變量和臨時表都爲1,可是實際的執行計劃對於臨時表會根據每次存儲過程的重編譯而改變(看參考1,Q2部分).若是臨時表不存在,在生成執行計劃的時候會產生錯誤。
2) 前面提到,必定創建表變量後就沒法對其進行DDL語句操做。所以若是須要爲表創建索引或者加一列,你須要臨時表。
3) 表變量不能使用select …into語句,而臨時表能夠
4) 在SQL Server 2008中,你能夠將表變量做爲參數傳入存儲過程。可是臨時表不行。在SQL Server 2000和2005中表變量也不行。
5) 做用域:表變量僅僅在當前的批處理中有效,而且對任何在其中嵌套的存儲過程等不可見。局部臨時表只在當前會話中有效,這也包括嵌套的存儲過程。但對父存儲過程不可見。全局臨時表能夠在任何會話中可見,可是會隨着建立其的會話終止而DROP,其它會話這時就不能再引用全局臨時表。
6) 排序規則:表變量使用當前數據庫的排序規則,臨時表使用TempDb的排序規則。若是它們不兼容,你還須要在查詢或者表定義中進行指定(參考7.Table Variables and Temporary Tables)
7) 你若是但願在動態SQL中使用表變量,你必須在動態SQL中定義表變量。而臨時表能夠提早定義,在動態SQL中進行引用。
說了這麼多,那麼,我該如何選擇呢?
微軟推薦使用表變量(看參考4),若是表中的行數很是小,則使用表變量。不少」網絡專家」會告訴你100是一個分界線,由於這是統計信息建立查詢計劃效率高低的開始。可是我仍是但願告訴你針對你的特定需求對臨時表和表變量進行測試。不少人在自定義函數中使用表變量,若是你須要在表變量中使用主鍵和惟一索引,你會發現包含數千行的表變量也依然性能卓越。但若是你須要將表變量和其它表進行join,你會發現因爲不精準的執行計劃,性能每每會很是差。
爲了證實這點,請看本文的附件。附件中代碼建立了表變量和臨時表.並裝入了AdventureWorks數據庫的Sales.SalesOrderDetail表。爲了獲得足夠的測試數據,我將這個表中的數據插入了10遍。而後以ModifiedDate 列做爲條件將臨時表和表變量與原始的Sales.SalesOrderDetail表進行了Join操做,從統計信息來看IO差異顯著。從時間來看錶變量作join花了50多秒,而臨時表僅僅花了8秒。
若是你須要在表創建後對錶進行DLL操做,那麼選擇臨時表吧。
臨時表和表變量有不少相似的地方。因此有時候並無具體的細則規定如何選擇哪個。對任何特定的狀況,你都須要考慮其各自優缺點並作一些性能測試。下面的表格會讓你比較其優略有了更詳細的參考。
總結
特性 |
表變量 |
臨時表 |
做用域 |
當前批處理 |
當前會話,嵌套存儲過程,全局:全部會話 |
使用場景 |
自定義函數,存儲過程,批處理 |
自定義函數,存儲過程,批處理 |
建立方式 |
DECLARE statement only.只能經過DECLEARE語句建立 |
CREATE TABLE 語句 SELECT INTO 語句. |
表名長度 |
最多128字節 |
最多116字節 |
列類型 |
可使用自定義數據類型 可使用XML集合 |
自定義數據類型和XML集合必須在TempDb內定義 |
Collation |
字符串排序規則繼承自當前數據庫 |
字符串排序規則繼承自TempDb數據庫 |
索引 |
索引必須在表定義時創建 |
索引能夠在表建立後創建 |
約束 |
PRIMARY KEY, UNIQUE, NULL, CHECK約束可使用,但必須在表創建時聲明 |
PRIMARY KEY, UNIQUE, NULL, CHECK. 約束可使用,能夠在任什麼時候後添加,但不能有外鍵約束 |
表創建後使用DDL (索引,列) |
不容許 |
容許. |
數據插入方式 |
INSERT 語句 (SQL 2000: 不能使用INSERT/EXEC). |
INSERT 語句, 包括 INSERT/EXEC. SELECT INTO 語句. |
Insert explicit values into identity columns (SET IDENTITY_INSERT). |
不支持SET IDENTITY_INSERT語句 |
支持SET IDENTITY_INSERT語句 |
Truncate table |
不容許 |
容許 |
析構方式 |
批處理結束後自動析構 |
顯式調用 DROP TABLE 語句. 當前會話結束自動析構 (全局臨時表: 還包括當其它會話語句不在引用表.) |
事務 |
只會在更新表的時候有事務,持續時間比臨時表短 |
正常的事務長度,比表變量長 |
存儲過程重編譯 |
否 |
會致使重編譯 |
回滾 |
不會被回滾影響 |
會被回滾影響 |
統計數據 |
不建立統計數據,因此全部的估計行數都爲1,因此生成執行計劃會不精準 |
建立統計數據,經過實際的行數生成執行計劃。 |
做爲參數傳入存儲過程 |
僅僅在SQL Server2008, 而且必須預約義 user-defined table type. |
不容許 |
顯式命名對象 (索引, 約束). |
不容許 |
容許,可是要注意多用戶的問題 |
動態SQL |
必須在動態SQL中定義表變量 |
能夠在調用動態SQL以前定義臨時表 |
參考:
1) INF: Frequently Asked Questions - SQL Server 2000 - Table Variables
2) T-SQL BOL (SQL 2000), table data type
3) T-SQL BOL (SQL 2008), Declare @local_variable
4) T-SQL BOL (SQL 2008), CREATE TABLE
5) Table-Valued Parameters (Database Engine)
6) Troubleshooting stored procedure recompilation
7) Local Temporary Tables and Table Variables
8) Startup stored procedure
9) Data Definition Language (DDL)
其它值得閱讀的文章:
1) Things You Didn’t Know About Temp Tables and Table Variables
-----------------------------------------------------------------------
原文連接:http://www.sqlservercentral.com/articles/Temporary+Tables/66720/
Translated by:CareySon
簡介
SQL Server每一個表中各列的數據類型的選擇一般顯得很簡單,可是對於具體數據類型的選擇的不一樣對性能的影響仍是略有差異。本篇文章對SQL Server表列數據類型的選擇進行一些探索。
一些數據存儲的基礎知識
在SQL Server中,數據的存儲以頁爲單位。八個頁爲一個區。一頁爲8K,一個區爲64K,這個意味着1M的空間能夠容納16個區。如圖1所示:
![1 1](http://static.javashuo.com/static/loading.gif)
圖1.SQL Server中的頁和區
如圖1(PS:發現用windows自帶的畫圖程序畫博客中的圖片也不錯
)能夠看出,SQL Server中的分配單元分爲三種,分別爲存儲行內數據的In_Row_Data,存儲Lob對象的LOB_Data,存儲溢出數據的Row_Overflow_data。下面咱們經過一個更具體的例子來理解這三種分配單元。
我創建如圖2所示的表。
![2 2](http://static.javashuo.com/static/loading.gif)
圖2.測試表
圖2的測試表不難看出,經過插入數據使得每一行的長度會超過每頁所能容納的最大長度8060字節。使得不只產生了行溢出(Row_Overflow_Data),還須要存儲LOB的頁.測試的插入語句和經過DBCC IND看到的分配狀況如圖3所示。
![3 3](http://static.javashuo.com/static/loading.gif)
圖3.超過8060字節的行所分配的頁
除去IAM頁,這1行數據所須要三個頁來存儲。首先是LOB頁,這類是用於存儲存在數據庫的二進制文件所設計,當這個類型的列出現時,在原有的列會存儲一個24字節的指針,而將具體的二進制數據存在LOB頁中,除去Text以外,VarBinary(max)也是存在LOB頁中的。而後是溢出行,在SQL Server 2000中,一行超過8060字節是不被容許的,在SQL Server 2005以後的版本對這個特性進行了改進,使用Varchar,nvarchar等數據類型時,當行的大小不超過8060字節時,所有存在行內In-row data,當varchar中存儲的數據過多使得整行超過8060字節時,會將額外的部分存於Row-overflow data頁中,若是update這列使得行大小減小到小於8060字節,則這行又會所有回到in-row data頁。
數據類型的選擇
在瞭解了一些基礎知識以後。咱們知道SQL Server讀取數據是以頁爲單位,更少的頁不只僅意味着更少的IO,還有更少的內存和CPU資源消耗。因此對於數據選擇的主旨是:
儘可能使得每行的大小更小
這個聽起來很是簡單,但實際上還須要對SQL Server的數據類型有更多的瞭解。
好比存儲INT類型的數據,按照業務規則,能用INT就不用BIGINT,能用SMALLINT就不用INT,能用TINYINT就不用SMALLINT。
因此爲了使每行的數據更小,則使用佔字節最小的數據類型。
1.好比不要使用DateTime類型,而根據業務使用更精確的類型,以下表:
類型 |
所佔字節 |
Date(僅日期) |
3 |
Time(僅時間) |
5 |
DateTime2(時間和日期) |
8 |
DateTimeOffSet(外加時區) |
10 |
2.使用VarChar(Max),Nvarchar(Max),varbinary(Max)來代替text,ntext和image類型
根據前面的基礎知識能夠知道,對於text,ntext和image類型來講,每一列只要不爲null,即便佔用很小的數據,也須要額外分配一個LOB頁,這無疑佔用了更多的頁。而對於Varchar(Max)等數據類型來講,當數據量很小的時候,存在In-row-data中就能知足要求,而不用額外的LOB頁,只有當數據溢出時,纔會額外分配LOB頁,除此以外,Varchar(Max)等類型支持字符串操做函數好比:
- COL_LENGTH
- CHARINDEX
- PATINDEX
- LEN
- DATALENGTH
- SUBSTRING
3.對於僅僅存儲數字的列,使用數字類型而不是Varchar等。
由於數字類型佔用更小的存儲空間。好比存儲123456789使用INT類型只須要4個字節,而使用Varchar就須要9個字節(這還不包括Varchar還須要佔用4個字節記錄長度)。
4.若是沒有必要,不要使用Nvarchar,Nchar等以「字」爲單位存儲的數據類型。這類數據類型相比varchar或是char須要更多的存儲空間。
5.關於Char和VarChar的選擇
這類比較其實有一些了。若是懶得記憶,大多數狀況下使用Varchar都是正確的選擇。咱們知道Varchar所佔用的存儲空間由其存儲的內容決定,而Char所佔用的存儲空間由定義其的長度決定。所以Char的長度不管存儲多少數據,都會佔用其定義的空間。因此若是列存儲着像郵政編碼這樣的固定長度的數據,選擇Char吧,不然選擇Varchar會比較好。除此以外,Varchar相比Char要多佔用幾個字節存儲其長度,下面咱們來作個簡單的實驗。
首先咱們創建表,這個表中只有兩個列,一個INT類型的列,另外一個類型定義爲Char(5),向其中插入兩條測試數據,而後經過DBCC PAGE來查看其頁內結構,如圖4所示。
圖4.使用char(5)類型,每行所佔的空間爲16字節
下面咱們再來看改成Varchar(5),此時的頁信息,如圖5所示。
![5 5](http://static.javashuo.com/static/loading.gif)
圖5.Varchar(5),每行所佔用的空間爲20字節
所以能夠看出,Varchar須要額外4個字節來記錄其內容長度。所以,當實際列存儲的內容長度小於5字節時,使用char而不是varchar會更節省空間。
關於Null的使用
關於Null的使用也是略有爭議。有些人建議不要容許Null,所有設置成Not Null+Default。這樣作是因爲SQL Server比較時就不會使用三值邏輯(TRUE,FALSE,UNKNOWN),而使用二值邏輯(True,False),而且查詢的時候也再也不須要IsNull函數來替換Null值。
但這也引出了一些問題,好比聚合函數的時候,Null值是不參與運算的,而使用Not Null+Default這個值就須要作排除處理。
所以Null的使用還須要按照具體的業務來看。
考慮使用稀疏列(Sparse)
稀疏列是對 Null 值採用優化的存儲方式的普通列。 稀疏列減小了 Null 值的空間需求,但代價是檢索非 Null 值的開銷增長。 當至少可以節省 20% 到 40% 的空間時,才應考慮使用稀疏列。
稀疏列在SSMS中的設置如圖6所示。
![6 6](http://static.javashuo.com/static/loading.gif)
圖6.稀疏列
更具體的稀疏列如何能節省空間,請參看MSDN。
對於主鍵的選擇
對於主鍵的選擇是表設計的重中之重,由於主鍵不只關係到業務模型,更關係到對錶數據操做的的效率(由於主鍵會處於B樹的非葉子節點中,對樹的高度的影響最多)。關於主鍵的選擇,我以前已經有一篇文章關於這點:從性能的角度談SQL Server彙集索引鍵的選擇,這裏就再也不細說了。
總結
本篇文章對於設計表時,數據列的選擇進行了一些探尋。好的表設計不只僅是能知足業務需求,還可以知足對性能的優化。
簡介
SQL Server中的複製(Replication)是SQL Server高可用性的核心功能之一,在我看來,複製指的並不只僅是一項技術,而是一些列技術的集合,包括從存儲轉發數據到同步數據到維護數據一致性。使用複製功能不只僅須要你對業務的熟悉,還須要對複製功能的總體有一個全面的瞭解,本系列文章旨在對SQL Server中的複製進行一個簡單全面的探討。(PS:在個人上篇文章中我發現某些文章的圖片使用mspaint手繪更有感受,但被不少人吐槽,所以在不考慮我的羞恥感的前提下,本系列文章中的一些圖片繼續使用mspaint
)。
複製是什麼
複製,英文是Replication,這個詞源自於拉丁文replicare,原意是重複。SQL Server中的複製也是這個意思,複製的核心功能是存儲轉發,意味着在一個在一個位置增刪改了數據之後,重複這個動做到其餘的數據源,概念如圖1所示。
![1 1](http://static.javashuo.com/static/loading.gif)
圖1.複製的基本概念
固然,上面的這個模型是複製最簡單的模型,實際中的模型可能會複雜不少,可是大多數使用複製的緣由能夠分爲以下幾類:
1.負載均衡----經過將數據複製到其它數據庫服務器來減小當前服務器的負載,好比說最典型的應用就是分發數據來分離OLTP和OLAP環境。
2.分區----將常用的數據和歷史數據隔離,將歷史數據複製到其它數據庫中
3.受權----將一部分數據提供給須要使用數據的人,以供其使用
4.數據合併-每一個區域都有其各自的數據,將其數據進行合併。好比一個大公司,每一個地區都有其各自的銷售數據,總部須要彙總這些數據。
5.故障轉移----複製全部數據,以便故障時進行轉移。
雖然須要使用複製的緣由多種多樣,可是在使用以前你首先要了解複製技術所需的組成元素。
複製的組成部分
複製的概念很像發行雜誌的模型,從發行商那裏出版後,須要經過報刊亭等地方分發到訂閱雜誌的人手裏。對於SQL Server複製來講,這個概念也是如此。對於SQL Server複製來講,發行商,報刊亭,訂閱者分別對應的是發佈服務器,分發服務器,訂閱服務器。概念如圖2所示。
![2 2](http://static.javashuo.com/static/loading.gif)
圖2.發佈分發訂閱的基本概念
發佈服務器
圖2中的發佈服務器包含了須要被髮布的數據庫。也就是須要向其它數據源分發內容的源數據庫。固然,被髮布的數據首先須要被容許發佈。關於這裏的詳細設置會在文章後面提到。
分發服務器
圖2中的分發服務器包含了分發數據庫,分發數據庫的做用是存儲轉發發佈服務器發過來的數據。一個分發服務器支持多個發佈服務器,就像一個報刊亭能夠出售多個出版社所出的雜誌同樣。同理,分發服務器也能夠和發佈服務器是同一個實例,這就像出版商不經過報刊亭,本身直接販賣雜誌同樣。
訂閱服務器
圖2中的訂閱服務器包含了發佈服務器所發佈的數據的副本。這個副本能夠是一個數據庫,或者一個表,甚至是一個表的子集。根據不一樣的設置,有些發佈服務器發佈的更新到訂閱服務器就是隻讀的(好比說用於出報表的OLAP環境),或者是訂閱服務器也能夠進行更新來將這些改變提交到發佈服務器。
發佈和文章
發佈指的是能夠發佈的文章的集合,這些文章包括表,存儲過程,視圖和用戶自定義函數,如圖3所示。
![3 3](http://static.javashuo.com/static/loading.gif)
圖3.能夠發佈的內容
當咱們發佈表時,還能夠根據限定條件只發布表的子集。
訂閱
訂閱是相對發佈的一個概念,訂閱定義了訂閱服務器從哪一個分發服務器接收發布。有兩類訂閱方式,推送訂閱(Push)和請求訂閱(Pull),根據名字就能夠望文生義的知道,在推送訂閱的狀況下,當發佈服務器產生更新時,分發服務器直接更新訂閱的內容,而請求訂閱須要訂閱服務器按期查看分發服務器是否有可用更新,若是存在可用更新,則訂閱服務器更新數據。
複製類型
SQL Server將複製方式分爲三大類,每個發佈只能有一種複製類型,分別爲:快照複製,事務複製和合並複製。
快照複製
快照複製將發佈的全部表作成一個鏡像,而後一次性複製到訂閱服務器。中間的更新不會像其它複製類型那樣自動傳送到訂閱服務器。由這個概念不難看出,快照複製的特色會是:
1.佔用網絡寬帶,由於一次性傳輸整個鏡像,因此快照複製的內容不該該太大。
2.適合那些更新不頻繁,但每次更新都比較大的數據。好比企業員工信息表,每半年更新一次這類的業務場景。
3.適合訂閱服務器是OLAP只讀的環境。
來自MSDN的配圖能很好的闡述快照複製,如圖4所示。
![snap snap](http://static.javashuo.com/static/loading.gif)
圖4.快照複製
事務複製
事務複製就像其名字同樣,複製事務。在第一次設置好事務複製後,發佈的表、存儲過程等將會被鏡像,以後每次對於發佈服務器所作的改動都會以日誌的方式傳送到訂閱服務器。使得發佈服務器和訂閱服務器幾乎能夠保持同步。所以,能夠看出事務複製的特色是:
1.發佈服務器和訂閱服務器內容基本能夠同步
2.發佈服務器,分發服務器,訂閱服務器之間的網絡鏈接要保持暢通。
3.訂閱服務器也能夠設置成請求訂閱,使得訂閱服務器也能夠不用一直和分發服務器保持鏈接。
4.適用於要求實時性的環境。
來自MSDN的配圖能很好的闡述事務複製,如圖5所示
![grid.ai grid.ai](http://static.javashuo.com/static/loading.gif)
圖5.事務複製
合併複製
合併複製即容許發佈服務器更新數據庫,也容許訂閱服務器更新數據。按期將這些更新進行合併,使得發佈的數據在全部的節點上保持一致。所以,有可能發佈服務器和訂閱服務器更新了一樣的數據,當衝突產生時,並非徹底按照發布服務器優先來處理衝突,而是根據設置進行處理,這些會在後續文章中講到。
來自MSDN的配圖能很好的闡述合併複製,如圖6所示。
![merge merge](http://static.javashuo.com/static/loading.gif)
圖6.合併複製
創建一個簡單的事務複製
下面我進行一個簡單的事務複製。首先,在本地安裝兩個SQL Server實例,我本機安裝的兩個實例分別爲SQL Server 2008R2和SQL Server 2012,其中,SQL Server 2008R2做爲發佈和分發服務器,SQL Server 2012做爲訂閱服務器,如圖7所示。
![7 7](http://static.javashuo.com/static/loading.gif)
圖7.複製的兩個實例
首先在SQL Server 2008R2上配置發佈服務器和分發服務器,選擇配置分發,如圖8所示。
![8 8](http://static.javashuo.com/static/loading.gif)
圖8.配置分發
將發佈服務器和分發服務器選擇爲同1臺,如圖9所示。
![9 9](http://static.javashuo.com/static/loading.gif)
圖9.設置發佈服務器和分發服務器爲同一臺服務器
設置快照文件夾,由上面MSDN的圖可知,快照代理是須要在分發服務器上暫存快照的,設置這個目錄,如圖10所示。
![10 10](http://static.javashuo.com/static/loading.gif)
圖10.設置快照文件夾
這裏值得注意的是,須要給這個目錄對於Everyone設置讀取權限,如圖11所示。
![11 11](http://static.javashuo.com/static/loading.gif)
圖11.設置讀取權限
下一步配置分發嚮導就按照默認值來,如圖12所示。
![12 12](http://static.javashuo.com/static/loading.gif)
圖12.配置分發嚮導
剩下的步驟都保持默認值,最後成功在SQL Server 2008R2實例上配置發佈服務器和分發服務器,如圖13所示。
![13 13](http://static.javashuo.com/static/loading.gif)
圖13.成功配置發佈和分發服務器
下面就要創建一個發佈了,選擇新建發佈,如圖14所示。
![14 14](http://static.javashuo.com/static/loading.gif)
圖14.新建發佈
一路next,在選擇發佈類型時選擇事務發佈,如圖15所示。
![15 15](http://static.javashuo.com/static/loading.gif)
圖15.選擇事務發佈
發佈用於測試的一個表,只有兩個列,一個爲自增的int型主鍵id,另外一個爲隨便設置的列,如圖16所示。
![16 16](http://static.javashuo.com/static/loading.gif)
圖16.設置發佈的表(文章)
下一個頁面不過濾文章,直接保持默認值下一步。在下一個窗口中選擇當即建立快照並初始化..如圖17所示。
![17 17](http://static.javashuo.com/static/loading.gif)
圖17.當即創造快照並初始化
安全設置保持和SQL Server Agent同樣的帳戶,如圖18所示。
![18 18](http://static.javashuo.com/static/loading.gif)
圖18.快照代理和日誌讀取代理設置和SQL Server Agent同一個帳戶
剩下的步驟一路下一步,設置好發佈名稱後,成功建立發佈,如圖19所示。
![19 19](http://static.javashuo.com/static/loading.gif)
圖19.成功建立發佈
下面咱們來在SQL Server 2012的實例上建立訂閱,選擇新建訂閱,如圖20所示。
![20 20](http://static.javashuo.com/static/loading.gif)
圖20.新建訂閱
在歡迎界面選擇下一步後,選擇剛剛建立的發佈,如圖21所示。
![21 21](http://static.javashuo.com/static/loading.gif)
圖21.選擇發佈服務器
下一步選擇推送訂閱,以便發佈服務器所作的改動能自動更改到訂閱服務器,如圖22所示。
![23 23](http://static.javashuo.com/static/loading.gif)
圖23.選擇推送訂閱
選擇保持鏈接,下一步保持默認值,而後在分發代理安全性下選擇模擬進程帳戶。如圖24所示。
![24 24](http://static.javashuo.com/static/loading.gif)
圖24.選擇模擬進程帳戶
保持默認值,一路下一步直到訂閱建立完成,如圖25所示。
![25 25](http://static.javashuo.com/static/loading.gif)
圖25.建立訂閱成功
如今咱們進行測試,向表中插入100條數據,監視狀態,發現100個事務已經成功傳到了訂閱服務器,如圖26所示。
![26 26](http://static.javashuo.com/static/loading.gif)
圖26.插入的100條數據已經成功傳送到訂閱服務器
如今咱們再來看訂閱服務器(SQL Server 2012),在發佈服務器插入的100條數據已經成功存在於訂閱服務器,如圖27所示。
![27 27](http://static.javashuo.com/static/loading.gif)
圖27.100條數據已經成功發佈到了訂閱服務器
總結
本文對SQL Server的複製進行了大體的講解,並實現了一個簡單的複製。複製的概念須要對SQL Server的各個方面都要有所涉獵,本系列文章的下一篇將會將複製應用的一些模式。
簡介
在傳統的操做系統中,進程擁有獨立的內存地址空間和一個用於控制的線程。可是,如今的狀況更多的狀況下要求在同一地址空間下擁有多個線程併發執行。所以線程被引入操做系統。
爲何須要線程?
若是非要說是爲何須要線程,還不如說爲何須要進程中還有其它進程。這些進程中包含的其它迷你進程就是線程。
線程之因此說是迷你進程,是由於線程和進程有不少類似之處,好比線程和進程的狀態都有運行,就緒,阻塞狀態。這幾種狀態理解起來很是簡單,當進程所需的資源沒有到位時會是阻塞狀態,當進程所需的資源到位時但CPU沒有到位時是就緒狀態,當進程既有所需的資源,又有CPU時,就爲運行狀態。
下面咱們來看一個具體的例子:
就拿我寫博客的LiveWriter來講,LiveWriter須要監聽我打字輸入的狀態,還須要每隔5分鐘對草稿進行自動保存。假設若是這個進程只有一個線程的話,那麼當對草稿進行保存時,由於此時須要訪問硬盤,而訪問硬盤的時間線程是阻塞狀態的,這時個人任何輸入都會沒有響應,這種用戶體驗是沒法接受的,或許咱們能夠經過鍵盤或者鼠標的輸入去中斷保存草稿的過程,但這種方案也並不討好。而使用多線程,每一個線程僅僅須要處理本身那一部分應該完成的任務,而不用去關心和其它線程的衝突。所以簡化了編程模型。如圖1所示。
![1 1](http://static.javashuo.com/static/loading.gif)
圖1.兩條線程知足各自的功能
更具體的說,線程的好處以下:
1.在不少程序中,須要多個線程互相同步或互斥的並行完成工做,而將這些工做分解到不一樣的線程中去無疑簡化了編程模型。
2.由於線程相比進程來講,更加的輕量,因此線程的建立和銷燬的代價變得更小。
3.線程提升了性能,雖然線程宏觀上是並行的,但微觀上倒是串行。從CPU角度線程並沒有法提高性能,但若是某些線程涉及到等待資源(好比IO,等待輸入)時,多線程容許進程中的其它線程繼續執行而不是整個進程被阻塞,所以提升了CPU的利用率,從這個角度會提高性能。
4.在多CPU或多核的狀況下,使用線程不只僅在宏觀上並行,在微觀上也是並行的。
這裏值得注意的是,上面的兩個線程若是改爲兩個進程,那麼達不到所要的效果,由於進程有本身獨立的內存地址空間,而線程共享進程的內存地址空間。
經典線程模型
另外一個看進程和線程的角度是進程模型基於兩類不一樣的概念:資源的組織和執行。在過去沒有線程的操做系統中,資源的組織和執行都是由進程完成的。但區分這二者不少時候須要加以區分,這也是爲何須要引入線程。
進程是用於組織資源的單位,進程將相關的資源組織在一塊兒,這些資源包括:內存地址空間,程序,數據等,將這些以進程的形式組織起來可使得操做系統管理這些資源更爲容易。
而線程,是每個進程中執行的一個條線。線程雖然共享進程中的大多數資源,但線程也須要本身的一些資源,好比:用於標識下一條執行指令的程序計數器,一些容納局部變量的寄存器,以及用於表示執行的歷史的棧。
總而言之:進程是組織資源的最小單位,而線程是安排CPU執行的最小單位。
其實在一個進程中多個線程並行和在操做系統中多個進程並行很是相似,只是線程共享的是地址空間,而進程共享的是物理內存,打印機,鍵盤等資源……
每個進程和線程所獨自佔有的資源如表1所示。
進程佔有的資源 |
線程佔有的資源 |
地址空間 全局變量 打開的文件 子進程 信號量 帳戶信息 |
棧 寄存器 狀態 程序計數器 |
表1.進程和線程所獨佔的資源
其中,線程能夠共享進程獨佔的資源。
咱們經常使用的術語「多線程」通常指的是在同一個進程中多個線程的併發執行。如圖2所示。
![2 2](http://static.javashuo.com/static/loading.gif)
圖2.沒有多線程的系統一個進程只能由一個線程
在多線程的進程中,每一個線程輪流使用CPU,所以實際上線程並非並行的,但從宏觀上看,是並行的。
在多線程模型中,每個進程初始建立時只有一個線程。這個線程能夠經過調用系統的庫函數去建立其它線程。線程建立的線程並必需要爲其指定地址,由於新的線程自動在建立它的地址空間內工做。雖然一個線程能夠建立另外一個線程,但一般來說,線程之間是並列的,並不存在層級關係。
當一個進程完成其工做後,能夠經過調用系統庫函數進行銷燬。
操做系統實現線程的幾種模式
在操做系統中,線程能夠實如今用戶模式下,也能夠實如今內核模式下,也能夠二者結合實現。
線程實如今用戶空間下
當線程在用戶空間下實現時,操做系統對線程的存在一無所知,操做系統只能看到進程,而不能看到線程。全部的線程都是在用戶空間實現。在操做系統看來,每個進程只有一個線程。過去的操做系統大部分是這種實現方式,這種方式的好處之一就是即便操做系統不支持線程,也能夠經過庫函數來支持線程。
在這種模式下,每個進程中都維護着一個線程表來追蹤本進程中的線程,這個表中包含表1中每一個線程獨佔的資源,好比棧,寄存器,狀態等,如圖3所示。
![3 3](http://static.javashuo.com/static/loading.gif)
圖3.在用戶空間中實現線程
這種模式當一個線程完成了其工做或等待須要被阻塞時,其調用系統過程阻塞自身,而後將CPU交由其它線程。
這種的模式的好處,首先,是在用戶空間下進行進程切換的速度要遠快於在操做系統內核中實現。其次,在用戶空間下實現線程使得程序員能夠實現本身的線程調度算法。好比進程能夠實現垃圾回收器來回收線程。還有,當線程數量過多時,因爲在用戶空間維護線程表,不會佔用大量的操做系統空間。
有好處就有壞處,這種模式最致命的缺點也是因爲操做系統不知道線程的存在,所以當一個進程中的某一個線程進行系統調用時,好比缺頁中斷而致使線程阻塞,此時操做系統會阻塞整個進程,即便這個進程中其它線程還在工做。還有一個問題是假如進程中一個線程長時間不釋放CPU,由於用戶空間並無時鐘中斷機制,會致使此進程中的其它線程得不到CPU而持續等待。
線程實如今操做系統內核中
在這種模式下,操做系統知道線程的存在。此時線程表存在操做系統內核中,如圖4所示。
![4 4](http://static.javashuo.com/static/loading.gif)
圖4.線程在操做系統內核中實現
在這種模式下,全部可能阻塞線程的調用都以系統調用(System Call)的方式實現,相比在用戶空間下實現線程形成阻塞的運行時調用(System runtime call)成本會高出不少。當一個線程阻塞時,操做系統能夠選擇將CPU交給同一進程中的其它線程,或是其它進程中的線程,而在用戶空間下實現線程時,調度只能在本進程中執行,直到操做系統剝奪了當前進程的CPU。
由於在內核模式下實現進程的成本更高,一個比較好的作法是另線程回收利用,當一個線程須要被銷燬時,僅僅是修改標記位,而不是直接銷燬其內容,當一個新的線程須要被建立時,也一樣修改被「銷燬」的線程其標記位便可。
這種模式下一樣仍是有一些弊端,好比接收系統信號的單位是進程,而不是線程,那麼由進程中的哪個線程接收系統信號呢?若是使用了表來記錄,那麼多個線程註冊則經過哪個線程處理系統信號?
混合模式
還有一種實現方式是將上面兩種模式進行混合,用戶空間中進程管理本身的線程,操做系統內核中有一部份內核級別的線程,如圖5所示。
![5 5](http://static.javashuo.com/static/loading.gif)
圖5.混合模式
在這種模式下,操做系統只能看到內核線程。用戶空間線程基於操做系統線程運行。所以,程序員能夠決定使用多少用戶空間線程以及操做系統線程,這無疑具備更大的靈活性。而用戶空間線程的調度和前面所說的在用戶空間下執行實現線程是同樣的,一樣能夠自定義實現。