Mysql的鎖和併發參數的介紹

Mysql的鎖和併發參數的介紹

一、Mysql併發參數調整

從實現上來說,MySQL Server 是多線程結構,包括後臺線程和客戶服務線程。多線程可以有效利用服務器資源,提高數據庫的併發性能。在Mysql中,控制併發連接和線程的主要參數包括 max_connections、back_log、thread_cache_size、table_open_cahce。

1. max_connections

採用max_connections 控制允許連接到MySQL數據庫的最大數量,默認值是 151。如果狀態變量 connection_errors_max_connections 不爲零,並且一直增長,則說明不斷有連接請求因數據庫連接數已達到允許最大值而失敗,這是可以考慮增大max_connections 的值。
Mysql 最大可支持的連接數,取決於很多因素,包括給定操作系統平臺的線程庫的質量、內存大小、每個連接的負荷、CPU的處理速度,期望的響應時間等。在Linux 平臺下,性能好的服務器,支持 500-1000 個連接不是難事,需要根據服務器性能進行評估設定。

我們可以查看一下:

show variables like 'max_connections';

在這裏插入圖片描述

2. back_log

back_log 參數控制MySQL監聽TCP端口時設置的積壓請求棧大小。如果MySql的連接數達到max_connections時,新來的請求將會被存在堆棧中,以等待某一連接釋放資源,該堆棧的數量即back_log,如果等待連接的數量超過back_log,將不被授予連接資源,將會報錯。5.6.6 版本之前默認值爲 50 , 之後的版本默認爲 50 + (max_connections / 5), 但最大不超過900。

show variables like 'back_log';

我這裏默認是80
在這裏插入圖片描述

3. table_open_cache

該參數用來控制所有SQL語句執行線程可打開表緩存的數量, 而在執行SQL語句時,每一個SQL執行線程至少要打開 1 個表緩存。該參數的值應該根據設置的最大連接數 max_connections 以及每個連接執行關聯查詢中涉及的表的最大數量來設定 :

​ max_connections x N ;

show variables like 'table_open_cache';

在這裏插入圖片描述

4. thread_cache_size

爲了加快連接數據庫的速度,MySQL 會緩存一定數量的客戶服務線程以備重用,通過參數 thread_cache_size 可控制 MySQL 緩存客戶服務線程的數量。

5. innodb_lock_wait_timeout

該參數是用來設置InnoDB 事務等待行鎖的時間,默認值是50ms , 可以根據需要進行動態設置。對於需要快速反饋的業務系統來說,可以將行鎖的等待時間調小,以避免事務長時間掛起; 對於後臺運行的批量處理程序來說, 可以將行鎖的等待時間調大, 以避免發生大的回滾操作。

二、Mysql的鎖機制

1. 鎖概述

鎖是計算機協調多個進程或線程併發訪問某一資源的機制(避免爭搶)。
在數據庫中,除傳統的計算資源(如 CPU、RAM、I/O 等)的爭用以外,數據也是一種供許多用戶共享的資源。如何保證數據併發訪問的一致性、有效性是所有數據庫必須解決的一個問題,鎖衝突也是影響數據庫併發訪問性能的一個重要因素。從這個角度來說,鎖對數據庫而言顯得尤其重要,也更加複雜。

2. 鎖分類

  • 從對數據操作的粒度分 :

1) 表鎖:操作時,會鎖定整個表。
2) 行鎖:操作時,會鎖定當前操作行。

  • 從對數據操作的類型分:

1) 讀鎖(共享鎖):針對同一份數據,多個讀操作可以同時進行而不會互相影響。
2) 寫鎖(排它鎖):當前操作沒有完成之前,它會阻斷其他寫鎖和讀鎖。

3. Mysql各大引擎支持的鎖機制

相對其他數據庫而言,MySQL的鎖機制比較簡單,其最顯著的特點是不同的存儲引擎支持不同的鎖機制。下表中羅列出了各存儲引擎對鎖的支持情況:

存儲引擎 表級鎖 行級鎖 頁面鎖
MyISAM 支持 不支持 不支持
InnoDB 支持 支持 不支持
MEMORY 支持 不支持 不支持
BDB 支持 不支持 支持

MySQL這3種鎖的特性可大致歸納如下 :

鎖類型 特點
表級鎖 偏向MyISAM 存儲引擎,開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。
行級鎖 偏向InnoDB 存儲引擎,開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。
頁面鎖 開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。

從上述特點可見,很難籠統地說哪種鎖更好,只能就具體應用的特點來說哪種鎖更合適!僅從鎖的角度來說:表級鎖更適合於以查詢爲主,只有少量按索引條件更新數據的應用,如Web 應用;而行級鎖則更適合於有大量按索引條件併發更新少量不同數據,同時又有並查詢的應用,如一些在線事務處理(OLTP)系統。

4. MyISAM 表鎖

(1) 如何加表鎖

MyISAM 在執行查詢語句(SELECT)前,會自動給涉及的所有表加讀鎖,在執行更新操作(UPDATE、DELETE、INSERT 等)前,會自動給涉及的表加寫鎖,這個過程並不需要用戶干預,因此,用戶一般不需要直接用 LOCK TABLE 命令給 MyISAM 表顯式加鎖。

顯式加表鎖語法:

# 加讀鎖
lock table table_name read;
# 加寫鎖
lock table table_name write;

(2)創建表結構

create database demo_03 default charset=utf8mb4;

use demo_03;

CREATE TABLE `tb_book` (
  `id` INT(11) auto_increment,
  `name` VARCHAR(50) DEFAULT NULL,
  `publish_time` DATE DEFAULT NULL,
  `status` CHAR(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=myisam DEFAULT CHARSET=utf8 ;

INSERT INTO tb_book (id, name, publish_time, status) VALUES(NULL,'java編程思想','2088-08-01','1');
INSERT INTO tb_book (id, name, publish_time, status) VALUES(NULL,'solr編程思想','2088-08-08','0');

CREATE TABLE `tb_user` (
  `id` INT(11) auto_increment,
  `name` VARCHAR(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=myisam DEFAULT CHARSET=utf8 ;

INSERT INTO tb_user (id, name) VALUES(NULL,'令狐沖');
INSERT INTO tb_user (id, name) VALUES(NULL,'田伯光');

(3)測試讀鎖

當我們對客戶端1添加讀鎖時:

 lock table tb_book read;

在這裏插入圖片描述
客戶端1和客戶端2都能正常讀取數據,這是第一種情況;

我們再測試在兩個客戶端中再去查詢另外一張沒有加鎖的表:
在這裏插入圖片描述
我們可以發現1號客戶端不能讀取當前會話中其他沒有加鎖的表,而2號客戶端則可以,因爲2號客戶端沒有加鎖操作;

然後再測試一下測試一下插入操作:
在這裏插入圖片描述
我們可以發現,在加了讀鎖的情況下,當前客戶端不能執行寫操作,而另外一個客戶端則是要一直等待,直到客戶端1的讀鎖釋放,才能進行插入操作;

(4)測試寫鎖

現在對客戶端1加上寫鎖:

lock table tb_book write;

在這裏插入圖片描述
這裏發現兩邊還都是可以讀取,這裏有點問題;然後我們在客戶端1在進行測試新增、修改操作:
在這裏插入圖片描述
在執行更新操作的時候,發現另外一邊就不能讀取了,所以這裏應該是,只有在執行更新或者插入操作之後另外一邊就不能讀取了,當釋放寫鎖之後就能正常訪問;

(5)結論

  1. 對MyISAM 表的讀操作,不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一表的寫請求;
  2. 對MyISAM 表的寫操作,則會阻塞其他用戶對同一表的讀和寫操作;

簡而言之,就是讀鎖會阻塞寫,但是不會阻塞讀。而寫鎖,則既會阻塞讀,又會阻塞寫。

(6)查看鎖的爭用情況

show open tables

Name_locked:表名稱是否被鎖定。名稱鎖定用於取消表或對錶進行重命名等操作。
In_user : 表當前被查詢使用的次數。如果該數爲零,則表是打開的,但是當前沒有被使用。

show status like 'Table_locks%';

在這裏插入圖片描述

Table_locks_immediate : 指的是能夠立即獲得表級鎖的次數,每立即獲取鎖,值加1。
Table_locks_waited : 指的是不能立即獲取表級鎖而需要等待的次數,每等待一次,該值加1,此值高說明存在着較爲嚴重的表級鎖爭用情況。

5. InnoDB 行鎖

(1)行鎖介紹

行鎖特點 :偏向InnoDB 存儲引擎,開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。
InnoDB 與 MyISAM 的最大不同有兩點:一是支持事務;二是 採用了行級鎖。

(2)Mysql基本事務概念

事務及其ACID屬性
事務是由一組SQL語句組成的邏輯處理單元。
事務具有以下4個特性,簡稱爲事務ACID屬性。

ACID屬性 含義
原子性(Atomicity) 事務是一個原子操作單元,其對數據的修改,要麼全部成功,要麼全部失敗。
一致性(Consistent) 在事務開始和完成時,數據都必須保持一致狀態。
隔離性(Isolation) 數據庫系統提供一定的隔離機制,保證事務在不受外部併發操作影響的 “獨立” 環境下運行。
持久性(Durable) 事務完成之後,對於數據的修改是永久的。

併發事務處理帶來的問題

問題 含義
丟失更新(Lost Update) 當兩個或多個事務選擇同一行,最初的事務修改的值,會被後面的事務修改的值覆蓋。
髒讀(Dirty Reads) 當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然後使用了這個數據。
不可重複讀(Non-Repeatable Reads) 一個事務在讀取某些數據後的某個時間,再次讀取以前讀過的數據,卻發現和以前讀出的數據不一致。
幻讀(Phantom Reads) 一個事務按照相同的查詢條件重新讀取以前查詢過的數據,卻發現其他事務插入了滿足其查詢條件的新數據。

事務隔離級別

爲了解決上述提到的事務併發問題,數據庫提供一定的事務隔離機制來解決這個問題。數據庫的事務隔離越嚴格,併發副作用越小,但付出的代價也就越大,因爲事務隔離實質上就是使用事務在一定程度上“串行化” 進行,這顯然與“併發” 是矛盾的。

數據庫的隔離級別有4個,由低到高依次爲Read uncommitted、Read committed、Repeatable read、Serializable,這四個級別可以逐個解決髒寫、髒讀、不可重複讀、幻讀這幾類問題。

隔離級別 丟失更新 髒讀 不可重複讀 幻讀
Read uncommitted ×
Read committed × ×
Repeatable read(默認) × × ×
Serializable × × × ×

備註 : √ 代表可能出現 , × 代表不會出現 。

Mysql 的數據庫的默認隔離級別爲 Repeatable read , 查看方式:

show variables like 'tx_isolation';

在這裏插入圖片描述

(3)InnoDB 的行鎖的模式

InnoDB 實現了以下兩種類型的行鎖。

  • 共享鎖(S):又稱爲讀鎖,簡稱S鎖,共享鎖就是多個事務對於同一數據可以共享一把鎖,都能訪問到數據,但是隻能讀不能修改。
  • 排他鎖(X):又稱爲寫鎖,簡稱X鎖,排他鎖就是不能與其他鎖並存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的其他鎖,包括共享鎖和排他鎖,但是獲取排他鎖的事務是可以對數據就行讀取和修改。

對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及數據集加排他鎖(X);
對於普通SELECT語句,InnoDB不會加任何鎖;

可以通過以下語句顯示給記錄集加共享鎖或排他鎖 。

# 共享鎖(S):
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
# 排他鎖(X) :
SELECT * FROM table_name WHERE ... FOR UPDATE

(4)創建對應的測試表

create table test_innodb_lock(
	id int(11),
	name varchar(16),
	sex varchar(1)
)engine = innodb default charset=utf8;

insert into test_innodb_lock values(1,'100','1');
insert into test_innodb_lock values(3,'3','1');
insert into test_innodb_lock values(4,'400','0');
insert into test_innodb_lock values(5,'500','1');
insert into test_innodb_lock values(6,'600','0');
insert into test_innodb_lock values(7,'700','0');
insert into test_innodb_lock values(8,'800','1');
insert into test_innodb_lock values(9,'900','1');
insert into test_innodb_lock values(1,'200','0');

create index idx_test_innodb_lock_id on test_innodb_lock(id);
create index idx_test_innodb_lock_name on test_innodb_lock(name);

(5)測試行鎖

先將mysql的自動提交事務關掉:

set autocommit = 0;

在這裏插入圖片描述
看下這組情況,首先是c1執行更新操作,執行完之後如果不提交,c2的更改操作一直在等待,當c1那邊提交了事務,c2的更新操作成功;

在這裏插入圖片描述
這組情況發現不是同行的操作是不會互相影響的;

(6)無索引行鎖升級爲表鎖

如果不通過索引條件檢索數據,那麼InnoDB將對錶中的所有記錄加鎖,實際效果跟表鎖一樣。
查看當前表的索引 : show index from test_innodb_lock ;

操作不同行鎖時不會互相影響是因爲索引沒有失效:name字段時設置可索引,而且是字符串類型,如果我們在進行以name = 400這種沒有+ ‘’ 單引號時,mysql因爲會去強制轉換導致索引失效。此時行鎖會自動升級成爲表鎖;其他的客戶端就不能執行更新操作;
在這裏插入圖片描述
我們可以發現現在雖然不是操作一行,但是還是需要等待;

(7)間隙鎖危害

當我們用範圍條件,而不是使用相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據進行加鎖; 對於鍵值在條件範圍內但並不存在的記錄,叫做 “間隙(GAP)” , InnoDB也會對這個 “間隙” 加鎖,這種鎖機制就是所謂的 間隙鎖(Next-Key鎖) 。

(8) InnoDB 行鎖爭用情況

show  status like 'innodb_row_lock%';
Innodb_row_lock_current_waits: 當前正在等待鎖定的數量
​Innodb_row_lock_time: 從系統啓動到現在鎖定總時間長度​
Innodb_row_lock_time_avg:每次等待所花平均時長
​Innodb_row_lock_time_max:從系統啓動到現在等待最長的一次所花的
時間​Innodb_row_lock_waits: 系統啓動後到現在總共等待的次數​​當等待的次數很高,而且每次等待的時長也不小的時候,
我們就需要分析系統中爲什麼會有如此多的等待,然後根據分析結果着手製定優化計劃。​

(9)總結

InnoDB存儲引擎由於實現了行級鎖定,雖然在鎖定機制的實現方面帶來了性能損耗可能比表鎖會更高一些,但是在整體併發處理能力方面要遠遠由於MyISAM的表鎖的。當系統併發量較高的時候,InnoDB的整體性能和MyISAM相比就會有比較明顯的優勢。
但是,InnoDB的行級鎖同樣也有其脆弱的一面,當我們使用不當的時候,可能會讓InnoDB的整體性能表現不僅不能比MyISAM高,甚至可能會更差。

(10)優化建議

  • 儘可能讓所有數據檢索都能通過索引來完成,避免無索引行鎖升級爲表鎖。
  • 合理設計索引,儘量縮小鎖的範圍
  • 儘可能減少索引條件,及索引範圍,避免間隙鎖
  • 儘量控制事務大小,減少鎖定資源量和時間長度
  • 儘可使用低級別事務隔離(但是需要業務層面滿足需求)

三、總結

這是一些簡單的鎖使用和一些併發參數的配置,謝謝大家閱讀!!相互學習!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章