锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的 计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一 个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。本章我们着重讨论MySQL锁机制 的特点,常见的锁问题,以及解决MySQL锁问题的一些方法或建议。
Mysql用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。这些锁统称为悲观锁(Pessimistic Lock)。
MySQL锁概述
相对其他数据库而言,MySQL的锁机制比较简单,其最 显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM
和MEMORY
存储引擎采用的是表级锁(table-level locking
);BDB
存储引擎采用的是页面锁(page-level locking
),但也支持表级锁;InnoDB
存储引擎既支持行级锁(row-level locking
),也支持表级锁,但默认情况下是采用行级锁。
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
从上述特点可见,很难笼统地说哪种锁更好,只能就具体应用的特点来说哪种锁更合适!仅从锁的角度 来说:表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有 并发查询的应用,如一些在线事务处理(OLTP)系统。
MyISAM表锁
MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。
对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对 MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;MyISAM表的读操作与写操作之间,以及写操作之间是串行的!根据如表20-2所示的 例子可以知道,当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
MyISAM存储引擎的写锁阻塞读例子:
当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
MyISAM存储引擎的读锁阻塞写例子:
一个session使用LOCK TABLE命令给表film_text加了读锁,这个session可以查询锁定表中的记录,但更新或访问其他表都会提示错误;同时,另外一个session可以查询表中的记录,但更新就会出现锁等待。
如何加表锁
MyISAM在執行查詢語句(SELECT)前,會自動為涉及的所有表加讀鎖,在執行更新操作(UPDATE、DELETE、INSERT等)前,會自動為涉及的表加寫鎖,這個過程並不需要使用者乾預,因此,使用者一般不需要直接用LOCK TABLE指令來為MyISAM表明確加鎖。在範例中,顯式加鎖基本上都是為了演示而已,並非必須如此。
給MyISAM表顯示加鎖,一般是為了在一定程度模擬事務操作,實現對某一時間點多個表的一致性讀取。例如, 有一個訂單表orders,記錄有各訂單的總金額total,同時還有一個訂單明細表order_detail,其中記錄有各訂單每一產品的金額小計subtotal,假設我們需要檢查這兩個表的金額合計是否相符,可能就需要執行以下兩個SQL:
Select sum(total) from orders; Select sum(subtotal) from order_detail;
這時,如果不先給兩個表加鎖,就可能產生錯誤的結果,因為第一條語句執行過程中, order_detail表可能已經發生了改變。因此,正確的方法應該是:
Lock tables orders read local, order_detail read local; Select sum(total) from orders; Select sum(subtotal) from order_detail; Unlock tables;
要特別說明以下兩點內容:
1、上面的例子在LOCK TABLES時加了「local」選項,其作用就是在滿足MyISAM表並發插入條件的情況下,允許其他使用者在表尾並發插入記錄,有關MyISAM表的並發插入問題,在後面還會進一步介紹。
2、在用LOCK TABLES為表明確加表鎖時,必須同時取得所有涉及到表的鎖,且MySQL不支援鎖定升級。也就是說,在執行LOCK TABLES後,只能存取明確加鎖的這些表,不能存取未加鎖的表;同時,如果加的是讀鎖,那麼只能執行查詢操作,而不能執行更新操作。其實,在自動加鎖的 情況下也基本如此,MyISAM總是一次獲得SQL語句所需的全部鎖。這也正是MyISAM表不會出現死鎖(Deadlock Free)的原因。
當使用LOCK TABLES時,不僅需要一次鎖定用到的所有表,而且,同一個表在SQL語句中出現多少次,就要透過與SQL語句中相同的別名鎖定多少次,否則也會出錯!舉例說明如下。
(1)對actor表獲得讀鎖:
mysql> lock table actor read; Query OK, 0 rows affected (0.00 sec)
(2)但是透過別名存取會提示錯誤:
mysql> select a.first_name,a.last_name,b.first_name,b.last_name from actor a,actor b where a.first_name = b.first_name and a.first_name = 'Lisa' and a.last_name = 'Tom' and a.last_name <> b.last_name; ERROR 1100 (HY000): Table ‘a’ was not locked with LOCK TABLES
(3)需要對別名分別鎖定:
mysql> lock table actor as a read,actor as b read; Query OK, 0 rows affected (0.00 sec)
(4)依照別名的查詢可以正確執行:
mysql> select a.first_name,a.last_name,b.first_name,b.last_name from actor a,actor b where a.first_name = b.first_name and a.first_name = 'Lisa' and a.last_name = 'Tom' and a.last_name <> b.last_name; +————+———–+————+———–+ | first_name | last_name | first_name | last_name | +————+———–+————+———–+ | Lisa | Tom | LISA | MONROE | +————+———–+————+———–+ 1 row in set (0.00 sec)
#查詢表級鎖定爭用情況
可以透過檢查table_locks_waited和table_locks_immediate狀態變數來分析系統上的表格鎖定爭奪:
mysql> show status like 'table%'; 1Variable_name | Value Table_locks_immediate | 2979 Table_locks_waited | 0 2 rows in set (0.00 sec))
如果Table_locks_waited的值比較高,則表示存在較嚴重的表級鎖定爭用情況。
並發插入(Concurrent Inserts)
當concurrent_insert設定為0時,不允許並發插入。當concurrent_insert設定為1時,如果MyISAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM允許在一個進程讀表的同時,另一個進程從表尾插入記錄。這也是MySQL的預設值。當concurrent_insert設定為2時,無論MyISAM表中有沒有空洞,都允許在表尾並發插入記錄。
在下面的例子中,session_1得到了一個表的READ LOCAL鎖,該線程可以對錶進行查詢操作,但不能對錶進行更新操作;其他的線程(session_2),雖然不能對錶進行刪除和更新操作,但卻可以對該表進行並發插入操作,這裡假設該表中間不存在空洞。
MyISAM儲存引擎的讀寫(INSERT)並發範例:
#可以利用MyISAM儲存引擎的並發插入特性,來解決應用程式中對相同表查詢和插入的鎖爭用。例如,將concurrent_insert系統變數設為2,總是允許並發插入;同時,透過定期在系統空閒時段執行 OPTIMIZE TABLE語句來整理空間碎片,收回因刪除記錄而產生的中間空洞。
MyISAM的鎖定調度
在前面講過,MyISAM儲存引擎的讀取鎖定和寫入鎖定是互斥的,讀取和寫入操作是串行的。那麼,一個進程請求某個 MyISAM表的讀鎖,同時另一個進程也請求同一表的寫鎖,MySQL該如何處理呢?答案是寫進程先獲得鎖。不僅如此,即使讀取請求先到鎖等待佇列,寫入請求後 到,寫鎖也會插到讀鎖請求之前!這是因為MySQL認為寫入請求一般比讀取請求重要。這也正是MyISAM表不太適合於有大量更新操作和查詢操作應用的原 因,因為,大量的更新操作會造成查詢操作很難獲得讀鎖,從而可能永遠阻塞。這種情況有時可能會變得非常糟糕!幸好我們可以透過一些設定來調節MyISAM 的調度行為。
1、透過指定啟動參數low-priority-updates,使MyISAM引擎預設給予讀取請求以優先的權利。
2、透過執行指令SET LOW_PRIORITY_UPDATES=1,使該連線所發出的更新請求優先權降低。
3、透過指定INSERT、UPDATE、DELETE語句的LOW_PRIORITY屬性,降低該語句的優先權。
雖然上面3種方法都是要更新優先,或是查詢優先的方法,但還是可以用其來解決查詢相對重要的應用程式(如使用者登入系統)中,讀鎖等待嚴重的問題。
另外,MySQL也提供了一種折中的辦法來調節讀寫衝突,即給系統參數max_write_lock_count設定一個合適的值,當一個表的讀鎖達到這個值後,MySQL就暫時將寫請求的優先權降低,給讀進程一定獲得鎖的機會。
上面已經討論了寫優先調度機制所帶來的問題和解決方法。這 裡還要強調一點:一些需要長時間運行的查詢操作,也會讓寫入進程「餓死」!因此,應用程式應盡量避免長時間運行的查詢操作,不要總是想用一條SELECT語句來解決問題,因為這種看似巧妙的SQL語句,往往比較複雜,執行時間較長,在可能的情況下可以透過使用中間表等措施對SQL語句做一定的“分解”,使每一步查詢都能在較短時間完成,從而減少鎖定衝突。如果複雜查詢不可避免,應盡量安排在資料庫空閒時段執行,例如一些定期統計可以安排在夜間執行。
後續會為大家講解InnoDB鎖定。
相了解更多相關問題請造訪PHP中文網:Mysql影片教學
#以上是關於mysql鎖機制原理的詳細講解(一)的詳細內容。更多資訊請關注PHP中文網其他相關文章!