這篇文章詳細介紹資料庫事務與鎖的相關知識。主要是一些概念性的東西看起來可能比較乏味,但作為一個合格的程式設計師來說,你應該掌握也必須掌握。這些理論知識好比是一個人的內功,我們平常敲程式碼是外功,只有內外兼修,互相促進,才能達到武林高手的境界。好了廢話不多說,從下面開始。
資料庫事務
事務的邊界
事務的開始邊界(begin)
事務的結束邊界(commit):提交事務,永久保存被事務更新後的資料庫狀態。
事務的異常結束邊界(rollback):撤銷事務,使資料庫退回到執行事務前的初始狀態。
每啟動一個MySQL.exe程序,就會得到一個單獨的資料庫連線。每個資料庫連線都有一個全域變數autocommit,表示目前的交易模式,它有兩個值可選:
0:表示手動提交模式
1:表示自動提交模式,預設值
1:表示自動提交模式,預設值
資料庫事務的4個特性(ACID):
原子性(Atomicity):事務是一個原子操作單元,其對資料的修改,要麼全部執行,要麼全都不一致;
持久性(Durable):事務完成之後,它對於資料的修改是永久性的,即使出現系統故障也能夠維持。
事務隔離級別
Read Uncommitted (讀取未提交內容)
Repeatable Read (可重讀)
這是 MySQL 的預設事務隔離級別,它確保相同交易的多個實例在並發讀取資料時,會看到相同的資料行。不過理論上,這會導致另一個棘手的問題:幻讀 ( Phantom Read )。簡單的說,幻讀指當用戶讀取某一範圍的數據行時,另一個事務又在該範圍內插入了新行,當用戶再讀取該範圍的數據行時,會發現有新的”幻影” 行。 InnoDB和 Falcon 儲存引擎透過多版本並發控制( MVCC , Multiversion Concurrency Control )機制解決了這個問題。
Serializable (可串行化)
這是最高的隔離級別,它透過強制事務排序,使其不可能相互衝突,從而解決幻讀問題。簡言之,它是在每個讀取的資料行上加上共享鎖定。在這個級別,可能導致大量的超時現象和鎖定競爭。
隔離等級越高,越能保證資料的完整性和一致性,但對並發效能的影響也越大。
對於多數應用程序,可以有效考慮把資料庫系統的隔離等級設為Read Committed,它能夠避免髒讀,而且具有較好的並發性能。儘管它會導致不可重複讀、虛度和第二類丟失更新這些並發問題,在可能出現這類問題的個別場合,可以由應用程式採用悲觀鎖和樂觀鎖來控制。
事務的傳播性
PROPAGATION_REQUIRED
加入當前正要執行的事務,如果當前事務不存在,那麼就起一個新的事務。 Spring 操作資料庫預設的交易傳播行為就是 propagation_required 。
PROPAGATION_SUPPORTS
如果目前在事務中,即以事務的形式運行,如果當前不再一個事務中,那麼就以非事務的形式運行.
PROPAGATION_MANDATORY_MANDATORY也就是說,他只能被一個父事務呼叫。否則,他就要拋出異常。
掛起目前事務,另一個新的交易。
不能在事務中運行,如果在事務中運行就會拋出異常。
嵌套的事務依賴父事務,父事務提交,它跟著提交,父事務回滾,它跟著回滾。
頁級:引擎 BDB,一次鎖定相鄰的一組記錄。
表級:引擎 MyISAM , 理解為鎖住整個表,可以同時讀,寫不行。
三種鎖的特性大致可歸納如下:
1) 表級鎖定:開銷小,加鎖快;不會出現死鎖;鎖定粒徑大,發生鎖衝突的機率最高,且發度最低。
2) 行級鎖定:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖定衝突的機率最低,並發度也最高。
3) 頁面鎖定:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,並發度一般。
ENGINE = InnoDB AUTO_INCREMENT=10 DEFAULT CHARACTER SET = utf8 comment='用户表
InnoDB中两种模式的行级锁:
1)共享锁:允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
( Select * from table_name where ……lock in share mode)
2)排他锁:允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和 排他写锁。(select * from table_name where…..for update)
为了允许行锁和表锁共存,实现多粒度锁机制;同时还有两种内部使用的意向锁(都是表锁),分别为意向共享锁和意向排他锁。
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
注意:InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。应用设计的时候要注意这一点。
行级锁的优缺点
行级锁定的优点:
当在许多线程中访问不同的行时只存在少量锁定冲突。
回滚时只有少量的更改。
可以长时间锁定单一的行。
行级锁定的缺点:
比页级或表级锁定占用更多的内存。
当在表的大部分数据上使用时,比页级或表级锁定速度慢,因为你必须获取更多的锁。如果你在大部分数据上经常进行GROUP BY操作或
者必须经常扫描整个表,比其它锁定明显慢很多。
hibernate中通过行级锁实现的悲观锁。
一些例子:
假设有个表单products ,里面有id跟name二个栏位,id是主键。
1: 明确指定主键,并且有此条记录,执行row lock。若查无此记录,无lock。
SELECT * FROM products WHERE id='3' FOR UPDATE;SELECT * FROM products WHERE id='3' and name="cat" FOR UPDATE;
2: 无主键,执行table lock。
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;
3: 主键不明确,table lock。
SELECT * FROM products WHERE id<>'3' FOR UPDATE;
注意: FOR UPDATE仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。此外,如果A与B都对表id进行查询但查询不到记录,则A与B在查询上不会进行row锁,但A与B都会获取排它锁,此时A再插入一条记录的话则会因为B已经有锁而处于等待中,此时B再插入一条同样的数据则会抛出Deadlock found when trying to get lock; try restarting transaction。然后释放锁,此时A就获得了锁而插入成功。
以上就是MySQL中的事务与锁的内容,更多相关内容请关注PHP中文网(www.php.cn)!