數據庫事務
- 什麼是事務:事務(Transaction)是訪問並可能更新數據庫中各種數據項的一個程序執行單元(unit)。,事務由事務開始(begin transaction)和事務結束(end transaction)之間執行的全體操作組成。
- 事務是一個不可分割的數據庫操作序列,也是數據庫併發控制的基本單位,其執行的結果必須使數據庫從一種一致性狀態變到另一種一致性狀態。
- 事務結束有兩種,事務中的步驟全部成功執行時,提交事務,如果其中一個失敗,那麼將發生回滾操作,並且撤銷之前的所有操作。
- 在MySQL中只有InnoDB或BDB類型的數據表支持事務,可以通過show engines查看
- 在nosql數據庫中,事務要求很低,
- 使用SQL事務的原因:保證數據安全有效
- 事務的特性:事務是恢復和併發控制的基本單位。
事務操作
- 提交:commit
- 回滾:rollback
四個特徵(ACID)
- 原子性(Atomicity):
- 操作要麼全部成功,否則回滾,什麼也不做(“要麼不做,要麼全做!”)
- 一致性(Consistency):
- 事務操作之後,數據庫所處的狀態和業務規則是一致的;比如a,b賬戶相互轉賬之後,總金額不變!
- 如果數據庫系統運行中發生故障,有些事務尚未完成就被迫中斷,這些未完成事務對數據庫所做的修改有一部分已寫入物理數據庫,這時數據庫就處於一種不正確的狀態,或者說是不一致的狀態
- 隔離性(Isolation):
- 事務的隔離級別有4級,一個事務的執行不能有其他事務的干擾,事務的內部操作和使用數據對其他的併發事務是隔離的,互不干擾。
- 持久性(Durability):
- 事務一旦提交後,他對數據的改變應該永久性的,不能回滾。接下來其他的操作或者故障不會對已經提交了的事務產生影響。
事務隔離級別
隔離級別決定了一個session中的事務可能對另一個session中的事務的影響。ANSI標準定義了4個隔離級別,MySQL的InnoDB和Oracle都支持,分別是:
- Oracle共支持3種事務隔離級別:Oracle默認的隔離級別是read committed。
- serializable
- read committed
- read only (Oracle自己獨有的事務隔離級別)
- READ UNCOMMITTED(讀取未提交):幻讀,不可重複讀和髒讀均允許;通常稱爲dirty read。
- 則其他線程可以看到未提交的數據, 因此就出現髒讀;
- READ COMMITTED(讀取已經提交):允許幻讀和不可重複讀,但不允許髒讀;
- 即沒提交的數據別人是看不見的,就避免了髒讀
- 正在讀取的數據只獲得了讀取鎖,讀完之後就解鎖,不管當前事務有沒有結束,這樣就容許其他事務修改本事務正在讀取的數據。導致不可重複讀。
- REPEATABLE READ(可重複讀):允許幻讀,但不允許不可重複讀和髒讀;
- 對正在操作的數據加鎖,並且只有等到事務結束才放開鎖, 則可以避免不可重複讀;
- 只能保證正在被本事務操作的數據不被其他事務修改,卻無法保證有其他事務提交新的數據
- 比如:線程1在操作表T1的時候(特別是統計性的事務),其他線程仍然可以提交新數據到表T1,這樣會導致線程1兩次統計的結果不一致,就像發生幻覺一樣(幻讀)。
- SERIALIZABLE(序列化):幻讀,不可重複讀和髒讀都不允許;最高級別的隔離,只允許事務串行執行。
- 因爲獲得範圍鎖,且事務是一個接着一個串行執行,則保證了不會發生幻讀。
- 由此可見,隔離級別越高,受其他事物干擾越少,併發性能越差。
- ORACLE默認的是 READ COMMITTED。
- MYSQL默認的是 REPEATABLE READ。
查看事務隔離級別的方法
1. SELECT * FROM dual FOR UPDATE; 2. SELECT s.sid, s.serial#, CASE BITAND(t.flag, POWER(2, 28)) WHEN 0 THEN 'READ COMMITTED' ELSE 'SERIALIZABLE' END AS isolation_level FROM v$transaction t JOIN v$session s ON t.addr = s.taddr AND s.sid = sys_context('USERENV', 'SID'); 設置隔離級別使用 SET TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE > > > READ|SERIALIZABLE]
修改事務隔離級別
- 在MySQL中默認事務隔離級別是可重複讀(Repeatable read).可通過SQL語句查詢:mysql> SELECT @@global.tx_isolation
- 查看InnoDB系統級別的事務隔離級別:mysql> SELECT @@tx_isolation
- 修改mysql:mysql> set global transaction isolation level read committed;
- 修改InnoDB:mysql> set session transaction isolation level read committed;
事務併發帶來的問題
- 幻讀:幻讀的重點在於新增或者刪除,同樣條件下兩次讀出來的記錄數不一樣。
- 一個事務重新執行一個查詢,返回一套符合查詢條件的行,發現這些行因爲其他最近提交的事務而發生了改變。
- 事務T1讀取一條指定的WHERE子句所返回的結果集。然後事務T2新插入 一行記錄,這行記錄恰好可以滿足T1所使用的查詢條件中的WHERE 子句的條件。然後T1又使用相同的查詢再次對錶進行檢索,但是此時卻看到了事務T2剛纔插入的新行。這個新行就稱爲“幻像”,因爲對T1來說這一行就像突 然出現的一樣。
- 舉例:目前分數爲90分以上的的學生有15人,事務A讀取所有分數爲90分以上的的學生人數有15人。此時,事務B插入一條分數爲99的學生記錄。這時,事務A再次讀取90分以上的的學生,記錄爲16人。此時產生了幻讀。
- 大部分數據庫缺省的事物隔離級別都會出現這種狀況,此種事物隔離級別將帶來表級鎖
- 不可重複讀:不可重複讀的重點是修改,同樣條件下兩次讀取結果不同,也就是說,被讀取的數據可以被其它事務修改;
- 一個事務重新讀取前面讀取過的數據,發現該數據已經被另一個已提交的事務修改過。
- 事務T1讀取一行記錄,緊接着事務T2修改了T1剛纔讀取的那一行記錄。然後T1又再次讀取這行記錄,發現與剛纔讀取的結果不同。這就稱爲“不可重複”讀,因爲T1原來讀取的那行記錄已經發生了變化。
- 舉例:在事務A中,讀取到小明的分數爲89,操作沒有完成,事務還沒提交。與此同時,事務B把小明的分數改爲98,並提交了事務。隨後,在事務A中,再次讀取小明的分數,此時分數變爲98。在一個事務中前後兩次讀取的結果並不致,導致了不可重複。
- 髒讀:
- 一個事務讀取了其另一個未提交的並行事務寫的數據。
- 事務T1更新了一行記錄的內容,但是並沒有提交所做的修改。事務T2讀取更新後的行,然後T1執行回滾操作,取消了剛纔所做的修改。現在T2所讀取的行就無效了,即髒數據。
- 舉例:小明的分數爲89,事務A中把他的分數改爲98,但事務A尚未提交。與此同時,事務B正在讀取小明的分數,讀取到小明的分數爲98。隨後,事務A發生異常,而回滾了事務。小明的分數又回滾爲89。最後,事務B讀取到的小明的分數爲98的數據即爲髒數據事務B做了一次髒讀。
MYSQL的事務處理主要有兩種方法
- 任何SQL語句如果僅僅是運行,是不會對數據庫做持久化修改的,必須提交事務才能完成持久化保持。MySQL數據庫默認自動提交事務。
- 關閉MySQL自動提交:set autocommit = 0;
- 開啓MySQL自動提交: set autocommit = 1;
- 查看當前自動提交狀態:show variables like “autocommit”; // on/off
- 手動提交:update user set money = 5000 where id = 2;
- 用BEGIN,ROLLBACK,COMMIT來實現
- 開始:START TRANSACTION或BEGIN語句可以開始一項新的事務
- 提交:COMMIT可以提交當前事務,是變更成爲永久變更
- 回滾:ROLLBACK可以回滾當前事務,取消其變更
- 直接用set來改變mysql的自動提交模式
- MYSQL默認是自動提交的,也就是你提交一個QUERY,它就直接執行!
- 我們可以通過set autocommit=0/1 禁止自動提交/開啓自動提交
- 但注意當你用 set autocommit=0 的時候,你以後所有的SQL都將做爲事務處理,直到你用commit確認或rollback結束,並且只用於當前連接。
事務分爲顯性事務和隱性事務
- 顯性事務:使用BEGIN TRANSACTION明確指定的事務
- 隱性事務:select、insert、update、delete語句都是隱性事務的一部分。
- 自動提交事務:每一條單獨的SQL語句都是一個事務,如果成功執行,就提交,否則回滾。
MySQL的事務支持
MySQL的事務支持不是綁定在MySQL服務器本身,而是與存儲引擎相關
- MyISAM:不支持事務,用於只讀程序提高性能;
- InnoDB:支持ACID事務、行級鎖、併發;
- Berkeley DB:支持事務。
鎖:排它鎖、共享鎖、樂觀鎖、悲觀鎖。
問題:二個或以上事務在操作同一個共享記錄集時,可能會出現的問題:
- (A)髒讀 (B)不可重複讀 (C)幻讀
- 隔離級別:(1)read-uncommit, (2)read-commit, (3)read-repeatable, (4)read-serializable
- 都是用來阻止上面的問題的,其中:
- (1)什麼都阻止不了。
- (2)阻止(A)
- (3)阻止(A)(B)
- (4)阻止(A)(B)(C)
- (1)--->(4)隔離級別越高,性能損失越大。
存儲過程
- 什麼是存儲過程?怎麼理解這個存儲過程?
- 存儲在數據庫中一組完成特定功能的SQL代碼組合(SQL代碼塊),經過一次編譯後再次調用不需要編譯,用戶通過指定存儲過程的名稱並給出參數(如果該存儲過程帶有參數)來執行它,
- 是一種數據庫中存儲複雜程序,以便外部程序調用的一種數據庫對象,可以視爲數據庫中的一種函數或子程序。
- 類似於c語言中的函數,存儲過程的名稱就是函數名,存儲過程的內部就是函數體,同樣可以被重複調用
存儲過程的優點
- 重複使用。存儲過程可以重複使用,一次編寫多次調用,避免開發者重複編寫SQL語句,可以減少客服端和服務端的數據傳輸頻率 ,提高效率。
- 減少網絡流量。存儲過程位於服務器上,調用的時候只需要傳遞存儲過程的名稱以及參數就可以了,因此降低了網絡傳輸的數據量。
- 執行速度快,模塊化的程序設計,如果某個操作需要執行大量的SQL語句或者某個重複的SQL語句,存儲過程比直接執行SQL語句更快。
- 安全性。參數化的存儲過程可以防止SQL注入式攻擊,而且可以將Grant、Deny以及Revoke權限應用於存儲過程。對於沒有權限執行存儲過程的用戶,可以授權來調用存儲過程。
- 效率高。由於數據庫執行動作時,是先編譯後執行的。然而存儲過程是一個編譯過的代碼塊,所以執行效率要比T-SQL語句高。
存儲過程的缺點
- 調試麻煩
- 移植性差:不同數據庫支持的語言不一樣,其存儲過程的編寫規則也不一樣,所以存儲過程無法移植到另一類數據庫。
- 重新編譯問題。因爲後端代碼是運行前編譯的,如果帶有引用關係的對象發生改變時,受影響的存儲過程、包將需要重新編譯(不過也可以設置成運行時刻自動編譯)。
- 不能大量使用:如果大量使用,程序交付後隨着用戶需求的增加,會導致數據結構的變化,接着就是系統相關的問題,最後如果用戶想維護該系統的代價是空前的
創建存儲過程:關鍵字procedure
create procedure 存儲過程名稱(參數列表) routine_body:SQL語句主體 begin/end來標識代碼的開始和結束 參數列表: 三部分組成:輸入/輸出類型,參數名,參數類型 存儲過程的參數分爲兩類:輸入參數,輸出參數(相當於java方法的返回值) 其中存儲過程名不能超過128個字。每個存儲過程中最多設定1024個參數
存儲過程分類
- 系統存儲過程:以sp_開頭,用來進行系統的各項設定.取得信息.相關管理工作。
- 本地存儲過程: 用戶創建的存儲過程是由用戶創建並完成某一特定功能的存儲過程,事實上一般所說的存儲過程就是指本地存儲過程。
- 遠程存儲過程: 在SQL Server2005中,遠程存儲過程(Remote Stored Procedures)是位於遠程服務器上的存儲過程,通常可以使用分佈式查詢和EXECUTE命令執行一個遠程存儲過程。
- 擴展存儲過程: 擴展存儲過程(Extended Stored Procedures)是用戶可以使用外部程序語言編寫的存儲過程,而且擴展存儲過程的名稱通常以xp_開頭。
- 臨時存儲過程:又分爲兩種:
- 一是本地臨時存儲過程,以井字號(#),作爲其名稱的第一個字符,則該存儲過程將成爲一個存放在tempdb數據庫中的本地臨時存儲過程,且只有創建它的用戶才能執行它;
- 二是全局臨時存儲過程,以兩個井字號(##)號開始,則該存儲過程將成爲一個存儲在tempdb數據庫中的全局臨時存儲過程,全局臨時存儲過程一旦創建,以後連接到服務器的任意用戶都可以執行它,而且不需要特定的權限。
入參存儲過程 :(相當於功能模塊函數 )
// java程序對比 模塊函數 target = 1 name = MySQL name = java public viod add_name( int id) { String name = " "; if(id==1){ name = "MySQL"; }else { name ="Java" ; } String sql = "insert into user( name ) values( "+name+")"; } //SQL語言對比 存儲過程 create procedure add_name( in target int ) begin if target = 1 then set name = "MySQL"; else set name = "Java"; end if; insert into user(name) values(name); end; 調用:call 存儲過程名(參數) 刪除:drop procedure 存儲過程名
出參存儲過程
create procedure count_of_user(out count int ) begin select count( id ) into count from user; end 調用:call count_of_user( @count ) ,count_of_user會將執行的結果賦值給@count 直接查詢@count 就可以獲取結果。
存儲過程的流程控制語句
if 判斷結構: create procedure example_if( in x int ) begin if x=1 then select name from user; else if x=2 then select id from user; end if; end; ------------------------------------------------------------------------- case結構: create procedure example_if( in x int ) begin case = x when 1 then select id from user; when 2 then select name from user; end case; end; -------------------------------------------------------------------------- while 結構 // java語言結構 int sum =0; int i = 1; while: int i = 1; while( i<=100 ){ sum += i; i++; } syso(sum) ; //SQL語言結構:(注意語句的分號要寫) create procedure example_while( out sum int ) begin declare i int default 1; declare s int default 0; while i<=100 do set s = s + i ; end i = i+1; end while set sum = s; end ;