mysql innodb 全表锁,Mysql InnoDB行锁及表锁分享
一. 背景知識
二. 步入正題:表鎖和行鎖
1.1. 表鎖 vs 行鎖
在 MySQL 中鎖的種類有很多,但是最基本的還是表鎖和行鎖:表鎖指的是對一整張表加鎖,一般是 DDL 處理時使用,也可以自己在 SQL 中指定;而行鎖指的是鎖定某一行數據或某幾行,或行和行之間的間隙。行鎖的加鎖方法比較復雜,但是由于只鎖住有限的數據,對于其它數據不加限制,所以并發能力強,通常都是用行鎖來處理并發事務。表鎖由 MySQL 服務器實現,行鎖由存儲引擎實現,常見的就是InnoDb,所以通常我們在討論行鎖時,隱含的一層意義就是數據庫的存儲引擎為 InnoDb ,而 MyISAM 存儲引擎只能使用表鎖。
1.2. 表鎖
表鎖由 MySQL 服務器實現,所以無論你的存儲引擎是什么,都可以使用。一般在執行 DDL 語句時,譬? ??? ??? ??? ?如?ALTER TABLE?就會對整個表進行加鎖。在執行 SQL 語句時,也可以明確對某個表加鎖。
1.2.1 操作演示(user_test創建表腳本見2.4.1)
-- 事物A中 顯示對 user_test 加上讀鎖locktableuser_test read;select*fromuser_testwhereid=3;? -- 此時事物B中對其進行update或者write操作start TRANSACTION;-- 手動開啟事物insertintouser_test(age,name)values(18,'Tom');
結論:此時事物B一致處于等待事物A釋放鎖的狀態,最終會回去鎖超時
2.1. 行鎖(以下驗證是在默認的隔離級別(可重復讀的事務隔離級)下操作)
InnoDB? NEXT-KEY Locks,解決了在可重復讀的事務隔離級別下出現幻讀的問題。
什么是幻讀?
幻讀是在可重復讀的事務隔離級別下會出現的一種問題,簡單來說,可重復讀保證了當前事務不會讀取到其他事? ? ? ?務已提交的 UPDATE 操作。但同時,也會導致當前事務無法感知到來自其他事務中的 INSERT 或 DELETE 操? ???? 作,這就是幻讀。
2.2. 關于行鎖我們要知道的
行鎖在 InnoDB 中是基于索引實現的,所以一旦某個加鎖操作沒有使用索引,那么該鎖就會退化為表鎖。
2.3 行鎖分類
2.3.1 從加鎖范圍劃分
a)記錄鎖(Record Locks):存在與唯一索引包括主鍵索引 顧名思義,記錄鎖就是為某行記錄加鎖,它封鎖該行的索引記錄:
b)間隙鎖(Gap Locks):
存在與非唯一索引中,鎖定開區間范圍內的一段間隔,它是基于臨鍵鎖實現的。
間隙鎖基于非唯一索引,它鎖定一段范圍內的索引記錄。間隙鎖基于下面將會提到的Next-Key Locking 算法,請務必牢記:使用間隙鎖鎖住的是一個區間,而不僅僅是這個區間中的每一條數據。
是一種加在兩個索引之間的鎖,或者加在第一個索引之前,或最后一個索引之后的間隙。有時候又稱為范圍(Range Locks),這個范圍可以跨一個索引記錄,多個索引記錄,甚至是空的。使用間隙鎖可以防止其他事務在這個范圍內插入或修改記錄,保證兩次讀取這個范圍內的記錄不會變,從而不會出現幻讀現象。很顯然,間隙鎖會增加數據庫的開銷,雖然解決了幻讀問題,但是數據庫的并發性一樣受到了影響,所以在選擇數據庫的隔離級別時,要注意權衡性能和并發性,根據實際情況考慮是否需要使用間隙鎖,大多數情況下使用 read committed 隔離級別就足夠了,對很多應用程序來說,幻讀也不是什么大問題。
產生間隙鎖的條件(RR事務隔離級別下):
3.1.? 使普通索引鎖定;
3.2.? 使用多列唯一索引;
3.3.? 使用唯一索引鎖定多行記錄。
c)臨鍵鎖(Next-Key Locks)臨鍵鎖存在于非唯一索引中(主鍵中不存在臨鍵鎖),該類型的每條記錄的索引上都存在這種鎖,它是一種特殊的間隙鎖,鎖定一段左開右閉的索引區間。臨鍵鎖,是記錄鎖與間隙鎖的組合,它的封鎖范圍,既包含索引記錄,又包含索引區間。
注意: Next-Key 可以理解為一種特殊的間隙鎖,也可以理解為一種特殊的算法。通過臨建鎖可以解決幻讀的問題。 每個數據行上的非唯一索引列上都會存在一把臨鍵鎖,當某個事務持有該數據行的臨鍵鎖時,會鎖住一段左開右閉區間的數據。需要強調的一點是,InnoDB 中行級鎖是基于索引實現的,臨鍵鎖只與非唯一索引列有關,在唯一索引列(包括主鍵列)上不存在臨鍵鎖 , 但是存在間隙鎖。臨鍵鎖的主要目的,也是為了避免幻讀(Phantom Read)。如果把事務的隔離級別降級為RC,臨鍵鎖則也會失效。
2.3.2? 從兼容性和存在形態劃分
2.4. 操作演示
2.4.1 創建測試表并初始化數據
CREATETABLE`user_test`(`id`bigint(20)unsignedNOTNULLAUTO_INCREMENT COMMENT'自增id',`age`int(11)unsignedNOTNULLCOMMENT'年齡',`name`varchar(16)NOTNULLCOMMENT'姓名',PRIMARY KEY(`id`)COMMENT'主鍵')ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='行鎖測試表';INSERTINTO`user_test`(`id`,`age`,`name`)VALUES(1,10,'Lee');INSERTINTO`user_test`(`id`,`age`,`name`)VALUES(2,24,'Ted');INSERTINTO`user_test`(`id`,`age`,`name`)VALUES(3,32,'Zed');INSERTINTO`user_test`(`id`,`age`,`name`)VALUES(4,45,'Talon');
2.4.2Record lock 記錄鎖驗證
執行一下sql
-- 事物A 中更新數據 sql如下:-- 手動開啟事物或者 beginSTART TRANSACTION;-- 根據非唯一索引列 UPDATE 某條記錄? (只會產生記錄鎖,不會產生間隙鎖)UPDATEuser_testSETname='LISHI'WHEREid=2;-- 等觀看完下面事物B在開啟事物commit;rollback;-- 事物B 也同時更新統一條記錄START TRANSACTION;UPDATEuser_testSETname='ZHANGSAN'WHEREid=2;commit;rollback;
此時事物B會出現如圖結果
事物A commit 或者 rollback 此時事物B會拿到鎖執行成功;事物B更新其他id記錄不受影響:
START TRANSACTION;UPDATEuser_testSETname='ZHANGSAN'WHEREid=1;commit;
2.4.2Next-Key Locks 臨鍵鎖驗證
首先在age字段上創建普通索引
ALTERTABLE`user_test` ADD INDEX `index_age`(`age`);
此時該表中 age 列潛在的臨鍵鎖有:
(-∞, 10],
(10, 24],
(24, 32],
(32, 45],
(45, +∞],
事物A 中更新數據 sql如下:
START TRANSACTION;-- 根據非唯一索引列 UPDATE 某條記錄select*fromuser_testwhereage=10forupdate-- 事物B此時插入或者更新age小于10大于1的一條記錄-- 插入一條記錄為age=7的記錄start TRANSACTION;insertintouser_test(age,name)values(7,'Tom');COMMIT;
-- 事物B更新其中一條記錄age為8也會被阻塞(雖然條件id是主鍵索引,更新的字段是普通索引,因此也會加上間隙鎖)start TRANSACTION;UPDATEuser_testSETage=8WHEREid=2;COMMIT;
此時事物B會出現如圖結果
事物A commit 或者 rollback 此時事物B會拿到鎖執行成功
2.4.3. Gap Locks 間隙鎖驗證
打開間隙鎖設置首先查看 innodb_locks_unsafe_for_binlog 是否禁用:
show variableslike'innodb_locks_unsafe_for_binlog';
innodb_locks_unsafe_for_binlog:默認值為OFF,即啟用間隙鎖。因為此參數是只讀模式,如果想要禁用間隙鎖,需要修改my.cnf(windows是my.ini)重新啟動才行。
默認mac是沒有my.cnf文件的,因此要在 etc文件下創建 my.cnf文件(etc/my.cnf)
my.cnf內容如下(主要添加:)
innodb_locks_unsafe_for_binlog=1
3.my.cnf內容如下:
# Example MySQL config file formediumsystems.? #? # Thisisfor a system with little memory(32M-64M)whereMySQL plays# an important part,orsystems up to128MwhereMySQLisused together with# other programs(suchasa web server)? #? # MySQL programs look for option filesinasetof# locations which dependonthe deployment platform.? # You can copy this option file to one of those? # locations. For information about these locations,see:# http://dev.mysql.com/doc/mysql/en/option-files.html? #? #Inthis file,you can use alllongoptions that a program supports.# If you want to know which options a program supports,run the program# with the"--help"option.? # The following options will be passed to all MySQL clients? [client]default-character-set=utf8#password=your_passwordport=3306socket=/tmp/mysql.sock? # Here follows entries for some specific programs? ? # The MySQL server? [mysqld]character-set-server=utf8init_connect='SET NAMES utf8? port? ? ? ? = 3306? ? socket? ? ? = /tmp/mysql.sock? ? skip-external-locking? ? key_buffer_size = 16M? ? max_allowed_packet = 1M? ? table_open_cache = 64? ? sort_buffer_size = 512K? ? net_buffer_length = 8K? ? read_buffer_size = 256K? ? read_rnd_buffer_size = 512K? ? myisam_sort_buffer_size = 8M? ? character-set-server=utf8? ? init_connect='SETNAMES utf8'? innodb_locks_unsafe_for_binlog = 1# Don't listenona TCP/IP port at all. This can be a security enhancement,# if all processes that need to connect to mysqld runonthe same host.# All interaction with mysqld must be made via Unix socketsornamed pipes.# Note that using this option without enabling named pipesonWindows#(via the"enable-named-pipe"option)will render mysqld useless!#? #skip-networking # Replication Master Server(default)# binary loggingisrequired for replicationlog-bin=mysql-bin # binary logging format-mixed recommendedbinlog_format=mixed # required unique idbetween1and2^32-1# defaults to1if master-hostisnotset# but willnotfunctionasa master if omittedserver-id=1 # Replication Slave(comment out master section to use this)? ? #? # To configure this hostasa replication slave,you can choosebetween# two methods:? ? #? #1)Use the CHANGE MASTER TO command(fully describedinour manual)-#? ? the syntaxis:? ? #? #? ? CHANGE MASTER TO MASTER_HOST=,MASTER_PORT=,#? ? MASTER_USER=,MASTER_PASSWORD=;? ? #? #whereyou replace,,byquoted stringsand#bythe master's port number (3306 by default).? ? ? #? ? ? #? ? Example:? ? ? #? ? ? #? ? CHANGE MASTER TO MASTER_HOST='125.564.12.1', MASTER_PORT=3306,? ? ? #? ? MASTER_USER='joe', MASTER_PASSWORD='secret';? ? ? #? ? ? # OR? ? ? #? ? ? # 2) Set the variables below. However, in case you choose this method, then? ? ? #? ? start replication for the first time (even unsuccessfully, for example? ? ? #? ? if you mistyped the password in master-password and the slave fails to? ? ? #? ? connect), the slave will create a master.info file, and any later? ? ? #? ? change in this file to the variables'valuesbelow will be ignoredand#? ? overriddenbythe content of the master.infofile,unless you shutdown#? ? the slave server,deletemaster.infoandrestart the slaver server.#? ? For that reason,you may want to leave the lines below untouched#(commented)andinstead use CHANGE MASTER TO(see above)? ? #? # required unique idbetween2and2^32-1#(anddifferentfromthe master)# defaults to2if master-hostisset# but willnotfunctionasa slave if omitted#server-id=2? ? #? # The replication master for this slave-required#master-host=? ? #? ? ? # The username the slave will use for authentication when connecting? # to the master-required#master-user=? ? #? ? ? # The password the slave will authenticate with when connecting to? # the master-required#master-password=? ? #? mysqld# The port the masterislisteningon.# optional-defaults to3306#master-port=? ? #? # binary logging-notrequired for slaves,but recommended#log-bin=mysql-bin? ? ? # Uncomment the following if you are using InnoDB tables? #innodb_data_home_dir=/usr/local/mysql/data#innodb_data_file_path=ibdata1:10M:autoextend#innodb_log_group_home_dir=/usr/local/mysql/data# You canset.._buffer_pool_size up to50-80%? ? ? # of RAM but beware of setting memory usage too high? #innodb_buffer_pool_size=16M#innodb_additional_mem_pool_size=2M#Set.._log_file_size to25%of buffer pool size#innodb_log_file_size=5M#innodb_log_buffer_size=8M#innodb_flush_log_at_trx_commit=1#innodb_lock_wait_timeout=50 [mysqldump]? ? ? ? quick? max_allowed_packet=16M [mysql]no-auto-rehash# Remove the next comment character if you arenotfamiliar with SQL#safe-updatesdefault-character-set=utf8 [myisamchk]key_buffer_size=20Msort_buffer_size=20Mread_buffer=2Mwrite_buffer=2M [mysqlhotcopy]interactive-timeout
此時再次查看間隙鎖是否開啟
唯一索引的間隙鎖
-- 創建test表并插入一些數據CREATETABLE`test`(`id`int(1)NOTNULLAUTO_INCREMENT,`name`varchar(8)DEFAULTNULL,PRIMARY KEY(`id`))ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERTINTO`test`VALUES('1','小羅');INSERTINTO`test`VALUES('5','小黃');INSERTINTO`test`VALUES('7','小明');INSERTINTO`test`VALUES('11','小紅');
在進行測試之前,我們先來看看test表中存在的隱藏間隙:
(-∞, 1]
(1, 5]
(5, 7]
(7, 11]
(11, +∞]
-- 執行事物1如下START TRANSACTION;-- 查詢 id 在 5 - 11 范圍的數據并加記錄鎖 SELECT*FROM`test`WHERE`id`BETWEEN5AND11FORUPDATE;-- 事務B進行插入數據操作 --? 注意:以下的語句不是放在一個事務中執行,而是分開多次執行,每次事務中只有一條添加語句START TRANSACTION;-- 事務2插入一條 id = 3,name = '小張1' 的數據 INSERTINTO`test`(`id`,`name`)VALUES(3,'小張1');# 正常執行-- 事務3插入一條 id = 4,name = '小白' 的數據INSERTINTO`test`(`id`,`name`)VALUES(4,'小白');# 正常執行-- 事務4插入一條 id = 6,name = '小東' 的數據INSERTINTO`test`(`id`,`name`)VALUES(6,'小東');# 阻塞-- 事務5插入一條 id = 8, name = '大羅' 的數據INSERTINTO`test`(`id`,`name`)VALUES(8,'大羅');# 阻塞-- 事務6插入一條 id = 9, name = '大東' 的數據INSERTINTO`test`(`id`,`name`)VALUES(9,'大東');# 阻塞-- 事務7插入一條 id = 11, name = '李西' 的數據INSERTINTO`test`(`id`,`name`)VALUES(11,'李西');# 阻塞-- 事務8插入一條 id = 12, name = '張三' 的數據INSERTINTO`test`(`id`,`name`)VALUES(12,'張三');# 正常執行-- 提交事務1,釋放事務1的鎖 COMMIT;
從上面我們可以看到,(5, 7]、(7, 11] 這兩個區間,都不可插入數據,其它區間,都可以正常插入數據。所以我們可以得出結論:當我們給 (5, 7] 這個區間加鎖的時候,會鎖住 (5, 7]、(7, 11] 這兩個區間。
我們再來測試如果我們鎖住不存在的數據時,會怎樣:
-- 開啟事務1 START TRANSACTION;--? 查詢 id = 3 這一條不存在的數據并加記錄鎖 SELECT*FROM`test`WHERE`id`=3FORUPDATE;-- 延遲30秒執行,防止鎖釋放 SELECTSLEEP(30);-- # 注意:以下的語句不是放在一個事務中執行,而是分開多次執行,每次事務中只有一條添加語句--? 事務2插入一條 id = 3,name = '小張1' 的數據 INSERTINTO`test`(`id`,`name`)VALUES(2,'小張1');# 阻塞--? 事務3插入一條 id = 4,name = '小白' 的數據 INSERTINTO`test`(`id`,`name`)VALUES(4,'小白');# 阻塞-- 事務4插入一條 id = 6,name = '小東' 的數據 INSERTINTO`test`(`id`,`name`)VALUES(6,'小東');# 正常執行-- 事務5插入一條 id = 8, name = '大羅' 的數據 INSERTINTO`test`(`id`,`name`)VALUES(8,'大羅');# 正常執行-- 提交事務1,釋放事務1的鎖 COMMIT;
我們可以看出,指定查詢某一條記錄時,如果這條記錄不存在,會產生間隙鎖。
結論
對于指定查詢某一條記錄的加鎖語句,如果該記錄不存在,會產生記錄鎖和間隙鎖,如果記錄存在,則只會產生記錄鎖,如:WHERE `id` = 5 FOR UPDATE;
對于查找某一范圍內的查詢語句,會產生間隙鎖,如:WHERE `id` BETWEEN 5 AND 7 FOR UPDATE;
總結:
上述文檔是自己及其團隊小伙伴們一起討論的結果,自己做了一下總結,很感謝團隊成員劉昌力、劉明遠、朱文彬、祁世松、桑萌萌、石偉男以及彭紹翔等他們的討論支持。
此文檔是對行鎖和表鎖的一個粗略的認識,存在一定的不足、知識點的缺失、不完善等問題。希望大家能夠一起再完善一下,共同學習進步,掌握知識技能,更好的正確高效的運用到平時的工作當中去,才是我們分享技術文檔的意思所在。
總結
以上是生活随笔為你收集整理的mysql innodb 全表锁,Mysql InnoDB行锁及表锁分享的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dlgli.exe是病毒程序吗 dlgl
- 下一篇: dlt.exe是一种什么样的进程 安全吗