SQL 名詞解釋
1. 事務
1.1 行級鎖
行級鎖是針對行來鎖定的,比如在事務裏,進程A執行了一條update語句:
update student set name='xx' where id=13
則行級鎖會鎖住student表裏id=13的記錄,不讓別的進程對它操作,
只有等事務完成後才解除鎖,舉個例子,以 SQL SERVER爲例,
同時打開兩個查詢分析器,在第一個查詢分析器裏寫:
update student set name='xx' where id=13
則行級鎖會鎖住student表裏id=13的記錄,不讓別的進程對它操作,
只有等事務完成後才解除鎖,舉個例子,以 SQL SERVER爲例,
同時打開兩個查詢分析器,在第一個查詢分析器裏寫:
use northwind
select * from suppliers
begin transaction
update suppliers set CompanyName='xx' where SupplierID=3
waitfor delay '00:00:20'
commit transaction
select * from suppliers
begin transaction
update suppliers set CompanyName='xx' where SupplierID=3
waitfor delay '00:00:20'
commit transaction
在第二個查詢分析器裏寫:
select * from suppliers
然後先運行第一個查詢分析器裏的代碼,再運行第二個查詢分析器裏的
代碼,可以看到第一個查詢分析器一直運行,運行了大概20秒後執行
完畢,第二個查詢分析器也一樣,運行了大概20秒才停止,
這說明執行 select * from suppliers 時在等待,如果不運行第一個
查詢分析器裏的代碼,直接運行第二個查詢分析器裏的代碼,那幾乎不
用等待就可以看到結果了;
修改第二個查詢分析器的代碼爲:
select * from suppliers where SupplierID<>3
然後先運行第1個查詢分析器的代碼,再運行第二個查詢分析器的代碼,
可以看到第二個查詢分析器一運行馬上就出結果了,沒有等待,
再修改代碼爲:
select * from suppliers where SupplierID=3
重複前面的操作,可以看到需要等待,等待約20秒後纔看到結果
這很明顯的告訴我們,行級鎖會鎖住修改的行,讓別的進程無法操作
那些行,只有事務完成後別的進程纔可以操作,而沒有修改的行,別的
進程就可以任意操作,不會有限制
1.2 頁級鎖(1)
代碼,可以看到第一個查詢分析器一直運行,運行了大概20秒後執行
完畢,第二個查詢分析器也一樣,運行了大概20秒才停止,
這說明執行 select * from suppliers 時在等待,如果不運行第一個
查詢分析器裏的代碼,直接運行第二個查詢分析器裏的代碼,那幾乎不
用等待就可以看到結果了;
修改第二個查詢分析器的代碼爲:
select * from suppliers where SupplierID<>3
然後先運行第1個查詢分析器的代碼,再運行第二個查詢分析器的代碼,
可以看到第二個查詢分析器一運行馬上就出結果了,沒有等待,
再修改代碼爲:
select * from suppliers where SupplierID=3
重複前面的操作,可以看到需要等待,等待約20秒後纔看到結果
這很明顯的告訴我們,行級鎖會鎖住修改的行,讓別的進程無法操作
那些行,只有事務完成後別的進程纔可以操作,而沒有修改的行,別的
進程就可以任意操作,不會有限制
1.2 頁級鎖(1)
先理解頁這個概念,在SQL SERVER裏建表時,如果字段大小比較大時,
往往會有提示: 表中允許的最大行大小 8060
比如在SQL SERVER2000 裏新建一個表:
往往會有提示: 表中允許的最大行大小 8060
比如在SQL SERVER2000 裏新建一個表:
create table Test(Fld1 char(5000),Fld2 char(5000))
會提示建立失敗,原因如:
“創建表 'Test' 失敗,因爲行大小將爲 10021(包括內部開銷),而該值超過了表中允許的最大行大小 8060。”
“創建表 'Test' 失敗,因爲行大小將爲 10021(包括內部開銷),而該值超過了表中允許的最大行大小 8060。”
爲什麼它會限制行大小爲8060呢?因爲在 SQLSERVER2000裏,一頁的
大小爲8K,這8K裏包括96字節的頁頭、36字節的其它信息、8060字節的
數據區,而數據就存儲在8060字節的數據區裏,
一頁能存儲多少行呢?這要看行的大小了,比如如果行大小爲2000,
則一頁能存儲4行,注意行大小不包括文本和圖象字段,
比如數據庫northwind的customers表的行大小爲 298,
則一頁可以存儲 27 行
看看行大小計算的問題:
大小爲8K,這8K裏包括96字節的頁頭、36字節的其它信息、8060字節的
數據區,而數據就存儲在8060字節的數據區裏,
一頁能存儲多少行呢?這要看行的大小了,比如如果行大小爲2000,
則一頁能存儲4行,注意行大小不包括文本和圖象字段,
比如數據庫northwind的customers表的行大小爲 298,
則一頁可以存儲 27 行
看看行大小計算的問題:
use northwind
alter table customers add xx char(8000)
alter table customers add xx char(8000)
運行結果有警告:
-----------------------------------------
警告: 已創建表 'customers',但其最大行大小(8578)超過了每行的最大字節數(8060)。如果結果行長度超過 8060 字節,則此表中行的 INSERT 或 UPDATE 將失敗
-----------------------------------------
它提示行大小爲8578,則修改前的customers的行大小爲578,
可爲什麼我將各個字段的大小加起來才268呢,
這有兩個原因,一方面,數據庫用兩個字節存儲一個nvarchar類型的字符
nchar也一樣,而customers的字段類型爲nchar和nvarchar,所以實際大小爲 268*2=536 ,那還有42呢?42是表的其它開銷。
-----------------------------------------
警告: 已創建表 'customers',但其最大行大小(8578)超過了每行的最大字節數(8060)。如果結果行長度超過 8060 字節,則此表中行的 INSERT 或 UPDATE 將失敗
-----------------------------------------
它提示行大小爲8578,則修改前的customers的行大小爲578,
可爲什麼我將各個字段的大小加起來才268呢,
這有兩個原因,一方面,數據庫用兩個字節存儲一個nvarchar類型的字符
nchar也一樣,而customers的字段類型爲nchar和nvarchar,所以實際大小爲 268*2=536 ,那還有42呢?42是表的其它開銷。
從行與頁的關係可以看出,行的大小越小,則一頁能存儲的行數越多,
數據庫查詢時,從一頁讀到另一頁,比只讀一頁的記錄要慢得多,
所以要減少跨頁讀取的次數,
比較下面的兩個語句:
數據庫查詢時,從一頁讀到另一頁,比只讀一頁的記錄要慢得多,
所以要減少跨頁讀取的次數,
比較下面的兩個語句:
create table x1(a char(5000),b char(5000))
create table x2(a varchar(5000),b char(5000))
create table x2(a varchar(5000),b char(5000))
運行結果爲:
------------------------------------
服務器: 消息 1701,級別 16,狀態 2,行 2
創建表 'x1' 失敗,因爲行大小將爲 10021(包括內部開銷),而該值超過了表中允許的最大行大小 8060。
警告: 已創建表 'x2',但其最大行大小(10023)超過了每行的最大字節數(8060)。如果結果行長度超過 8060 字節,則此表中行的 INSERT 或 UPDATE 將失敗。
------------------------------------
x1創建失敗,x2創建成功,但有警告,爲什麼呢?
這要比較char和varchar的區別了,當創建x1時,最大行大小10023就是
實際的行大小,因爲char是定長的,大小總是10023,而x2不同,
varchar是變長的,雖然最大行大小是10023,而實際行大小卻不一定的,
實際行大小隨字段a的值的大小的變化而變化,
所以,每頁能存儲的行數,如果是定長的,那在建表時就可以確定了,
如果是變長的,那要根據表中的數據來確定,當然,SQL SERVER存儲記錄
時,對於頁的選擇還會考慮一些問題,也並不完全是這樣看看Northwind數據庫的Customers表吧,
Customers的主鍵字段爲CustomerID,主鍵是聚集索引的,
主鍵的順序代表了行的實際存儲順序,
比如你往Customers裏插入一條記錄:
insert into Customers(CustomerId,CompanyName) values('cvcvc','ff')
------------------------------------
服務器: 消息 1701,級別 16,狀態 2,行 2
創建表 'x1' 失敗,因爲行大小將爲 10021(包括內部開銷),而該值超過了表中允許的最大行大小 8060。
警告: 已創建表 'x2',但其最大行大小(10023)超過了每行的最大字節數(8060)。如果結果行長度超過 8060 字節,則此表中行的 INSERT 或 UPDATE 將失敗。
------------------------------------
x1創建失敗,x2創建成功,但有警告,爲什麼呢?
這要比較char和varchar的區別了,當創建x1時,最大行大小10023就是
實際的行大小,因爲char是定長的,大小總是10023,而x2不同,
varchar是變長的,雖然最大行大小是10023,而實際行大小卻不一定的,
實際行大小隨字段a的值的大小的變化而變化,
所以,每頁能存儲的行數,如果是定長的,那在建表時就可以確定了,
如果是變長的,那要根據表中的數據來確定,當然,SQL SERVER存儲記錄
時,對於頁的選擇還會考慮一些問題,也並不完全是這樣看看Northwind數據庫的Customers表吧,
Customers的主鍵字段爲CustomerID,主鍵是聚集索引的,
主鍵的順序代表了行的實際存儲順序,
比如你往Customers裏插入一條記錄:
insert into Customers(CustomerId,CompanyName) values('cvcvc','ff')
然後用select * from customers查看數據,
可以看到新插入的記錄自動排在了CustomerId等於CONSH的後面,
看起來就和 select * from customers order by customerId
查出來的數據一樣,聚集索引就是這樣,記錄的物理存儲順序與
聚集索引的順序是一樣的.
看看Customers表,打開三個查詢分析器,在第一個表寫:
begin transaction
update customers with(PagLock) set Address=Address
where customerId='ALFKI'
waitfor delay '00:00:30'
commit transaction
在第二個查詢分析器裏寫:
select * from customers where customerId='GREAL'
在第三個查詢分析器裏寫:
select * from customers where customerId='GROSR'
先運行第一個查詢分析器,然後運行第二個,再運行第三個,
可以看到,第一和第二個查詢分析器等待執行了20秒,
而第三個查詢分析器沒有等待立即就顯示運行結果了,
我更新的是'ALFKI',因爲是頁鎖,所以它鎖住了一頁的數據,
從'ALFKI'到'GREAL'的行都鎖住了,這也說明,
'ALFKI'到'GREAL'之間的共34行都是屬於同一頁的,
你可以將第一個查詢分析器的'ALFKI'換成'DRACD',可以看到運行
結果是一樣的,如果換成'HANAR',那結果就變了,
變成第一個和第三個查詢分析器在等待,而第二個查詢分析器不用等待,
因爲'HANAR'和'GROSR'屬於同一頁,而'GREAL'在其它的頁,
頁鎖概念是比較簡單的,但頁的概念卻比較複雜,
頁是在SQLSERVER的內部管理的,用戶看不到,頁比較抽象,
對於變長的數據類型,頁的分配是隨數據的變化而變化的,
請參考數據庫相關的資料。
可以看到新插入的記錄自動排在了CustomerId等於CONSH的後面,
看起來就和 select * from customers order by customerId
查出來的數據一樣,聚集索引就是這樣,記錄的物理存儲順序與
聚集索引的順序是一樣的.
看看Customers表,打開三個查詢分析器,在第一個表寫:
begin transaction
update customers with(PagLock) set Address=Address
where customerId='ALFKI'
waitfor delay '00:00:30'
commit transaction
在第二個查詢分析器裏寫:
select * from customers where customerId='GREAL'
在第三個查詢分析器裏寫:
select * from customers where customerId='GROSR'
先運行第一個查詢分析器,然後運行第二個,再運行第三個,
可以看到,第一和第二個查詢分析器等待執行了20秒,
而第三個查詢分析器沒有等待立即就顯示運行結果了,
我更新的是'ALFKI',因爲是頁鎖,所以它鎖住了一頁的數據,
從'ALFKI'到'GREAL'的行都鎖住了,這也說明,
'ALFKI'到'GREAL'之間的共34行都是屬於同一頁的,
你可以將第一個查詢分析器的'ALFKI'換成'DRACD',可以看到運行
結果是一樣的,如果換成'HANAR',那結果就變了,
變成第一個和第三個查詢分析器在等待,而第二個查詢分析器不用等待,
因爲'HANAR'和'GROSR'屬於同一頁,而'GREAL'在其它的頁,
頁鎖概念是比較簡單的,但頁的概念卻比較複雜,
頁是在SQLSERVER的內部管理的,用戶看不到,頁比較抽象,
對於變長的數據類型,頁的分配是隨數據的變化而變化的,
請參考數據庫相關的資料。
1.3 表鎖
在第一個查詢分析器寫:
begin transaction tran1
update customers with(TabLock) set City=City
where CustomerId='ALFKI'
waitfor delay '00:00:20'
commit transaction tran1
begin transaction tran1
update customers with(TabLock) set City=City
where CustomerId='ALFKI'
waitfor delay '00:00:20'
commit transaction tran1
在第二個查詢分析器寫:
select * from customers where customerId='WOLZA'
select * from customers where customerId='WOLZA'
先運行第一個查詢分析器,再運行第二個,兩個查詢分析器都在等待.
注意customerId='WOLZA'是表的最後一條記錄
1.4 阻塞
注意customerId='WOLZA'是表的最後一條記錄
1.4 阻塞
前面的例子裏一個事務未提交,導致別的事務必須等待,這就是阻塞,
查看阻塞可以用sp_lock,打開三個查詢分析器,
第一個寫:
begin transaction tran1
update products set productName=productName+'A'
where ProductId=1
waitfor delay '00:00:30'
commit transaction tran1
第二個寫:
select * from products
第三個寫:
sp_lock
依次運行第一個、第二個、第三個,
然後查看第三個分析器,看看Status列,
看是否有Status='Wait'的行,比如我這裏查看有這麼一行:
查看阻塞可以用sp_lock,打開三個查詢分析器,
第一個寫:
begin transaction tran1
update products set productName=productName+'A'
where ProductId=1
waitfor delay '00:00:30'
commit transaction tran1
第二個寫:
select * from products
第三個寫:
sp_lock
依次運行第一個、第二個、第三個,
然後查看第三個分析器,看看Status列,
看是否有Status='Wait'的行,比如我這裏查看有這麼一行:
53 6 117575457 1 KEY (010086470766) S WAIT
其中ObjId=117575457
然後運行:
use northwind
select object_name(117575457)
可以看到對應的表爲 Products
1.5 死鎖
use northwind
select object_name(117575457)
可以看到對應的表爲 Products
1.5 死鎖
同時打開兩個查詢分析器,
第一個寫:
begin transaction tran2
update products set productName=productName+'A'
where ProductId=2
waitfor delay '00:00:10'
update products set productName=productName+'A'
where ProductId=1
commit transaction tran2
第一個寫:
begin transaction tran2
update products set productName=productName+'A'
where ProductId=2
waitfor delay '00:00:10'
update products set productName=productName+'A'
where ProductId=1
commit transaction tran2
第二個寫:
begin transaction tran1
update products set productName=productName+'A'
where ProductId=1
waitfor delay '00:00:10'
update products set productName=productName+'A'
where ProductId=2
commit transaction tran1
begin transaction tran1
update products set productName=productName+'A'
where ProductId=1
waitfor delay '00:00:10'
update products set productName=productName+'A'
where ProductId=2
commit transaction tran1
先運行第一個,再運行第二個
然後等待它們執行,等待大概十多秒,
檢查運行結果,可以看到其中一個出錯,錯誤提示如:
然後等待它們執行,等待大概十多秒,
檢查運行結果,可以看到其中一個出錯,錯誤提示如:
服務器: 消息 1205,級別 13,狀態 50,行 1
在查詢分析器裏按F1打開幫助,在幫助裏選擇索引選項卡,
輸入 1205 ,你仔細查看幫助文檔是如何描述 1205 錯誤的,
輸入 1205 ,你仔細查看幫助文檔是如何描述 1205 錯誤的,
爲什麼會死鎖呢?看看執行過程,爲了簡單,我將productId簡寫爲id
先是分析器1更新id=2的記錄,並鎖住id=2的記錄,那別的進程都無法
操作id=2的記錄,
然後分析器2更新id=1的記錄,並鎖住id=1的記錄,同樣別的進程無法
操作id=1的記錄,
然後分析器1更新id=1的記錄,因爲id=1的記錄被分析器2鎖住了,
所以必須等待,分析器1被阻塞
同樣分析器2更新id=2的記錄,因爲id=2的記錄被分析器1鎖住了,
所以也要等待,分析器2被阻塞
兩個分析器都要等待對方,所以就出現死鎖,哪個都不能執行,
先是分析器1更新id=2的記錄,並鎖住id=2的記錄,那別的進程都無法
操作id=2的記錄,
然後分析器2更新id=1的記錄,並鎖住id=1的記錄,同樣別的進程無法
操作id=1的記錄,
然後分析器1更新id=1的記錄,因爲id=1的記錄被分析器2鎖住了,
所以必須等待,分析器1被阻塞
同樣分析器2更新id=2的記錄,因爲id=2的記錄被分析器1鎖住了,
所以也要等待,分析器2被阻塞
兩個分析器都要等待對方,所以就出現死鎖,哪個都不能執行,
當然,SQLSERVER2000爲了解決死鎖問題,它會幹掉其中一個進程
來結束死鎖。
來結束死鎖。
1.6 佔用讀
佔用讀指可以讀別的進程未提交的數據,
打開兩個查詢分析器,第一個寫:
begin transaction tran1
update products set productName=productName+'C'
where ProductId=1
waitfor delay '00:00:15'
commit transaction tran1
打開兩個查詢分析器,第一個寫:
begin transaction tran1
update products set productName=productName+'C'
where ProductId=1
waitfor delay '00:00:15'
commit transaction tran1
第二個寫:
set transaction isolation level read uncommitted
select * from products where ProductId=1
set transaction isolation level read uncommitted
select * from products where ProductId=1
依次運行第一個和第二個,
可以看到第一個在等待,而第二個不用等待,
因爲我在第二個裏設置了隔離級別爲read uncommitted,
就是允許讀別的事務未提交的數據,
你看看第二個的運行結果,找到products列,看到products列已經修改了
如果你修改第二個查詢分析器代碼爲:
set transaction isolation level read committed
select * from products where ProductId=1
可以看到第一個在等待,而第二個不用等待,
因爲我在第二個裏設置了隔離級別爲read uncommitted,
就是允許讀別的事務未提交的數據,
你看看第二個的運行結果,找到products列,看到products列已經修改了
如果你修改第二個查詢分析器代碼爲:
set transaction isolation level read committed
select * from products where ProductId=1
同樣運行,那第二個也要等待了,因爲隔離級別是read committed,
只能讀提交後的數據,不能讀未提交的修改,這樣就防止了
佔用讀,SQLSERVER2000裏默認是read committed
說明,佔用讀也叫髒讀,髒讀就是修改了但沒提交的數據,
在文本編輯器裏也有髒讀的概念,就是修改了但未保存的數據
1.7 不可重複讀
只能讀提交後的數據,不能讀未提交的修改,這樣就防止了
佔用讀,SQLSERVER2000裏默認是read committed
說明,佔用讀也叫髒讀,髒讀就是修改了但沒提交的數據,
在文本編輯器裏也有髒讀的概念,就是修改了但未保存的數據
1.7 不可重複讀
事務裏執行兩次相同的查詢時,查詢出來的結果不相同,
說明是不可重複讀,打開兩個查詢分析器,
第一個寫:
use northwind
set transaction isolation level read committed
begin transaction tran1
select * from region where regionId=3
waitfor delay '00:00:10'
select * from region where regionId=3
commit transaction tran1
說明是不可重複讀,打開兩個查詢分析器,
第一個寫:
use northwind
set transaction isolation level read committed
begin transaction tran1
select * from region where regionId=3
waitfor delay '00:00:10'
select * from region where regionId=3
commit transaction tran1
第二個寫:
use northwind
update region set regionDescription='xx' where regionId=3
use northwind
update region set regionDescription='xx' where regionId=3
依次運行第一個和第二個分析器,第一個分析器等待10秒,第二個
不用等待立即得到結果,第一個分析器運行結果爲:
3 Northern
3 xx
不用等待立即得到結果,第一個分析器運行結果爲:
3 Northern
3 xx
兩次讀得的值不相同,
修改第一個查詢分析器代碼爲:
use northwind
set transaction isolation level repeatable read
begin transaction tran1
select * from region where regionId=3
waitfor delay '00:00:10'
select * from region where regionId=3
commit transaction tran1
修改第一個查詢分析器代碼爲:
use northwind
set transaction isolation level repeatable read
begin transaction tran1
select * from region where regionId=3
waitfor delay '00:00:10'
select * from region where regionId=3
commit transaction tran1
第二個修改爲:
use northwind
update region set regionDescription='yy' where regionId=3
use northwind
update region set regionDescription='yy' where regionId=3
同樣依次運行第一個和第二個,看到第一個在等待,
第二個也在等待,第一個分析器運行結果爲:
3 xx
3 xx
第二個也在等待,第一個分析器運行結果爲:
3 xx
3 xx
看看兩次的區別,第一次我設置隔離級別爲read committed,
第二次我設置爲repeatable read,
repeatable read 會鎖住讀的數據,
read committed 會鎖住修改的數據,
repeatable read會鎖住insert、update、delete、select操作的數據
read committed只鎖insert 、update、delete, 不鎖select查詢的數據
1.8 幻像讀
第二次我設置爲repeatable read,
repeatable read 會鎖住讀的數據,
read committed 會鎖住修改的數據,
repeatable read會鎖住insert、update、delete、select操作的數據
read committed只鎖insert 、update、delete, 不鎖select查詢的數據
1.8 幻像讀
打開兩個查詢分析器,第一個寫:
use northwind
set transaction isolation level repeatable read
begin transaction tran1
select * from region
waitfor delay '00:00:10'
select * from region
commit transaction tran1
use northwind
set transaction isolation level repeatable read
begin transaction tran1
select * from region
waitfor delay '00:00:10'
select * from region
commit transaction tran1
第二個寫:
use northwind
insert into region values(5,'xx')
use northwind
insert into region values(5,'xx')
依次運行第一個和第二個分析器,第一個分析器等待10秒,第二個
不用等待立即得到結果,第一個分析器運行結果爲:
1 Eastern
2 Western
3 Northern
4 Southern
不用等待立即得到結果,第一個分析器運行結果爲:
1 Eastern
2 Western
3 Northern
4 Southern
1 Eastern
2 Western
3 Northern
4 Southern
5 xx
2 Western
3 Northern
4 Southern
5 xx
比較兩次查詢的結果,第二次查詢多了一行,
修改第一個分析器的代碼爲:
use northwind
set transaction isolation level serializable
begin transaction tran1
select * from region
waitfor delay '00:00:10'
select * from region
commit transaction tran1
修改第一個分析器的代碼爲:
use northwind
set transaction isolation level serializable
begin transaction tran1
select * from region
waitfor delay '00:00:10'
select * from region
commit transaction tran1
修改第二個分析器代碼爲:
use northwind
insert into region values(6,'yy')
use northwind
insert into region values(6,'yy')
再依次運行第一個和第二個分析器,
可以看到兩個分析器都要等待,第一個的運行結果是:
兩次查詢返回的行數是相同的。
可以看到兩個分析器都要等待,第一個的運行結果是:
兩次查詢返回的行數是相同的。
理解佔用讀、不可重複讀、幻像讀要從數據庫如何操作來避免他們上
來理解,如果只從概念上去理解,概念往往很抽象,比較晦澀難懂,
而且概念往往只說到其中一個方面,應該弄清楚各種級別的瑣是如何
避免出現佔用讀、不可重複讀、幻像讀的。
read uncommitted不設置鎖,
read commmitted會鎖住update、insert、delete
repeatable read會鎖住update、insert、delete、select
seriablizable會鎖住update、insert、delete、select
repeatable read和seriablizable的區別在於:
repeatable read鎖住時別的事務不能update、delete鎖住的數據,
但別的事務能夠插入,
seriablizable鎖住時別的事務不能update、delete、insert 說明:
repeatable read 和 seriablizable 對 select 的鎖定採用範圍的方式
要鎖哪些行,主要是受where語句的限制,另外還受行鎖、頁鎖、表鎖方式
的限制,對於update、insert、delete的鎖定範圍比較明確,
repeatable read隔離級別對select的鎖定也比較明確,
而seriablizable對select的鎖定,當別的事務insert時,
哪些時候不能插入呢?這個範圍如何確定?
因爲鎖的概念往往是針對已有的數據,而insert插入的數據是原來表裏
沒有的,原來表裏沒有,那又如何鎖定呢?
比如:
set transaction isolation level seriablizable
select * from region
則鎖住表的所有行,比如開始有四行,則鎖住四行,
那insert一行呢,這裏有個範圍,那就是select的範圍,
它會判斷insert的行是否在鎖定的範圍之內,比如:
use northwind
set transaction isolation level serializable
begin transaction tran1
select * from region where regionId>6
waitfor delay '00:00:10'
commit transaction tran1
repeatable read鎖住時別的事務不能update、delete鎖住的數據,
但別的事務能夠插入,
seriablizable鎖住時別的事務不能update、delete、insert 說明:
repeatable read 和 seriablizable 對 select 的鎖定採用範圍的方式
要鎖哪些行,主要是受where語句的限制,另外還受行鎖、頁鎖、表鎖方式
的限制,對於update、insert、delete的鎖定範圍比較明確,
repeatable read隔離級別對select的鎖定也比較明確,
而seriablizable對select的鎖定,當別的事務insert時,
哪些時候不能插入呢?這個範圍如何確定?
因爲鎖的概念往往是針對已有的數據,而insert插入的數據是原來表裏
沒有的,原來表裏沒有,那又如何鎖定呢?
比如:
set transaction isolation level seriablizable
select * from region
則鎖住表的所有行,比如開始有四行,則鎖住四行,
那insert一行呢,這裏有個範圍,那就是select的範圍,
它會判斷insert的行是否在鎖定的範圍之內,比如:
use northwind
set transaction isolation level serializable
begin transaction tran1
select * from region where regionId>6
waitfor delay '00:00:10'
commit transaction tran1
如果別的事務插入記錄:
insert into region values(5,'yy')
insert into region values(5,'yy')
因爲插入的記錄regionId=5,而6<5,不滿足鎖定的條件,
所以該插入是允許的,如果別的事務插入記錄:
insert into region values(7,'yy')
因爲7>6,滿足條件,所以插入被阻塞,只能等待鎖釋放才能插入
不過,到底鎖定哪些記錄,這比較難說,鎖做爲SQLSERVER的內部管理,
到底是怎麼樣的不怎麼清楚,我試過有些情況不滿足的條件的記錄也
被阻塞,不過有點是清楚的,那就是滿足條件的一定被阻塞 1.8 隔離級別
所以該插入是允許的,如果別的事務插入記錄:
insert into region values(7,'yy')
因爲7>6,滿足條件,所以插入被阻塞,只能等待鎖釋放才能插入
不過,到底鎖定哪些記錄,這比較難說,鎖做爲SQLSERVER的內部管理,
到底是怎麼樣的不怎麼清楚,我試過有些情況不滿足的條件的記錄也
被阻塞,不過有點是清楚的,那就是滿足條件的一定被阻塞 1.8 隔離級別
前面已經說過了,有四種隔離級別:
read uncommitted
read committed
repeatable read
serializable
read uncommitted
read committed
repeatable read
serializable
read committed是默認的隔離級別,
隔離級別對單個用戶有用,比如你設置了隔離級別爲serializable,
那隻對你自己有用,對別的用戶不起作用,設置了隔離級別後,
那一直有效,直到用戶退出爲止,
鎖的作用主要是用來保證數據一致性的,
read uncommitted不會在被讀的數據上放置鎖,所以它執行
的速度是最快的,也不會造成阻塞和死鎖,但因爲數據一致
性問題,所以往往不採用,當然可以通過別的技術比如增加
rowverision列等來保證數據一致性,但對於複雜的操作,還
是選擇事務比較安全,對於事務我經歷過一些教訓,
比如一次我在VB裏保存數據,大致如:
隔離級別對單個用戶有用,比如你設置了隔離級別爲serializable,
那隻對你自己有用,對別的用戶不起作用,設置了隔離級別後,
那一直有效,直到用戶退出爲止,
鎖的作用主要是用來保證數據一致性的,
read uncommitted不會在被讀的數據上放置鎖,所以它執行
的速度是最快的,也不會造成阻塞和死鎖,但因爲數據一致
性問題,所以往往不採用,當然可以通過別的技術比如增加
rowverision列等來保證數據一致性,但對於複雜的操作,還
是選擇事務比較安全,對於事務我經歷過一些教訓,
比如一次我在VB裏保存數據,大致如:
begin transaction
declare @id as int
insert into A(...) values(...)
select @id=max(id) from t1
insert into B(AId...) values(@id...)
commit transation
declare @id as int
insert into A(...) values(...)
select @id=max(id) from t1
insert into B(AId...) values(@id...)
commit transation
其中A表的id字段是自動編號的,我先在A表插入一條記錄,
再將A表剛插入的記錄的Id插入到B表,必須保證@id是前面insert
生成的那個id,
但測試時,因爲客戶有很多電腦同時錄,所以導致一些id不一致的情況,
爲什麼會不一致呢?我不是已經加了事務了嗎?
做一個例子,同樣打開兩個查詢分析器,第一個寫:
再將A表剛插入的記錄的Id插入到B表,必須保證@id是前面insert
生成的那個id,
但測試時,因爲客戶有很多電腦同時錄,所以導致一些id不一致的情況,
爲什麼會不一致呢?我不是已經加了事務了嗎?
做一個例子,同樣打開兩個查詢分析器,第一個寫:
use northwind
set transaction isolation level serializable
begin transaction tran1
insert into region values(10,'aa')
waitfor delay '00:00:10'
select max(regionId) from region
commit transaction tran1
set transaction isolation level serializable
begin transaction tran1
insert into region values(10,'aa')
waitfor delay '00:00:10'
select max(regionId) from region
commit transaction tran1
第二個寫:
insert into region values(11,'aa')
insert into region values(11,'aa')
依次執行第一個和第二個分析器,
本來我希望第一個分析器裏查詢出來的是10,可結果卻是11
在這裏,唯一的方法是指定表鎖,如:
insert into region with(TabLock) values(10,'aa')
本來我希望第一個分析器裏查詢出來的是10,可結果卻是11
在這裏,唯一的方法是指定表鎖,如:
insert into region with(TabLock) values(10,'aa')
只有指定表鎖,讓別的事務無法操作它,才能保證數據一致,
當然,如果是自動編號,那可以用 @@identity 來獲取剛生成的Id號,
比如:
insert into orders(CustomerId) values('ALFKI')
print @@identity
當然,如果是自動編號,那可以用 @@identity 來獲取剛生成的Id號,
比如:
insert into orders(CustomerId) values('ALFKI')
print @@identity
這個技巧在別的數據庫驅動程序裏可能無效,事務纔是普遍支持的 補充(表來源: SQL SERVER7.0 系統管理指南)
隔離級別 阻塞風險 防止佔用讀 防止不可重複讀 防止幻像讀
---------------------------------------------------------------
read uncommitted 最低 NO NO NO
read committed 較低 Yes NO NO
repeatable read 較高 Yes Yes NO
serializable 最高 Yes Yes Yes
1.9 顯式鎖
---------------------------------------------------------------
read uncommitted 最低 NO NO NO
read committed 較低 Yes NO NO
repeatable read 較高 Yes Yes NO
serializable 最高 Yes Yes Yes
1.9 顯式鎖
顯式鎖是在select、insert、delete、update語句裏指定鎖的類型,
如:
如:
select * from authors with(RowLock) where au_id <='555-55-5555'
如果不用顯式鎖,那就是:
select * from authors where au_id <='555-55-5555'
如果不用顯式鎖,那採用那種鎖是由SQL SERVER內部來決定的,
2. 數據庫設計
2. 數據庫設計
2.1 冗餘數據
冗餘數據就是重複的數據,冗餘數據出現的情況很多,這裏舉一種情況,
就是表之間的冗餘,假設我在northwind裏建立一個sales表,字段如:
timeId,date,year,month,custId,custName,productId,
QuanPerUnit,productName,regionID,regionName,storeId,
storeName,quantity,price,money,
就是表之間的冗餘,假設我在northwind裏建立一個sales表,字段如:
timeId,date,year,month,custId,custName,productId,
QuanPerUnit,productName,regionID,regionName,storeId,
storeName,quantity,price,money,
現在我要查看2005年各商品的銷售情況,那可以用:
select productName,QuanPerUnit,sum(quantity),sum(money)
from sales where year=2005 group by productId
from sales where year=2005 group by productId
如果我這樣建sales表:
timeId,custId,productId,storeId,regionID
quantity,price,money
timeId,custId,productId,storeId,regionID
quantity,price,money
那查詢是怎樣的?查詢就會鏈接幾個表,sql語句變得複雜一些,
速度也會慢得多,但是按前面的方式建立sales表,會忽略一個問題,
那就是數據冗餘的問題,從物理存儲上來說,productName已經存在了
products表裏,沒必要在sales表裏再存儲一次,這樣就可以,
而這裏又存儲了一次,那就要佔用更多的物理空間,
速度也會慢得多,但是按前面的方式建立sales表,會忽略一個問題,
那就是數據冗餘的問題,從物理存儲上來說,productName已經存在了
products表裏,沒必要在sales表裏再存儲一次,這樣就可以,
而這裏又存儲了一次,那就要佔用更多的物理空間,
再看一個表內冗餘的情況,
如果sales表的字段是這樣的:
timeId,date,custId,proId,storeId,price,
northQuan,northMoney,eastQuan,eastMoney,
westQuan,westMoney,southQuan,southMoney
如果sales表的字段是這樣的:
timeId,date,custId,proId,storeId,price,
northQuan,northMoney,eastQuan,eastMoney,
westQuan,westMoney,southQuan,southMoney
那如果要查2005年各區域產品的銷售情況,就可以用:
select
(select productName from products where productId=proId) [產品]
sum(northMoney) as [北部],
sum(eastMoney) as [東部],
sum(westMoney) as [西部],
sum(southMoney) as [南部]
from sales where year(date)=2005
group by proId
select
(select productName from products where productId=proId) [產品]
sum(northMoney) as [北部],
sum(eastMoney) as [東部],
sum(westMoney) as [西部],
sum(southMoney) as [南部]
from sales where year(date)=2005
group by proId
上面的表的後面8個字段都是冗餘的,
只要用regionId這個字段就足夠了,
表內冗餘的情況還有一些,理解冗餘應從反面去理解,
就是如何去避免冗餘,要避免冗餘,那凡是可以通過直接或間接
方式得出來的數據,就不要在表裏保存,比如通過錶鏈接得到或
通過計算得到等等的數據,就沒必要在表裏再保存一次了
只要用regionId這個字段就足夠了,
表內冗餘的情況還有一些,理解冗餘應從反面去理解,
就是如何去避免冗餘,要避免冗餘,那凡是可以通過直接或間接
方式得出來的數據,就不要在表裏保存,比如通過錶鏈接得到或
通過計算得到等等的數據,就沒必要在表裏再保存一次了