1. 程式人生 > >Java程式猿之資料庫理論

Java程式猿之資料庫理論

一、事務的四大特性 ACID

  • 只有滿足一致性,事務的執行結果才是正確的。

  • 在無併發的情況下,事務序列執行,隔離性一定能夠滿足。此時要只要能滿足原子性,就一定能滿足一致性。

  • 在併發的情況下,多個事務併發執行,事務不僅要滿足原子性,還需要滿足隔離性,才能滿足一致性。

  • 事務滿足持久化是為了能應對資料庫奔潰的情況。

1.1 原子性 Atomicity

原子性是指事務是一個不可分割的工作單位,事務中的操作要麼全部成功,要麼全部失敗。比如在同一個事務中的SQL語句,要麼全部執行成功,要麼全部執行失敗。

回滾可以用日誌來實現,日誌記錄著事務所執行的修改操作,在回滾時反向執行這些修改操作即可。

1.2 一致性 Consistency

事務必須使資料庫從一個一致性狀態變換到另外一個一致性狀態。 以轉賬為例子,A向B轉賬,假設轉賬之前這兩個使用者的錢加起來總共是2000,那麼A向B轉賬之後,不管這兩個賬戶怎麼轉,A使用者的錢和B使用者的錢加起來的總額還是2000,這個就是事務的一致性。

1.3 隔離性 Isolation

隔離性是當多個使用者併發訪問資料庫時,比如操作同一張表時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作所幹擾,多個併發事務之間要相互隔離

即要達到這麼一種效果:對於任意兩個併發的事務 T1 和 T2,在事務 T1 看來,T2 要麼在 T1 開始之前就已經結束,要麼在 T1 結束之後才開始,這樣每個事務都感覺不到有其他事務在併發地執行。

1.4 永續性 Durability

一旦事務提交,則其所做的修改將會永遠儲存到資料庫中。即使系統發生崩潰,事務執行的結果也不能丟失。

可以通過資料庫備份和恢復來實現,在系統發生奔潰時,使用備份的資料庫進行資料恢復。

二、併發一致性問題

2.1 丟失修改

T1 和 T2 兩個事務都對一個數據進行修改,T1 先修改,T2 隨後修改,T2 的修改覆蓋了 T1 的修改。

2.2 髒讀

如果一個事務中對資料進行了更新,但事務還沒有提交,另一個事務可以 “看到” 該事務沒有提交的更新結果,這樣造成的問題就是,如果第一個事務回滾,那麼,第二個事務在此之前所 “看到” 的資料就是一筆髒資料。(髒讀又稱無效資料讀出, 一個事務讀取另外一個事務還沒有提交的資料叫髒讀

。)

例子:

  1. Mary 的原工資為 1000, 財務人員將 Mary 的工資改為了 8000 (但未提交事務)
  2. Mary 讀取自己的工資,發現自己的工資變為了 8000,歡天喜地!
  3. 而財務發現操作有誤,回滾了事務,Mary 的工資又變為了1000
  4. 像這樣,Mary記取的工資數8000是一個髒資料。

解決辦法:
  把資料庫的事務隔離級別調整到 READ_COMMITTED

圖解:
  T1 修改一個數據,T2 隨後讀取這個資料。如果 T1 撤銷了這次修改,那麼 T2 讀取的資料是髒資料。

2.3 不可重複讀

是指在一個事務內,多次讀同一資料。在這個事務還沒有結束時,另外一個事務也訪問該同一資料。那麼,在第一個事務中的兩次讀資料之間,由於第二個事務的修改,那麼第一個事務兩次讀到的的資料可能是不一樣的。這樣在一個事務內兩次讀到的資料是不一樣的,因此稱為是不可重複讀。(同時操作,事務1分別讀取事務2操作時和提交後的資料,讀取的記錄內容不一致。不可重複讀是指在同一個事務內,兩個相同的查詢返回了不同的結果。

例子:

  1. 在事務1中,Mary 讀取了自己的工資為1000,操作並沒有完成
  2. 在事務2中,這時財務人員修改了 Mary 的工資為 2000,並提交了事務
  3. 在事務1中,Mary 再次讀取自己的工資時,工資變為了2000

解決辦法:
  如果只有在修改事務完全提交之後才可以讀取資料,則可以避免該問題。把資料庫的事務隔離級別調整到REPEATABLE_READ

圖解:
  T2 讀取一個數據,T1 對該資料做了修改。如果 T2 再次讀取這個資料,此時讀取的結果和第一次讀取的結果不同。

2.4 幻讀

事務 T1 讀取一條指定的 Where 子句所返回的結果集,然後 T2 事務新插入一行記錄,這行記錄恰好可以滿足T1 所使用的查詢條件。然後 T1 再次對錶進行檢索,但又看到了 T2 插入的資料。 (和可重複讀類似,但是事務 T2 的資料操作僅僅是插入和刪除,不是修改資料,讀取的記錄數量前後不一致)。幻讀的重點在於新增或者刪除 (資料條數變化),同樣的條件,第1次和第2次讀出來的記錄數不一樣。

例子:

  1. 事務1,讀取所有工資為 1000 的員工(共讀取 10 條記錄 )
  2. 這時另一個事務向 employee 表插入了一條員工記錄,工資也為 1000
  3. 事務1再次讀取所有工資為 1000的 員工(共讀取到了 11 條記錄,這就產生了幻讀)

解決辦法:
  如果在操作事務完成資料處理之前,任何其他事務都不可以新增新資料,則可避免該問題。把資料庫的事務隔離級別調整到 SERIALIZABLE_READ

圖解:
  T1 讀取某個範圍的資料,T2 在這個範圍內插入新的資料,T1 再次讀取這個範圍的資料,此時讀取的結果和和第一次讀取的結果不同。

三、事務隔離級別

讀資料一致性及允許的併發副作用隔離級別 讀資料一致性 髒讀 不可重複讀 幻讀
未提交讀 最低級別,只能保證不讀取物理上損壞的資料 Y Y Y
已提交讀 語句級 N Y Y
可重複讀 事務級 N N Y
可序列化 最高級別,事務級 N N N

3.1 未提交讀 Read Uncommitted

最低的隔離等級,允許其他事務看到沒有提交的資料,會導致髒讀

3.2 已提交讀 Read Committed

被讀取的資料可以被其他事務修改,這樣可能導致不可重複讀。也就是說,事務讀取的時候獲取讀鎖,但是在讀完之後立即釋放(不需要等事務結束),而寫鎖則是事務提交之後才釋放,釋放讀鎖之後,就可能被其他事務修改資料。該等級也是 SQL Server 預設的隔離等級。

3.3 可重複讀 Repeated Read

所有被選中獲取的資料都不能被修改,這樣就可以避免一個事務前後讀取資料不一致的情況。但是卻沒有辦法控制幻讀,因為這個時候其他事務不能更改所選的資料,但是可以增加資料,即前一個事務有讀鎖但是沒有範圍鎖,為什麼叫做可重複讀等級呢?那是因為該等級解決了下面的不可重複讀問題。
  
引申:現在主流資料庫都使用 MVCC 併發控制,使用之後RR(可重複讀)隔離級別下是不會出現幻讀的現象。

3.4 序列化 Serializable

所有事務一個接著一個的執行,這樣可以避免幻讀 (phantom read),對於基於鎖來實現併發控制的資料庫來說,序列化要求在執行範圍查詢的時候,需要獲取範圍鎖,如果不是基於鎖實現併發控制的資料庫,則檢查到有違反序列操作的事務時,需回滾該事務。

四、MyISAM VS InnoDB

對比項 MyISAM InnoDB
主外來鍵 N Y
事務 N Y
行表鎖 表鎖,即使操作一條記錄也會鎖住整個表,不適合高併發操作 行鎖,操作時只鎖某一行,不對其他行有影響,適合高併發操作
快取 只快取索引,不快取真實資料 不僅快取索引還快取真實資料,對記憶體要求較高,而且記憶體大小對效能有決定性的影響
表空間
關注點 效能 事務
預設安裝 Y Y

五、鎖機制

5.1 樂觀鎖、悲觀鎖

1.樂觀鎖
顧名思義,就是很樂觀,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料,可以使用版本號等機制。

樂觀鎖適用於多讀的應用型別,這樣可以提高吞吐量,像資料庫如果提供類似於write_condition機制的其實都是提供的樂觀鎖。

2.悲觀鎖
顧名思義,就是很悲觀,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會block直到它拿到鎖

傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。

5.2 表鎖

1.表共享讀鎖(Table Read Lock)
對MyISAM表進行讀操作時,它不會阻塞其他使用者對同一表的讀請求,但會阻塞對同一表的寫操作。

2.表獨佔寫鎖(Table Write Lock)
對MyISAM表的寫操作,則會阻塞其他使用者對同一表的讀和寫操作。

5.3 行鎖

1.共享鎖(S)
共享鎖又稱為讀鎖。若事務T對資料物件A加上S鎖,則事務T可以讀A但不能修改A,其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這就保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。

2.排他鎖(X)
排它鎖又稱為寫鎖。若事務T對資料物件A加上X鎖,則允許T讀取和修改A,其他任何事務都不能再對A加任何型別的鎖,直到T釋放A上的鎖。這就保證了其他事務在T釋放A上的鎖之前不能再讀取和修改A

六、索引

索引(Index)是幫助MySQL高效獲取資料的資料結構,可以被理解為排好序的快速查詢資料結構

一般來說索引本身也很大,不可能全部儲存在記憶體中,因此索引往往以索引檔案的形式儲存在磁碟上。

我們通常所說的索引,如果沒有特別指明,都是指B樹(多路搜尋樹,並不一定是二叉的)結構組織的索引。其中聚集索引、次要索引、覆蓋索引、混合索引、字首索引、唯一索引預設都是使用B+樹索引,統稱索引。

6.1 索引的特點

  1. 可以加快資料庫的檢索速度
  2. 降低資料庫插入、修改、刪除等維護的速度
  3. 只能建立在表上,不能建立到檢視上
  4. 既可以直接建立又可以間接建立
  5. 可以在優化隱藏中使用索引
  6. 使用查詢處理器執行SQL語句,在一個表上,一次只能使用一個索引

6.2 索引的優點

  1. 建立唯一性索引,保證資料庫表中每一行資料的唯一性
  2. 大大加快資料的檢索速度,這是建立索引的最主要的原因
  3. 加速資料庫表之間的連線,特別是在實現資料的參考完整性方面特別有意義
  4. 在使用分組和排序子句進行資料檢索時,同樣可以顯著減少查詢中分組和排序的時間
  5. 通過使用索引,可以在查詢中使用優化隱藏器,提高系統的效能

6.3 索引的缺點

  1. 建立索引和維護索引要耗費時間,這種時間隨著資料量的增加而增加
  2. 索引需要佔用物理空間,除了資料表佔用資料空間之外,每一個索引還要佔一定的物理空間,如果建立聚簇索引,那麼需要的空間就會更大
  3. 當對錶中的資料進行增加、刪除和修改的時候,索引也需要維護,降低資料維護的速度

6.4 不會使用索引的時機

  1. 如果MySQL估計使用全表掃秒比使用索引快,則不適用索引。
select * from table_name where key>1 and key<90;
//如果列key均勻分佈在1和100之間,這個查詢使用索引就不是很好
  1. 如果條件中有or,即使其中有條件帶索引也不會使用。
select * from table_name where key1='a' or key2='b';
//在key1上有索引而在key2上沒有索引,則該查詢也不會走索引
  1. 複合索引,如果索引列不是複合索引的第一部分,則不使用索引(即不符合最左字首)。
select * from table_name where key2='b';
//符合索引為(key1,key2),則查詢將不會使用索引
  1. 如果like是以 % 開始的,則該列上的索引不會被使用。
select * from table_name where key1 like '%a';
//該查詢即使key1上存在索引,也不會被使用如果列型別是字串,那一定要在條件中使用引號引起來,否則不會使用索引
  1. 如果列為字串,則where條件中必須將字元常量值加引號,否則即使該列上存在索引,也不會被使用。
select * from table_name where key1=1;
//如果key1列儲存的是字串,即使key1上有索引,也不會被使用。

6.5 適合建立索引的時機

  1. 為經常出現在關鍵字order by、group by、distinct後面的欄位,建立索引。
  2. 在union等集合操作的結果集欄位上,建立索引。
  3. 經常用作查詢選擇 where 後的欄位,建立索引。
  4. 在經常用作表連線 join 的屬性上,建立索引。
  5. 考慮使用索引覆蓋。對資料很少被更新的表,如果使用者經常只查詢其中的幾個欄位,可以考慮在這幾個欄位上建立索引,從而將表的掃描改變為索引的掃描。

6.6 索引分類

6.6.1 單值索引

即一個索引只包含單個列,一個表可以有多個單值索引。

6.6.2 唯一索引

索引列的值必須唯一,但允許有空值。

6.6.3 聯合索引

兩個或更多個列上的索引被稱作聯合索引,聯合索引又叫複合索引。對於複合索引:Mysql 從左到右的使用索引中的欄位,一個查詢可以只使用索引中的一部份,但只能是最左側部分

例如索引是key index (a,b,c),可以支援[a]、[a,b]、[a,b,c] 3種組合進行查詢,但不支 [b,c] 進行查詢。當最左側欄位是常量引用時,索引就十分有效。

七、JOIN

例如我們有兩張表,Orders 表通過外來鍵 Id_P 和 Persons 表進行關聯。

7.1 inner join

在兩張表進行連線查詢時,只保留兩張表中完全匹配的結果集。

inner join

我們使用 inner join 對兩張表進行連線查詢,sql如下:

SELECT Persons.LastName, Persons.FirstName, Orders.OrderNo
FROM Persons
INNER JOIN Orders
ON Persons.Id_P=Orders.Id_P
ORDER BY Persons.LastName

查詢結果集如下,此種連線方式 Orders 表中 Id_P 欄位在 Persons 表中找不到匹配的,則不會列出來。

7.2 left join

在兩張表進行連線查詢時,會返回左表所有的行,即使在右表中沒有匹配的記錄。

left join

我們使用 left join 對兩張表進行連線查詢,sql如下:

SELECT Persons.LastName, Persons.FirstName, Orders.OrderNo
FROM Persons
LEFT JOIN Orders
ON Persons.Id_P=Orders.Id_P
ORDER BY Persons.LastName

查詢結果如下,可以看到,左表(Persons表)中 LastName 為 Bush 的行的 Id_P 欄位在右表(Orders表)中沒有匹配,但查詢結果仍然保留該行。

7.3 right join

在兩張表進行連線查詢時,會返回右表所有的行,即使在左表中沒有匹配的記錄。

right join

我們使用right join對兩張表進行連線查詢,sql如下:

SELECT Persons.LastName, Persons.FirstName, Orders.OrderNo
FROM Persons
RIGHT JOIN Orders
ON Persons.Id_P=Orders.Id_P
ORDER BY Persons.LastName

查詢結果如下,Orders 表中最後一條記錄 Id_P 欄位值為 65,在左表中沒有記錄與之匹配,但依然保留。

7.4 full join

在兩張表進行連線查詢時,返回左表和右表中所有沒有匹配的行。

full join

我們使用 full join 對兩張表進行連線查詢,sql如下:

SELECT Persons.LastName, Persons.FirstName, Orders.OrderNo
FROM Persons
FULL JOIN Orders
ON Persons.Id_P=Orders.Id_P
ORDER BY Persons.LastName

查詢結果如下,查詢結果是 left join 和 right join 的並集。