參考文章;
https://www.oreilly.com/library/view/high-performance-mysql/9781449332471/ch01.html
三級架構
第一級向外提供連接,身份認證,安全維護等;
第二級:是mysql server的核心,核心功能就在這;
第三級:存儲引擎,數據真正存放的地方;
第二級和存儲引擎,通過存儲引擎提供的api 交互;
mysql支持多個存儲引擎;
連接管理和安全認證
第一層:
每個connect都會在服務進程中開啟一個線程,
connect的所有操作都是在這個線程內完成的,
并且這個線程會被緩存起來多次使用;
因此每次連接不需要銷毀connect,
新的連接可以反復使用已經創建的這個connect.
認證管理:每次連接都要認證:
認證有 username:password和ssl兩種方式,
并且服務會控制用戶的執行權限;
第二層:解析,優化和執行
執行sql語句,緩存數據,優化sql等
并發控制
MySQL has to do this at two levels: the server level and the storage engine level.
mysql是如何處理并發讀寫的:
并發:要求的是多任務同時執行處理,同時執行;
共享鎖:讀鎖:允許多個線程同時讀取,前提是沒有寫數據;
獨占鎖;寫鎖,阻止所有讀取操作,只能允許一個線程寫數據;
鎖粒度:
鎖一行;
鎖多行;
鎖整張表;
提高共享資源并發性的一種方法是更有選擇性地鎖定什么。而不是鎖定整個資源,只鎖定包含您需要更改的數據的部分。更好的是,只鎖定您計劃更改的確切數據。最大限度地減少您一次鎖定的數據量,只要它們不相互沖突,就可以同時更改給定資源。
問題是鎖消耗資源。每個鎖定操作 - 獲取鎖定,檢查鎖定是否空閑,釋放鎖定等等 - 都有開銷。如果系統花費太多時間來管理鎖而不是存儲和檢索數據,那么性能就會受到影響。
鎖定策略是鎖定開銷和數據安全之間的折衷,并且該折衷會影響性能。大多數商業數據庫服務器都沒有給您太多選擇:
您在表中獲得了所謂的行級鎖定,通過各種通常很復雜的方法來提供許多鎖定的良好性能。
另一方面,MySQL確實提供了選擇。它的存儲引擎可以實現自己的鎖定策略和鎖定粒度。
鎖管理是存儲引擎設計中非常重要的決策;
將粒度固定在一定水平可以為某些用途提供更好的性能,但使該引擎不太適合其他目的。
由于MySQL提供多個存儲引擎,因此不需要單一的通用解決方案。
讓我們來看看兩個最重要的鎖定策略。
鎖類型
表鎖
MySQL中最基本的鎖定策略,以及開銷最低的策略是表鎖。表鎖類似于前面描述的郵箱鎖:它鎖定整個表。當客戶端希望寫入表(插入,刪除,更新等)時,它會獲取寫鎖定。這樣可以防止所有其他讀寫操作。當沒有人寫數據時,讀數據可以獲得讀鎖,這與其他讀鎖不沖突。
表鎖在特定情況下具有良好性能的變化。例如,READ LOCAL
表鎖允許某些類型的并發寫操作。
寫鎖也具有比讀鎖更高的優先級,因此即使讀取器已經在隊列中,寫鎖的請求也將前進到鎖隊列的前面(寫鎖可以超過隊列中的讀鎖,但讀鎖不能提前過去寫鎖)。
雖然存儲引擎可以管理自己的鎖,但MySQL本身也使用各種鎖,這些鎖實際上是用于各種目的的表級。例如ALTER TABLE
,無論存儲引擎如何,服務器都會對語句使用表級鎖。
行鎖
提供最大并發性(并且承載最大開銷)的鎖定樣式是使用行鎖。
此策略眾所周知的行級鎖定可在InnoDB和XtraDB存儲引擎中使用。
行鎖在存儲引擎中實現,而不是在服務器中實現(如果需要,請參考邏輯體系結構圖)。
服務器完全不知道存儲引擎中實現的鎖,正如您將在本章后面和整本書中看到的那樣,存儲引擎都以自己的方式實現鎖定。
事務
事務是一組以原子方式處理的SQL queries,作為單個工作單元。
這么一組sql queries,要么全部成功,要么全部失敗。
原子性
事務必須作為單個不可分割的工作單元運行,以便應用或回滾整個事務。當事務是原子的時,就沒有部分完成的事務:它是全部或全部。
一致性
數據庫應始終從一個一致狀態移動到下一個狀態。在我們的示例中,一致性確保第3行和第4行之間的崩潰不會導致$ 200從支票帳戶中消失。由于事務從未提交,因此事務的所有更改都不會反映在數據庫中。
隔離性
在事務完成之前,事務的結果通常對其他事務是不可見的。這樣可以確保如果銀行帳戶摘要在第3行之后但在我們的示例中第4行之前運行,則它仍會在支票帳戶中看到$ 200。當我們討論隔離級別時,你會理解為什么我們說通常是 不可見的。
持久性
一旦提交,事務的更改是永久性的。
隔離級別
SQL標準定義了四個隔離級別,其中包含特定的規則,在事務內外都可以看到更改。
較低的隔離級別通常允許更高的并發性并具有更低的開銷。
注意
每個存儲引擎實現的隔離級別略有不同,如果您習慣使用其他數據庫產品,它們不一定符合您的預期(因此,我們不會在本節中詳細介紹)。您應該閱讀您決定使用的任何存儲引擎的手冊。
單獨的增刪改查等一條sql語句必須是原子操作,也就是或行在同一時刻只能被一個線程寫入;每一行在寫數據的時候,都是被鎖起來的,同一時間每一行只允許一個線程執行寫數據。
臟讀:讀取未提交的數據也稱為臟讀。
一個事務包含多條sql語句,以轉賬為例,
1 START TRANSACTION;
2 SELECT balance FROM checking WHERE customer_id = 10233276;
3 UPDATE checking SET balance = balance - 200.00 WHERE customer_id = 10233276;
4 UPDATE savings SET balance = balance + 200.00 WHERE customer_id = 10233276;
5 COMMIT;
2,3,4每條執行都是原子操作,也就是當前行被鎖起來了,只允許一個線程寫數據;
當執行了3,后續sql還沒執行呢,事務還沒提交呢,這個時候用戶customer_id = 10233276的戶頭上就消失了200美金,如果4執行失敗,那問題就大了。這就是臟讀。
不可重復讀:運行相同的語句兩次但是查看的數據是不同的;
為什么?因為事務對外不可見,舉例兩個事務1,事務2
事務2在事務1開始前讀了數據,
恰巧事務1修改的就是事務2要讀取的數據,當事務1執行并提交,
事務2再次查詢,這次查詢到的是事務1提交后的數據,
針對事務2來說,兩次查詢,但是兩次查詢出來的數據不一樣。
大多數數據庫系統(但不是MySQL?。┑哪J隔離級別是READ COMMITTED
。
它滿足前面使用的簡單隔離定義:事務只會看到那些在開始時已經提交的事務所做的更改,并且在其提交之前,其他更改將不會被其他人看到。
這個級別仍然允許所謂的不可重復的閱讀。
這意味著您可以運行相同的語句兩次并查看不同的數據。
幻讀:保證同一事務中兩次讀取的數據相同,但是第二次的讀取數據依然是第一次版本的數據,只是“看起來相同”而已;
因為還是不能阻止其他事務在這個多行數據范圍內插入新數據和修改舊數據;
REPEATABLE READ
解決了READ UNCOMMITTED
允許的問題。
它保證事務讀取的任何行在同一事務中的后續讀取中“看起來相同”,但理論上它仍然允許另一個棘手的問題:幻影讀。
簡單地說,當您選擇某些行范圍時,可能會發生幻像讀取,另一個事務會在該范圍中插入新行,然后再次選擇相同的范圍; 然后你會看到新的“幽靈”行。
InnoDB和XtraDB使用多版本并發控制來解決幻像讀取問題,我們將在本章后面解釋。
死鎖:只要不是多行范圍鎖定,都是有死鎖的可能。
一個 死鎖是指兩個或多個事務相互持有并請求鎖定相同資源,從而創建依賴關系循環。當事務嘗試以不同的順序鎖定資源時會發生死鎖。只要多個事務鎖定相同的資源,它們就會發生。例如,考慮針對StockPrice
表運行的這兩個事務:
Transaction #1
START TRANSACTION;
UPDATE StockPrice SET close = 45.50 WHERE stock_id = 4 and date = '2002-05-01';
UPDATE StockPrice SET close = 19.80 WHERE stock_id = 3 and date = '2002-05-02';
COMMIT;
Transaction #2
START TRANSACTION;
UPDATE StockPrice SET high = 20.12 WHERE stock_id = 3 and date = '2002-05-02';
UPDATE StockPrice SET high = 47.20 WHERE stock_id = 4 and date = '2002-05-01';
COMMIT;
如果你運氣不好,每個事務都會執行第一個查詢并更新一行數據,并將其鎖定在進程中。然后,每個事務將嘗試更新其第二行,但卻發現它已被鎖定。這兩個事務將永遠等待彼此完成,除非有什么干預來打破僵局。
為了解決這個問題,數據庫系統實現了各種形式的死鎖檢測和超時。更復雜的系統,例如InnoDB存儲引擎,會注意到循環依賴并立即返回錯誤。這可能是一件好事 - 否則,死鎖會表現為非常慢的查詢。其他人將在查詢超過鎖定等待超時后放棄,這并不總是好的。方式InnoDB當前處理死鎖是為了回滾具有最少獨占行鎖的事務(其中一個近似的度量標準將是最容易回滾的)。
鎖定行為和順序是特定于存儲引擎的,因此某些存儲引擎可能會在某些語句序列上死鎖,即使其他語句也不會。死鎖具有雙重性質:由于真正的數據沖突,有些是不可避免的,有些是由存儲引擎的工作原理引起的。
如果不部分或全部回滾其中一個交易,就不能破解死鎖。它們是事務系統中的事實,您的應用程序應該設計為處理它們。許多應用程序可以從一開始就簡單地重試它們的事務。
事務記錄:就是我考慮的災害情況下:斷電,數據庫的記錄恢復
事務記錄有助于提高事務處理效率。
每次發生更改時,存儲引擎都可以更改其內存中的數據副本,而不是更新磁盤上的表。
這非常快。然后,存儲引擎可以將更改記錄寫入事務日志,該日志位于磁盤上,因此是持久的。
這也是一個相對較快的操作,因為附加日志事件涉及磁盤的一個小區域中的順序I / O,而不是許多地方的隨機I / O.
然后,稍后,進程可以更新磁盤上的表。
因此,大多數使用這種技術的存儲引擎(稱為預寫日志記錄)最后將更改寫入磁盤兩次。
如果在將更新寫入事務日志之后但在對數據本身進行更改之前發生崩潰,則存儲引擎仍可在重新啟動時恢復更改。恢復方法因存儲引擎而異。
我的方法:
第一步:日志記錄第一行:寫下事務sql;
第二步: 向磁盤寫數據,持久化數據;
第三步:日志記錄 第二行:ack
當在第二步發生了災害:斷電,事務要寫的數據沒有完全執行,就不會有第三步ack,當恢復數據的時候,只有看到日志記錄中這個事務有 ack記錄,那么才能認定事務提交并寫入成功,否則的話,不會恢復沒有ack的事務;
多版本并發控制
InnoDB通過在每行中存儲兩個額外的隱藏值來實現MVCC,這些值記錄了創建行以及何時過期(或刪除)。該行不存儲發生這些事件的實際時間,而是存儲每個事件發生時的系統版本號。這是一個每次交易開始時遞增的數字。每個事務在其開始時保留其自己的當前系統版本記錄。每個查詢都必須根據事務的版本檢查每一行的版本號。讓我們看看當事務隔離級別設置為時,這如何適用于特定操作REPEATABLE READ
:
InnoDB必須檢查每一行以確保它符合兩個標準:
InnoDB必須找到至少與事務一樣舊的行的版本(即,其版本必須小于或等于事務的版本)。這可確保在事務開始之前存在行,或者事務創建或更改行。
行的刪除版本必須未定義或大于事務的版本。這可確保在事務開始之前未刪除該行。
通過兩個測試的行可以作為查詢的結果返回。
InnoDB使用新行記錄當前系統版本號。
InnoDB將當前系統版本號記錄為行的刪除ID。
InnoDB使用新行版本的系統版本號寫入該行的新副本。它還將系統版本號寫為舊行的刪除版本。
所有這些額外記錄保留的結果是大多數讀取查詢永遠不會獲得鎖定。他們只是盡可能快地讀取數據,確保只選擇符合條件的行。缺點是存儲引擎必須在每行存儲更多數據,在檢查行時執行更多工作,以及處理一些額外的內務操作。
MVCC只適用于REPEATABLE READ
和READ COMMITTED
隔離級別。 READ UNCOMMITTED
不是MVCC兼容的[ 7 ],因為查詢不會讀取適合其事務版本的行版本; 他們閱讀最新版本,無論如何。SERIALIZABLE
不是MVCC兼容的,因為讀取鎖定它們返回的每一行。
java連接池和mysql線程
java客戶端都是先創建固定數量的連接池,
而連接池就對應了mysql的線程,
舉例,連接池中連接數量是8個連接,
那對應就是8個線程,
當連接池都被占用,加入連接池等待隊列,
所以說,mysql根本就沒啥事務隊列, mysql就8個線程而已,
再借助版本并發控制(mvvc),
寫鎖是獨占鎖,寫數據時,不允許其他線程讀數據和寫數據,
讀鎖是共享鎖,
問題01:怎么保證java客戶端先后發出的兩次事務,最后數據庫的數據是最后一次修改后的最新數據呢?
考慮網絡延遲,用戶最先發出的請求,因為網絡延遲,第二個到達服務端;
服務端發送給數據庫的事務請求,也因為網絡延遲,第一個發出的事務請求,反而第二個到達;
比如現在a 地址余額不足,
用戶向a地址轉賬100元,然后用戶在a地址花掉100元,
但是因為網絡延遲,導致用戶先花掉100元在先,而轉賬100元到地址a在后;
沒辦法避免的,只能通過業務手段區分;
比如轉賬操作,都是有到賬時間的,不能說我還沒到賬,你就可以花錢,
開發人員對未來可能的數據變化是不知道的,
只能盯著過去已經存在的數據,也就是從表中讀取數據;
對未來的數據,不用管;
只看你當前余額,未到賬的余額,不計入的,
比如轉賬,余額充足你就轉賬唄,
場景03:修改商品數據
常見04;修改用戶信息
針對這種場景也是需要前臺業務流程的引導的;
問題02:怎么保證并發修改同一條數據,最終結果就是最新提交的數據呢?
答案:mysql管不了你的業務邏輯,mysql只知道有幾個線程,
現在這個線程要執行什么任務,是阻塞,還是讀,還是寫,
共享鎖和獨占鎖
reentrantreadwritelock example
沒有線程在讀和寫,這個時候才能獨占鎖,寫數據;
沒有線程在寫數據,并且沒有線程要獲取寫鎖,可以共享讀了;
業務邏輯需要你自己在開發中設計,你就不應該讓并發修改相同數據的事務出現,這是你業務邏輯的問題;
java客戶端只需要創建數據庫連接池就行,把控業務邏輯;