diff --git a/Database.md b/Database.md index e9312aa..a04dd82 100644 --- a/Database.md +++ b/Database.md @@ -627,25 +627,6 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID; -按照锁的共享策略来分: - -- **共享锁(S锁,Shared Locks)**:读锁。针对同一份数据,多个读操作可以同时进行而不会互相影响 -- **排它锁(X锁,Exclusive Locks)**:写锁。当前写操作没有完成前,它会阻断其他写锁和读锁 - -为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁,这两种意向锁都是表锁: - -- **意向共享锁(IS锁,Intention Shared Lock)**:当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁 -- **意向排他锁(IX锁,Intention Exclusive Lock)**:当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁 - -| 请求锁模式是否兼容当前锁模式 | X锁 | IX锁 | S锁 | IS锁 | -| ---------------------------- | ------ | ------ | ------ | ------ | -| X锁 | `冲突` | `冲突` | `冲突` | `冲突` | -| IX锁 | `冲突` | 兼容 | `冲突` | 兼容 | -| S锁 | `冲突` | `冲突` | 兼容 | 兼容 | -| IS锁 | `冲突` | 兼容 | 兼容 | 兼容 | - -若一个事务请求锁模式与当前锁兼容,InnoDB就将请求锁授予该事务;反之,两者不兼容,该事务就要等待锁释放。 - 从加锁策略上分: @@ -669,19 +650,34 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID; - **数据定义语句**(包括建表、修改表结构等) - **更新类事务的提交语句** -### 使用场景 -- 典型使用场景是做**全库逻辑备份(mysqldump)** +**使用场景** +- 典型使用场景是做**全库逻辑备份(mysqldump)** -**数据库只读状态的危险性:** +**数据库只读状态的危险性** - 如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就能停止 - 如果你在从库上备份,那么备份期间从库不能执行主库同步过来的binlog,会导致主从延迟 +全局锁两种方法: + +- **FLUSH TABLES WRITE READ LOCK(`FTWRL`)** +- **set global readonly=true** + +一般建议使用 `FTWRL` 方式,因为: + +- 有些系统中 readonly 的值会被用来做其它逻辑。如判断一个库是主库或备库。因此修改 global 变量的方式影响面更大 +- 异常处理机制上有差异: + - 如果执行`FTWRL`后由于客户端发生异常断开,则MySQL会自动释放这个全局锁,整个库回到可正常更新状态 + - 将库设置为`readonly`后,若客户端发生异常,则数据库会一直保持`readonly`状态,导致整库长时间处于不可写状态 + - readonly 对super用户权限无效 + + + ### 全局读锁(FTWRL) **为什么需要全局读锁(FTWRL)?** @@ -711,20 +707,7 @@ unlock tables -### 两种全局锁 - -全局锁两种方法: - -- **FLUSH TABLES WRITE READ LOCK(`FTWRL`)** -- **set global readonly=true** - -一般建议使用 `FTWRL` 方式,因为: - -- 有些系统中 readonly 的值会被用来做其它逻辑。如判断一个库是主库或备库。因此修改 global 变量的方式影响面更大 -- 异常处理机制上有差异: - - 如果执行`FTWRL`后由于客户端发生异常断开,则MySQL会自动释放这个全局锁,整个库回到可正常更新状态 - - 将库设置为`readonly`后,若客户端发生异常,则数据库会一直保持`readonly`状态,导致整库长时间处于不可写状态 - - readonly 对super用户权限无效 +### set global readonly=true @@ -776,7 +759,7 @@ MDL 是在事务提交后才会释放,这意味着**事务执行期间,MDL -### 意向锁 +### 意向锁(Intention Locks) **意向锁的目的是为了快速判断表里是否有记录被加锁**。 @@ -794,6 +777,8 @@ select ... for update; 意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁(lock tables … read)和独占表锁(lock tables … write)发生冲突。 + + 表锁和行锁是满足读读共享、读写互斥、写写互斥的。 - 如果没有**意向锁**,那么加**独占表锁**时,就需要遍历表里所有记录,查看是否有记录存在独占锁,这样效率会很慢 @@ -801,6 +786,31 @@ select ... for update; +意向共享锁和意向排它锁是数据库主动加的,不需要我们手动处理。意向共享锁和意向排他锁锁定的是表。 + +为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁,这两种意向锁都是表锁: + +- **意向共享锁(IS锁,Intention Shared Lock)** +- **意向排他锁(IX锁,Intention Exclusive Lock)** + + + +#### 意向共享锁(Intention Shared Lock) + +**意向共享锁(IS锁)是指当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁。** + +**作用**:通知数据库接下来需要施加什么锁并对表加锁。如果需要对记录A加共享锁,那么此时innodb会先找到这张表,对该表加意向共享锁之后,再对记录A添加共享锁。 + + + +#### 意向排他锁(Intention Exclusive Lock) + +**意向排他锁(IX锁)是指当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁** + +**作用**:通知数据库接下来需要施加什么锁并对表加锁。如果需要对记录A加排他锁,那么此时innodb会先找到这张表,对该表加意向排他锁之后,再对记录A添加排他锁。 + + + ### AUTO-INC锁(自增长锁) 在为某个字段声明 `AUTO_INCREMENT` 属性时,之后可以在插入数据时,可以不指定该字段的值,数据库会自动给该字段赋值递增的值,这主要是通过 `AUTO-INC` 锁实现的。 @@ -836,6 +846,8 @@ InnoDB 存储引擎提供了个`innodb_autoinc_lock_mode`的系统变量,是 **特点:开销大、加锁慢、会出现死锁、发生锁冲突的概率最低、并发度也最高。** + + 在 `InnoDB` 事务中,行锁通过给索引上的**索引项加锁**来实现。只有通过索引条件检索数据,才使用行级锁,否则将使用表锁。行级锁定同样分为两种类型:`共享锁` 和 `排他锁`,以及加锁前需要先获得的 `意向共享锁` 和 `意向排他锁`。行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是**两阶段锁协议**。 行锁实现算法(**3种锁都是排它锁(X锁)**): @@ -844,7 +856,61 @@ InnoDB 存储引擎提供了个`innodb_autoinc_lock_mode`的系统变量,是 - **Gap Lock(间隙锁)** - **Next-Key Lock(临键锁)** -### Record Lock(记录锁) + + +### 属性锁 + +按照锁的共享策略来分: + +- **共享锁(S锁,Shared Locks)** +- **排它锁(X锁,Exclusive Locks)** + +| 请求锁模式是否兼容当前锁模式 | X锁 | IX锁 | S锁 | IS锁 | +| ---------------------------- | ------ | ------ | ------ | ------ | +| X锁 | `冲突` | `冲突` | `冲突` | `冲突` | +| IX锁 | `冲突` | 兼容 | `冲突` | 兼容 | +| S锁 | `冲突` | `冲突` | 兼容 | 兼容 | +| IS锁 | `冲突` | 兼容 | 兼容 | 兼容 | + +若一个事务请求锁模式与当前锁兼容,InnoDB就将请求锁授予该事务;反之,两者不兼容,该事务就要等待锁释放。 + +#### 共享锁(Shared Locks) + +**共享锁( `S锁 ` 或 `读锁`)是指针对同一份数据,多个读操作可以同时进行而不会互相影响。**若事务T对数据对象A加上**S锁**,则事务T只能读A;其它事务只能再对A加**S锁**,而不能加X锁,直到T释放A上的S锁。加锁方式: + +- `select ... lock in share mode` + +**注意**: + +- 共享锁都是行锁 + + + +#### 排它锁(Exclusive Locks) + +**排它锁( `X锁` 或 `写锁`)是指当前写操作没有完成前,它会阻断其它写锁和读锁。**若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它事务都不能再对A加任何类型的锁,直到T释放A上的锁。加锁方式: + +- `insert` +- `update` +- `delete` +- `select ... for update` + +**注意** + +- 在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁 + +- 排它锁可以是**行锁**也可以是**表锁** + + ```sql + -- 当username是主键时,锁定的是行锁;当username不是主键时,是表锁 + SELECT count(*) as total FROM test WHERE username = "zhangsan" FOR UPDATE + ``` + + + +### 锁实现方式 + +#### Record Lock(记录锁) **为单个行记录上的锁,总是会去锁住索引记录**。 @@ -867,7 +933,7 @@ UPDATE t_user SET age = 50 WHERE id = 1; -### Gap Lock(间隙锁) +#### Gap Lock(间隙锁) 间隙锁,想一下幻读的原因,其实就是行锁只能锁住行,但新插入记录这个动作,要更新的是记录之间的“间隙”。**所以加入间隙锁来解决幻读。** @@ -885,7 +951,7 @@ SELECT * FROM t_user WHERE id > 1 AND id < 10 FOR UPDATE; -### Next-Key Lock(临键锁) +#### Next-Key Lock(临键锁) Gap Lock + Record Lock, 左开又闭。 @@ -926,60 +992,9 @@ INSERT INTO table VALUES(100, 30, 'zhang'); -## 其它锁 - -### 共享锁(Shared Locks) - -共享锁又称为 `S锁` 或 `读锁`。若事务T对数据对象A加上 `S锁`,则事务T `只能读A`;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。 - -- `select ... lock in share mode`: 会加`共享锁` - - - -### 排它锁(Exclusive Locks) - -排它锁又称为 `X锁` 或 `写锁`。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。 - -注意:排他锁会阻止其它事务再对其**锁定的数据**加读或写的锁,但是不加锁的就没办法控制了。 - -- `insert`、`update`、`delete`、`select ... for update`:会加`排它锁` - - - -### MVCC - -https://mp.weixin.qq.com/s/KbOiJ8SKJ_wFZcIyDVGD9g - -**MVCC**主要是通过**版本链**和**ReadView**来实现的。在Mysql的InnoDB引擎中,只有**已提交读(READ COMMITTD)**和**可重复读(REPEATABLE READ)**这两种隔离级别下的事务采用了MVCC机制。 - -- **版本链** - - 在InnoDB引擎表中,它的每一行记录中有两个必要的隐藏列: - - - `DATA_TRX_ID`:表示插入或更新该行的最后一个事务的事务标识符,同样删除在内部被视为更新,在该更新中,行中的特殊位被设置为将其标记为已删除。行中会有一个特殊位置来标记删除。 - - `DATA_ROLL_PTR`:存储了一个指针,它指向这条记录的上一个版本的位置,通过它来获得上一个版本的记录信息。 - - **作用**:解决了读和写的并发执行。 - -- **ReadView** - - ReadView主要存放的是当前事务操作时,系统中仍然活跃着的事务(事务开启后,没有提交或回滚的事务)。 - - - **ReadView数据结构**:ReadView是MySQL底层使用C++代码实现的一个结构体,主要的内部属性如下: - - `trx_ids`:数组,存储的是创建readview时,活跃事务链表里所有的事务ID - - `low_limit_id`:存储的是创建readview时,活跃事务链表里最大的事务ID - - `up_limit_id`:存储的是创建readview时,活跃事务链表里最小的事务ID - - `creator_trx_id`:当前readview所属事务的事务版本号 - - - **ReadView创建策略**:对于读提交和可重复读事务隔离级别来说,ReadView创建策略是不同的,这样才能保证隔离性不同 - - `可重复读隔离级别`:事务开启后,第一次查询的时候创建,之后一直不变,直到事务结束 - - `读提交隔离级别`:事务开启后,每一次读取都重新创建 - - 也就是说已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。 - - +## 锁问题 -## 死锁 +### 死锁 当两个及以上的事务,双方都在等待对方释放已经持有的锁或因为加锁顺序不一致造成循环等待锁资源,就会出现“死锁”。常见的报错信息为 ” `Deadlock found when trying to get lock...`”。`MySQL` 出现死锁的几个要素为: @@ -988,9 +1003,7 @@ https://mp.weixin.qq.com/s/KbOiJ8SKJ_wFZcIyDVGD9g - 锁资源同时只能被同一个事务持有或者不兼容 - 事务之间因为持有锁和申请锁导致彼此循环等待 - - -### 预防死锁 +#### 预防死锁 - `innodb_lock_wait_timeout` **等待锁超时回滚事务** @@ -1004,7 +1017,7 @@ https://mp.weixin.qq.com/s/KbOiJ8SKJ_wFZcIyDVGD9g -### 解决死锁 +#### 解决死锁 - 等待事务超时,主动回滚 - 进行死锁检查,主动回滚某条事务,让别的事务能继续走下去 @@ -1025,8 +1038,6 @@ kill trx_mysql_thread_id; -## 锁问题 - ### 脏读 **脏读指的是不同事务下,当前事务可以读取到另外事务未提交的数据**。 @@ -1071,6 +1082,41 @@ kill trx_mysql_thread_id; +# MVCC + +https://mp.weixin.qq.com/s/KbOiJ8SKJ_wFZcIyDVGD9g + +**MVCC**主要是通过**版本链**和**ReadView**来实现的。在Mysql的InnoDB引擎中,只有**已提交读(READ COMMITTD)**和**可重复读(REPEATABLE READ)**这两种隔离级别下的事务采用了MVCC机制。 + +- **版本链** + + 在InnoDB引擎表中,它的每一行记录中有两个必要的隐藏列: + + - `DATA_TRX_ID`:表示插入或更新该行的最后一个事务的事务标识符,同样删除在内部被视为更新,在该更新中,行中的特殊位被设置为将其标记为已删除。行中会有一个特殊位置来标记删除。 + - `DATA_ROLL_PTR`:存储了一个指针,它指向这条记录的上一个版本的位置,通过它来获得上一个版本的记录信息。 + + **作用**:解决了读和写的并发执行。 + +- **ReadView** + + ReadView主要存放的是当前事务操作时,系统中仍然活跃着的事务(事务开启后,没有提交或回滚的事务)。 + + - **ReadView数据结构**:ReadView是MySQL底层使用C++代码实现的一个结构体,主要的内部属性如下: + - `trx_ids`:数组,存储的是创建readview时,活跃事务链表里所有的事务ID + - `low_limit_id`:存储的是创建readview时,活跃事务链表里最大的事务ID + - `up_limit_id`:存储的是创建readview时,活跃事务链表里最小的事务ID + - `creator_trx_id`:当前readview所属事务的事务版本号 + + - **ReadView创建策略**:对于读提交和可重复读事务隔离级别来说,ReadView创建策略是不同的,这样才能保证隔离性不同 + - `可重复读隔离级别`:事务开启后,第一次查询的时候创建,之后一直不变,直到事务结束 + - `读提交隔离级别`:事务开启后,每一次读取都重新创建 + + 也就是说已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。 + + + + + # 数据库事务 **什么叫事务?**