[Reprint] 2019-03-26 published in-depth understanding of MySQL - lock, transaction and concurrency control

In-depth understanding of MySQL - lock, transaction and concurrency control

21
This article first appeared in Internet technology vivo micro-channel public number  https: //mp.weixin.qq.com/s/JF ...
Author: Zhang Shuo 
paper MySQL database about locks, transactions and concurrency control of knowledge and its principles do systematic introduction and summary, hope to help readers understand more deeply in MySQL locks and transactions, which can better optimize the interaction with the database in the business system development process.

Directory
1.MySQL server logic architecture
2.MySQL lock
3. transaction
4. isolation level
5. concurrency control and MVCC
6.MySQL Deadlock

1, MySQL server logic architecture

image description
Each connection will have a thread (internal thread by thread pool management), such as in a select statement into the MySQL server, MySQL will first look whether the cached results set this select in the query cache, if not proceed to resolve, optimization, the process of implementation; otherwise obtaining the result set will be from among the cache.

2, MySQL locks

2.1, Shared and Exclusive Locks (shared locks and exclusive locks)

They are standard row-level locking .

Shared lock (S) shared lock is also referred to as read locks, a read lock allows multiple concurrent connections may read the same time the same resources, without disturbing each other;

Exclusive lock (X) exclusive lock, also known as a write lock, a write lock block other write locks or read locks to ensure that only one connection can write data while preventing other users to read and write the data.

Note: The so-called shared locks and exclusive locks are actually locking mechanism of the policy itself, by these two strategies to lock made a distinction.

2.2, Intention Locks (intent locks)

InnoDB supports multiple granularity locking (lock granularity can be divided into rows and table locks), are allowed to coexist and table locks. For example, a statement such as LOCK TABLES ... WRITE accepted an exclusive lock on the specified table. In order to achieve multi-level granularity of locking, InnoDB uses intent locks.

Intent locks: table-level locking. To advance a declaration of intent, and the intent to acquire a table-level locking (IS shared intent lock or exclusive intent lock IX), if the acquisition is successful, it will be later or is (are allowed), the locking some rows of a table (S or X) a. (Except LOCK TABLES ... WRITE, will lock all the rows in the table, the other scene intent locks do not actually lock any rows)

for example:

SELECT ... LOCK IN SHARE MODE, to obtain an IS lock; An intention shared lock (IS)

SELECT ... FOR UPDATE ,要获取IX锁;An intention exclusive lock (IX) i

Intent lock agreement before the transaction can acquire a shared lock on a row in a table, it must first obtain a lock IS or stronger lock on the table. Before the transaction can acquire an exclusive lock on the rows in the table, it must first acquire an IX lock on the table.

Earlier said, realize intent lock background is multi-granularity locks coexist scene. Compatibility summarized as follows:
image description
intent locks intended only table, it is a weak lock compatible parallel (the relationship between IS, IX compatible parallel) between intent locks. X and ISIX mutually exclusive; S and IX are mutually exclusive. Can appreciate the intention lock is weaker than the XS locks, there is a sense of anticipation! First get weaker IXIS lock, if the acquisition is not necessary to spend a failure to obtain a stronger XS lock with the big overhead ... ...

2.3, Record Locks (index row locks)

record lock is locked in a row the index records. For example, SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE, if the index is used to c1. Other transactions to prevent any changes in the line c1 = 10's.

record lock always lock on the index line. Even if a table has not set any index, this time innoDB implicitly creates a clustered index (primary Key), and then lock on the clustered index.

When not indexed query field , such as update table set columnA = "A" where columnB = "B". If columnB field index (or is not a combination of the prefix index) does not exist, this statement will lock all the records in the table is locked. If the execution of the statement is capable of performing a columnB index field, then only the rows where the meet will lock (RecordLock).

View Sample lock appears:

(Use show engine innodb status command to view):

image description

2.4, Gap locks (gap lock)

Gap Locks: the locking gap between the index record ([2]), or the locking gap before a record index ([1]), or after a locking gap index record ([3]).

Example: In [1], [2], [3] section. Generally the role of screening in the scope of our inquiry>, <, between ...... 
image description

For example, SELECT userId FROM t1 WHERE userId BETWEEN 1 and 4 FOR UPDATE; prevent another transaction value of 3 into a column userId. Since the gap existing between all values ​​within the range are locked.

对于使用唯一索引来搜索唯一行的语句 select a from ,不产生间隙锁定。(不包含组合唯一索引,也就是说 gapLock 不作用于单列唯一索引)

例如,如果id列有唯一的索引,下面的语句只对id值为100的行使用索引记录锁,其他会话是否在前一个间隙中插入行并不重要:
image description

间隙可以跨越单个索引值、多个索引值(如上图2,3),甚至是空的。

间隙锁是性能和并发性之间权衡的一种折衷,用于某些特定的事务隔离级别,如RC级别(RC级别:REPEATABLE READ,我司为了减少死锁,关闭了gap锁,使用RR级别)。

在重叠的间隙中(或者说重叠的行记录)中允许gap共存比如同一个 gap 中,允许一个事务持有 gap X-Lock(gap 写锁排他锁),同时另一个事务在这个 gap 中持有(gap 写锁排他锁)

CREATE TABLE `new_table` (

  `id` int(11) NOT NULL AUTO_INCREMENT, `a` int(11) DEFAULT NULL, `b` varchar(45) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_new_table_a` (`a`), KEY `idx_new_table_b` (`b`) ) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8 INSERT INTO `new_table` VALUES (1,1,'1'),(2,3,'2'),(3,5,'3'),(4,8,'4'),(5,11,'5'),(6,2,'6'),(7,2,'7'),(8,2,'8'),(9,4,'9'),(10,4,'10'); ######## 事务一 ######## START TRANSACTION; SELECT * FROM new_table WHERE a between 5 and 8 FOR UPDATE; ##暂不commit ######## 事务二 ######## SELECT * FROM new_table WHERE a = 4 FOR UPDATE; ##顺利执行! 因为gap锁可以共存; ######## 事务三 ######## SELECT * FROM new_table WHERE b = 3 FOR UPDATE; ##获取锁超时,失败。因为事务一的gap锁定了 b=3的数据。 

2.5、next-key lock

**next-key lock 是 record lock 与 gap lock 的组合。
比如 存在一个查询匹配 b=3 的行(b上有个非唯一索引),那么所谓 NextLock 就是:在b=3 的行加了 RecordLock 并且使用 GapLock 锁定了 b=3 之前(“之前”:索引排序)的所有行记录。**

MySQL 查询时执行 行级锁策略,会对扫描过程中匹配的行进行加锁(X 或 S),也就是加Record Lock,同时会对这个记录之前的所有行加 GapLock 锁。 假设一个索引包含值10、11、13和20。该索引可能的NexKey Lock锁定以下区间:

(negative infinity, 10]
(10, 11]
(11, 13] (13, 20] (20, positive infinity) 

另外,值得一提的是 : innodb 中默认隔离级别(RR)下,next key Lock 自动开启。(很好理解,因为 gap 作用于RR,如果是 RC,gapLock 不会生效,那么 next key lock 自然也不会)

锁出现查看示例:(使用 show engine innodb status 命令查看):

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t` 

trx id 10080 lock_mode X

Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;; Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 8000000a; asc ;; 1: len 6; hex 00000000274f; asc 'O;; 2: len 7; hex b60000019d0110; asc ;; 

2.6、Insert Intention Locks(插入意向锁)

一个 insert intention lock 是一种发生在 insert 插入语句时的 gap lock 间隙锁,锁定插入行之前的所有行。

这个锁以这样一种方式表明插入的意图,如果插入到同一索引间隙中的多个事务没有插入到该间隙中的相同位置,则它们不需要等待对方。

假设存在值为4和7的索引记录。尝试分别插入值为5和6的独立事务,在获得所插入行上的独占锁之前,每个事务使用 insert intention lock 锁定4和7之间的间隙,但不会阻塞彼此,因为这些行不冲突。

示例:

mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB; mysql> INSERT INTO child (id) values (90),(102); ##事务一 mysql> START TRANSACTION; mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE; +-----+ | id | +-----+ | 102 | +-----+ 
##事务二

mysql> START TRANSACTION;

mysql> INSERT INTO child (id) VALUES (101);

##失败,已被锁定

mysql> SHOW ENGINE INNODB STATUS

RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child` trx id 8731 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 80000066; asc f;; 1: len 6; hex 000000002215; asc " ;; 2: len 7; hex 9000000172011c; asc r ;;...

2.7、 AUTO-INC Locks

AUTO-INC 锁是一种特殊的表级锁,产生于这样的场景:事务插入(inserting into )到具有 AUTO_INCREMENT 列的表中。

在最简单的情况下,如果一个事务正在向表中插入值,那么其他任何事务必须等待向该表中插入它们自己的值,以便由第一个事务插入的行接收连续的主键值。

2.8 Predicate Locks for Spatial Indexes 空间索引的谓词锁

3、事务

事务就是一组原子性的 sql,或者说一个独立的工作单元。 事务就是说,要么 MySQL 引擎会全部执行这一组sql语句,要么全部都不执行(比如其中一条语句失败的话)。

· 自动提交(AutoCommit,MySQL 默认)

show variables like "autocommit";

set autocommit=0; //0表示AutoCommit关闭 set autocommit=1; //1表示AutoCommit开启 

MySQL 默认采用 AutoCommit 模式,也就是每个 sql 都是一个事务,并不需要显示的执行事务。如果 autoCommit 关闭,那么每个 sql 都默认开启一个事务,只有显式的执行“commit”后这个事务才会被提交。

· 显示事务 (START TRANSACTION...COMMIT)

比如,tim 要给 bill 转账100块钱:

1.检查 tim 的账户余额是否大于100块;

2.tim 的账户减少100块;

3.bill 的账户增加100块;

这三个操作就是一个事务,必须打包执行,要么全部成功, 要么全部不执行,其中任何一个操作的失败都会导致所有三个操作“不执行”——回滚。

CREATE DATABASE IF NOT EXISTS employees; USE employees; CREATE TABLE `employees`.`account` ( `id` BIGINT (11) NOT NULL AUTO_INCREMENT, `p_name` VARCHAR (4), `p_money` DECIMAL (10, 2) NOT NULL DEFAULT 0, PRIMARY KEY (`id`) ) ; INSERT INTO `employees`.`account` (`id`, `p_name`, `p_money`) VALUES ('1', 'tim', '200'); INSERT INTO `employees`.`account` (`id`, `p_name`, `p_money`) VALUES ('2', 'bill', '200'); START TRANSACTION; SELECT p_money FROM account WHERE p_name="tim";-- step1 UPDATE account SET p_money=p_money-100 WHERE p_name="tim";-- step2 UPDATE account SET p_money=p_money+100 WHERE p_name="bill";-- step3 COMMIT;

一个良好的事务系统,必须满足ACID特点:

3.1、事务的ACID:

· A:atomiciy 原子性:一个事务必须保证其中的操作要么全部执行,要么全部回滚,不可能存在只执行了一部分这种情况出现。

· C:consistency 一致性:数据必须保证从一种一致性的状态转换为另一种一致性状态。比如上一个事务中执行了第二步时系统崩溃了,数据也不会出现 bill 的账户少了100块,但是 tim 的账户没变的情况。要么维持原装(全部回滚),要么 bill 少了100块同时 tim 多了100块,只有这两种一致性状态的。

· I:isolation 隔离性:在一个事务未执行完毕时,通常会保证其他 Session 无法看到这个事务的执行结果。

· D:durability 持久性:事务一旦 commit,则数据就会保存下来,即使提交完之后系统崩溃,数据也不会丢失。

4、隔离级别

image description

查看系统隔离级别:
select @@global.tx_isolation;

查看当前会话隔离级别
select @@tx_isolation;

设置当前会话隔离级别
SET session TRANSACTION ISOLATION LEVEL serializable;

设置全局系统隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

4.1、 READ UNCOMMITTED (未提交读,可脏读)

事务中的修改,即使没有提交,对其他会话也是可见的。可以读取未提交的数据——脏读。脏读会导致很多问题,一般不适用这个隔离级别。 实例:

-- ------------------------- read-uncommitted实例 ------------------------------

-- 设置全局系统隔离级别

SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -- Session A START TRANSACTION; SELECT * FROM USER; UPDATE USER SET NAME="READ UNCOMMITTED"; -- commit; -- Session B SELECT * FROM USER; //SessionB Console 可以看到Session A未提交的事物处理,在另一个Session 中也看到了,这就是所谓的脏读 id name 2 READ UNCOMMITTED 34 READ UNCOMMITTED

4.2、READ COMMITTED (提交读或不可重复读,幻读)

一般数据库都默认使用这个隔离级别(MySQL 不是), 这个隔离级别保证了一个事务如果没有完全成功(commit 执行完),事务中的操作对其他会话是不可见的。

-- ------------------------- read-cmmitted实例 ------------------------------

-- 设置全局系统隔离级别

SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED; -- Session A START TRANSACTION; SELECT * FROM USER; UPDATE USER SET NAME="READ COMMITTED"; -- COMMIT; -- Session B SELECT * FROM USER; //Console OUTPUT: id name 2 READ UNCOMMITTED 34 READ UNCOMMITTED --------------------------------------------------- -- 当 Session A执行了commit,Session B得到如下结果: id name 2 READ COMMITTED 34 READ COMMITTED

也就验证了read committed 级别在事物未完成 commit 操作之前修改的数据对其他 Session 不可见,执行了 commit 之后才会对其他 Session 可见。 我们可以看到 Session B 两次查询得到了不同的数据。

read committed 隔离级别解决了脏读的问题,但是会对其他 Session 产生两次不一致的读取结果(因为另一个 Session 执行了事务,一致性变化)。

4.3、 REPEATABLE READ (可重复读)

一个事务中多次执行统一读 SQL,返回结果一样。 这个隔离级别解决了脏读的问题,幻读问题。这里指的是 innodb 的 rr 级别,innodb 中使用 next-key 锁对"当前读"进行加锁,锁住行以及可能产生幻读的插入位置,阻止新的数据插入产生幻行。 下文中详细分析。具体请参考 MySQL 手册:

https://dev.mysql.com/doc/ref...

4.4、 SERIALIZABLE (可串行化)

最强的隔离级别,通过给事务中每次读取的行加锁,写加写锁,保证不产生幻读问题,但是会导致大量超时以及锁争用问题。

5、并发控制 与 MVCC

MVCC (multiple-version-concurrency-control)它是个行级锁的变种, 在普通读情况下避免了加锁操作,因此开销更低。虽然实现不同,但通常都是实现非阻塞读,对于写操作只锁定必要的行。

· 一致性读 (就是读取快照)select * from table ....

· 当前读(就是读取实际的持久化的数据)特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁。 select from table where ? lock in share mode; select from table where ? for update; insert; update ; delete;

注意:select ...... from where...... (没有额外加锁后缀)使用MVCC,保证了读快照(MySQL 称为 consistent read),所谓一致性读或者读快照就是读取当前事务开始之前的数据快照,在这个事务开始之后的更新不会被读到。详细情况下文 select 的详述。

对于加锁读 SELECT with FOR UPDATE (排他锁) or LOCK IN SHARE MODE (共享锁)、 update、delete语句,要考虑是否是唯一索引的等值查询。

INNODB 的 MVCC 通常是通过在每行数据后边保存两个隐藏的列来实现(其实是三列,第三列是用于事务回滚,此处略去),一个保存了行的创建版本号,另一个保存了行的更新版本号(上一次被更新数据的版本号) 这个版本号是每个事务的版本号,递增的。这样保证了 innodb 对读操作不需要加锁也能保证正确读取数据。

5.1、MVCC select无锁操作 与 维护版本号

下边在 MySQL 默认的 Repeatable Read 隔离级别下,具体看看 MVCC 操作:

· Select(快照读,所谓读快照就是读取当前事务之前的数据。):

a.InnoDB 只 select 查找版本号早于当前版本号的数据行,这样保证了读取的数据要么是在这个事务开始之前就已经 commit 了的(早于当前版本号),要么是在这个事务自身中执行创建操作的数据(等于当前版本号)。

b.查找行的更新版本号要么未定义,要么大于当前的版本号(为了保证事务可以读到老数据),这样保证了事务读取到在当前事务开始之后未被更新的数据。

注意: 这里的 select 不能有 for update、lock in share 语句。 总之要只返回满足以下条件的行数据,达到了快照读的效果:

(行创建版本号< =当前版本号 && (行更新版本号==null or 行更新版本号>当前版本号 ) )

· Insert InnoDB为这个事务中新插入的行,保存当前事务版本号的行作为行的行创建版本号。

· Delete InnoDB 为每一个删除的行保存当前事务版本号,作为行的删除标记。

· Update 将存在两条数据,保持当前版本号作为更新后的数据的新增版本号,同时保存当前版本号作为老数据行的更新版本号。

当前版本号—写—>新数据行创建版本号 && 当前版本号—写—>老数据更新版本号();

5.2、脏读 vs 幻读 vs 不可重复读

脏读:一事务未提交的中间状态的更新数据 被其他会话读取到。

当一个事务正在访问数据,并且对数据进行了修改, 而这种修改还没有 提交到数据库中(commit 未执行), 这时,另外会话也访问这个数据,因为这个数据是还没有提交, 那么另外一个会话读到的这个数据是脏数据,依据脏数据所做的操作也可能是不正确的。

不可重复读:简单来说就是在一个事务中读取的数据可能产生变化,ReadCommitted 也称为不可重复读。

在同一事务中,多次读取同一数据返回的结果有所不同。 换句话说就是,后续读取可以读到另一会话事务已提交的更新数据。 相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样, 也就是,后续读取不能读到另一会话事务已提交的更新数据。

幻读:会话T1事务中执行一次查询,然后会话T2新插入一行记录,这行记录恰好可以满足T1所使用的查询的条件。然后T1又使用相同 的查询再次对表进行检索,但是此时却看到了事务T2刚才插入的新行。这个新行就称为“幻像”,因为对T1来说这一行就像突然 出现的一样。innoDB 的 RR 级别无法做到完全避免幻读,下文详细分析。
image description

5.3、 如何保证 rr 级别绝对不产生幻读?

在使用的 select ...where 语句中加入 for update (排他锁) 或者 lock in share mode (共享锁)语句来实现。其实就是锁住了可能造成幻读的数据,阻止数据的写入操作。

其实是因为数据的写入操作(insert 、update)需要先获取写锁,由于可能产生幻读的部分,已经获取到了某种锁,所以要在另外一个会话中获取写锁的前提是当前会话中释放所有因加锁语句产生的锁。

5.4、 从另一个角度看锁:显式锁、隐式锁

隐式锁:我们上文说的锁都属于不需要额外语句加锁的隐式锁。

显示锁:

SELECT ... LOCK IN SHARE MODE(加共享锁);

SELECT ... FOR UPDATE(加排他锁);

详情上文已经说过。

5.5、查看锁情况

通过如下 sql 可以查看等待锁的情况

select * from information_schema.innodb_trx where trx_state="lock wait";
或
show engine innodb status; 

6、MySQL 死锁问题

死锁,就是产生了循环等待链条,我等待你的资源,你却等待我的资源,我们都相互等待,谁也不释放自己占有的资源,导致无线等待下去。 比如:

//Session A

START TRANSACTION;

UPDATE account SET p_money=p_money-100 WHERE p_name="tim"; UPDATE account SET p_money=p_money+100 WHERE p_name="bill"; COMMIT; //Thread B START TRANSACTION; UPDATE account SET p_money=p_money+100 WHERE p_name="bill"; UPDATE account SET p_money=p_money-100 WHERE p_name="tim"; COMMIT; 

当线程A执行到第一条语句UPDATE account SET p_money=p_money-100 WHERE p_name="tim";锁定了p_name="tim" 的行数据;并且试图获取p_name="bill" 的数据;

此时,恰好,线程B也执行到第一条语句:UPDATE account SET p_money=p_money+100 WHERE p_name="bill";锁定了 p_name="bill" 的数据,同时试图获取p_name="tim" 的数据;

此时,两个线程就进入了死锁,谁也无法获取自己想要获取的资源,进入无线等待中,直到超时!

innodb_lock_wait_timeout 等待锁超时回滚事务:

The method is intuitive when two transactions wait for each other, when the wait time exceeds a certain threshold is set, for which a transaction is rolled back, another transaction can proceed.

This method is simple and effective, in the i nnodb, the parameter used to set the timeout innodb_lock_wait_timeout.

wait-for graph algorithms to proactively Deadlock Detection: InnoDB also provides a wait-for graph algorithms to proactively detect deadlocks, lock request can not meet the needs of every time and immediately upon entering wait, wait-for graph algorithm will be triggered .

6.1 How to avoid deadlocks

A fixed sequence access tables and rows. For example two transaction update data, update data A transaction order is 1, 2; B sequentially updated transaction data to 2,1. This is more likely to cause a deadlock;

Demolition of large transactions small. Large transactions are more likely deadlock, if the business allows the demolition of large transactions small;

In the same transaction, as far as possible to achieve a lock all the resources needed to reduce the probability of deadlock;

Reduce isolation level. If business allowed the isolation level down is also a good choice, such as the isolation level adjustment from RR, RC, lost a lot of deadlocks can be avoided because the gap caused by the lock. (Our MySQL norm);

Add a reasonable index for the table. You can see if you do not take the index will add a lock for each row of the table records, greatly increase the probability of deadlock.

Further reading:

· MySQL official website Reference Documents: HTTPS: //dev.mysql.com/doc/ref ...

· More content Stay tuned  vivo Internet technology micro-channel public number.

Guess you like

Origin www.cnblogs.com/jinanxiaolaohu/p/11448923.html