1. 程式人生 > >關於Mysql的事務和鎖 看這一篇文章就夠了

關於Mysql的事務和鎖 看這一篇文章就夠了

  1. 共享讀鎖(S鎖)和 排他寫鎖(X鎖)
  2. 行鎖與表鎖
innodb用的是行級鎖,相對於表鎖來說效能開銷會更大。雖然叫做行級鎖,但不表示他只鎖住修改的行記錄,即使找不到行記錄,他也會產生鎖。innodb 是根據掃描範圍來鎖定行記錄,如果有索引,那麼只會鎖定索引的覆蓋範圍,如果找不到索引,就會掃描全表,那麼行級鎖就會升級為表級鎖。做個測試:CREATE TABLE `user` (  `id` int(10) NOT NULL AUTO_INCREMENT,  `name` varchar(255) DEFAULT NULL,  `age` int(10) DEFAULT NULL,  PRIMARY KEY (`id`),  KEY `age` (`age`)) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;-- ------------------------------ Records of user-- ----------------------------INSERT INTO `user` VALUES (1, 'Jammes', 23);INSERT INTO `user` VALUES (2, 'Kobe', 34);INSERT INTO `user` VALUES (3, 'Smith', 45);INSERT INTO `user` VALUES (4, 'JR', 34);INSERT INTO `user` VALUES (5, 'Yi', 45);
Session 1Session 2
T1start transaction;start transaction;
T2select * from user;+----+--------+------+| id | name   | age  |+----+--------+------+|  1 | Jammes |   23 ||  2 | Kobe   |   34 ||  3 | Smith  |   45 ||  4 | JR     |   34 ||  5 | Yi     |   45 |+----+--------+------+
T3update user set name="Polo" where name="Kobe" and age=45;
Query OK, 0 rows affected (0.00 sec)Rows matched: 0  Changed: 0  Warnings: 0
T4update user set name = "Smith" where id=5;^C^C -- query abortedERROR 1317 (70100): Query execution was interrupted
在T3,Session1 雖然查詢不到任何符合條件的行記錄, 但innodb會根據 ‘age’ 索引給 age = 45  的行上加上寫鎖, 而其他行不會加鎖。如果把 ‘age’ 索引去掉, 那麼innodb 就會鎖全表。所以建立合適的索引不僅對查詢效能有很大的提升, 對於並行寫入也能有很大的幫助。
  1. 死鎖
死鎖是指兩個事務互相等待對方鎖的釋放,惡性迴圈下去就會產生死鎖。但是mysql預設會開啟死鎖檢查,發現事務會產生死鎖,就會主動結束掉事務。如何檢查執行伺服器的死鎖狀態:Show engine innodb status;
  • 事務的特性ACID
  1. 原子性(atomicity)
  2. 一致性(consistency)
  3. 隔離型(isolation)
  4. 永續性(durability)
  • MVCC, 多版本併發控制, 是指為了提高mysql的併發讀寫能力,事務查詢時只是讀取行記錄某個時間點的快照,所以並不會去對資料行加讀鎖,也不用等待資料行鎖的釋放,自然對併發讀寫效能會有很大提升。
  • 隔離級別
  1. 讀未提交(Read Uncommitted)
這是隔離性最低的級別, 當前事務可以讀取到其他事務未提交的更改。在此隔離級別下事務讀取時也不會對記錄行加讀鎖。開啟兩個會話,隔離級別都設定為讀未提交( set session transaction isolation level read uncommitted;
Session 1Session 2
T1start transaction;start transaction;
T2select * from user where id = 4;+----+-------+------+| id | name  | age  |+----+-------+------+|  4 | Smith |   34 |+----+-------+------+
T3update user set name = 'JR' where id = 4;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0
T4select * from user where id = 4;+----+------+------+| id | name | age  |+----+------+------+|  4 | JR   |   34 |+----+------+------+1 row in set (0.00 sec)
T5Rollback;
T6select * from user where id = 4;+----+-------+------+| id | name  | age  |+----+-------+------+|  4 | Smith |   34 |+----+-------+------+
  1. 讀提交(Read Committed)
讀提交顧名思義就是在當前事務中只能讀取到其他事務提交後到記錄,他其實也是利用多版本併發控制(MVCC)的方式去讀取行記錄,但他會讀取行記錄的最新版本,所以在讀提交事務隔離級別下, 事務不會對資料行加讀鎖。開啟兩個會話,隔離級別都設定為讀提交( set session transaction isolation level read committed;
Session 1Session 2
T1start transaction;start transaction;
T2select * from user where id = 4;+----+------+------+| id | name | age  |+----+------+------+|  4 | JR   |   34 |+----+------+------+
T3update user set name = 'Smith' where id = 4;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0
T4select * from user where id = 4;+----+------+------+| id | name | age  |+----+------+------+|  4 | JR   |   34 |+----+------+------+1 row in set (0.00 sec)
T5commit;
T6select * from user where id = 4;+----+-------+------+| id | name  | age  |+----+-------+------+|  4 | Smith |   34 |+----+-------+------+
  1. 可重複讀(Repeatable Read)
mysql預設的隔離級別就是可重複讀,他是利用MCCC的方式去讀取記錄行, 但他讀取的行記錄版本永遠是事務開啟時的那個版本,這跟讀提交讀取的最新版本是有區別的。開啟兩個會話,隔離級別都設定為可重複讀( set session transaction isolation level repeatable read;
Session 1Session 2
T1start transaction;start transaction;
T2select * from user where id = 4;+----+------+------+| id | name | age  |+----+------+------+|  4 | JR   |   34 |+----+------+------+
T3update user set name = 'Smith' where id = 4;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0
T4select * from user where id = 4;+----+------+------+| id | name | age  |+----+------+------+|  4 | JR   |   34 |+----+------+------+1 row in set (0.00 sec)
T5commit;
T6select * from user where id = 4;+----+------+------+| id | name | age  |+----+------+------+|  4 | JR   |   34 |+----+------+------+
  1. 可序列化(Serializable)
可序列化是隔離級別最高的隔離級別, 他會鎖住資料行。開啟兩個會話,隔離級別都設定為可序列化( set session transaction isolation level serializable;
Session 1Session 2
T1start transaction;start transaction;
T2select * from user where id = 4;+----+------+------+| id | name | age  |+----+------+------+|  4 | JR   |   34 |+----+------+------+
T3update user set name = 'Smith' where id = 4;等待鎖釋放……..
在測試中遇到一個不能理解的情況:在autocommit 開啟時, Session 1 更新id=4的記錄行, 也就對記錄行加了X鎖,但是session 2在不顯示宣告start transaction 的情況下,竟然可以讀取,理論上應該要等待Session1的鎖釋放。但是如果顯示宣告start transaction就正常會等待鎖的狀態。
Session 1Session 2
T1start transaction;
T2update user set name = "Smith" where id=4;
Query OK, 1 row affected (0.00 sec)
T3select * from user;
+----+--------+———+
| id | name   | age  |
+----+--------+———+
|  1 | Jammes |   23 |
|  2 | Kobe   |   34 |
|  3 | Wade   |   45 |
|  4 | JR     |   34 |
+----+--------+------+
  • 一致性非鎖定讀
一致性非鎖定讀是指在MVCC方式下事務讀取的時候並不會鎖定資料行或者等待資料行鎖的釋放,他只會讀取資料行某個時間快照版本。 隔離級別讀提交和可重複讀下的讀取方式就是一致性非鎖定讀,但兩者還是又些區別的:讀提交級別下讀到的資料行版本是最新的,而可重複讀級別下讀到的事務開啟時的版本。
  • 關於髒讀和幻讀
讀未提交隔離級別下可以讀取到其他事務未提交的資料行記錄,如果其他事務回滾了, 那麼該事務讀取到的資料行記錄就是錯誤的,這就是髒讀;(測試下刪除)讀提交隔離級別讀取的最新版本的行記錄, 所以在一個事務的不同時間點讀取到的行記錄可能就會不一樣,這就是幻讀;可重複讀隔離級別讀取的是事務開啟時的行記錄版本,所以無論在事務的任何時間點讀取到的結果都是一致的, 這就不會產生髒讀和幻讀。
  • 隔離級別的應用場景
預設情況下mysql的隔離級別是可重複讀, 那麼是否有必要因為特定的場景去切換隔離級別呢?