@@ -1946,6 +1946,10 @@ INSERT INTO card VALUES (NULL,'12345',1),(NULL,'56789',2);
19461946
19471947
19481948
1949+ ***
1950+
1951+
1952+
19491953# ### 一对多
19501954
19511955举例:用户和订单、商品分类和商品
@@ -1976,6 +1980,10 @@ INSERT INTO orderlist VALUES (NULL,'hm001',1),(NULL,'hm002',1),(NULL,'hm003',2),
19761980
19771981
19781982
1983+ ***
1984+
1985+
1986+
19791987# ### 多对多
19801988
19811989举例:学生和课程。一个学生可以选择多个课程,一个课程也可以被多个学生选择
@@ -6044,7 +6052,7 @@ ID 为 0 的事务创建 Read View:
60446052
60456053#### RC RR
60466054
6047- Read View 用于支持 RC(Read Committed,读已提交)和 RR(Repeatable Read,可重复读)隔离级别的实现,所以 SELECT 在 RC 和 RR 隔离级别才使用 MVCC 读取记录
6055+ Read View 用于支持 RC(Read Committed,读已提交)和 RR(Repeatable Read,可重复读)隔离级别的实现,所以 ** SELECT 在 RC 和 RR 隔离级别才使用 MVCC 读取记录**
60486056
60496057RR、RC 生成时机:
60506058
@@ -6300,7 +6308,7 @@ InnoDB 刷脏页的控制策略:
63006308
63016309锁机制:数据库为了保证数据的一致性,在共享的资源被并发访问时变得安全有序所设计的一种规则
63026310
6303- 作用:锁机制类似于多线程中的同步,可以保证数据的一致性和安全性
6311+ 利用 MVCC 性质进行读取的操作叫 ** 一致性读 ** ,读取数据前加锁的操作叫 ** 锁定读 **
63046312
63056313锁的分类:
63066314
@@ -6332,27 +6340,56 @@ InnoDB 刷脏页的控制策略:
63326340
63336341
63346342
6335- ### Server
6343+ ### 内存结构
63366344
6337- FLUSH TABLES WITH READ LOCK 简称(FTWRL),全局读锁,让整个库处于只读状态,工作流程:
6345+ 对一条记录加锁的本质就是在内存中创建一个锁结构与之关联,结构包括
63386346
6339- 1 . 上全局读锁(lock_global_read_lock)
6340- 2 . 清理表缓存(close_cached_tables)
6341- 3 . 上全局 COMMIT 锁(make_global_read_lock_block_commit)
6347+ * 事务信息:锁对应的事务信息,一个锁属于一个事务
6348+ * 索引信息:对于行级锁,需要记录加锁的记录属于哪个索引
6349+ * 表锁和行锁信息:表锁记录着锁定的表,行锁记录了 Space ID 所在表空间、Page Number 所在的页号、n_bits 使用了多少比特
6350+ * type_mode:一个 32 比特的数,被分成 lock_mode、lock_type、rec_lock_type 三个部分
6351+ * lock_mode:锁模式,记录是共享锁、排他锁、意向锁之类
6352+ * lock_type:代表表级锁还是行级锁
6353+ * rec_lock_type:代表行锁的具体类型和 is_waiting 属性,is_waiting = true 时表示当前事务尚未获取到锁,处于等待状态。事务获取锁后的锁结构是 is_waiting 为 false,释放锁时会检查是否与当前记录关联的锁结构,如果有就唤醒对应事务的线程
63426354
6343- 该命令主要用于备份工具做一致性备份,由于 FTWRL 需要持有两把全局的 MDL 锁,并且还要关闭所有表对象,因此杀伤性很大
6355+ 一个事务可能操作多条记录,为了节省内存,满足下面条件的锁使用同一个锁结构:
6356+
6357+ * 在同一个事务中的加锁操作
6358+ * 被加锁的记录在同一个页面中
6359+ * 加锁的类型时一样的
6360+ * 加锁的状态时一样的
6361+
6362+
6363+
6364+
6365+
6366+ ****
6367+
6368+
6369+
6370+ ### Server
63446371
63456372MySQL 里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)
63466373
6347- MDL 叫元数据锁,主要用来保护 MySQL内部对象的元数据,保证数据读写的正确性,通过 MDL 机制保证 DDL、DML、DQL 操作的并发, ** 当对一个表做增删改查操作的时候 ,加 MDL 读锁;当要对表做结构变更操作的时候 ,加 MDL 写锁**
6374+ MDL 叫元数据锁,主要用来保护 MySQL内部对象的元数据,保证数据读写的正确性,** 当对一个表做增删改查的时候 ,加 MDL 读锁;当要对表做结构变更操作 DDL 的时候 ,加 MDL 写锁** ,两种锁不相互兼容,所以可以保证 DDL、DML、DQL 操作的安全
63486375
6349- * MDL 锁不需要显式使用,在访问一个表的时候会被自动加上,事务中的 MDL 锁,在语句执行开始时申请,在整个事务提交后释放
6376+ 说明:DDL 操作执行前会隐式提交当前会话的事务,因为 DDL 一般会在若干个特殊事务中完成,开启特殊事务前需要提交到其他事务
63506377
6351- * MDL 锁是在 Server 中实现,不是 InnoDB 存储引擎层不能直接实现的锁
6378+ MDL 锁的特性:
6379+
6380+ * MDL 锁不需要显式使用,在访问一个表的时候会被自动加上,在事务开始执行时申请,在整个事务提交后释放
6381+
6382+ * MDL 锁是在 Server 中实现,不是 InnoDB 存储引擎层能直接实现的锁
63526383
63536384* MDL 锁还能实现其他粒度级别的锁,比如全局锁、库级别的锁、表空间级别的锁
63546385
6386+ FLUSH TABLES WITH READ LOCK 简称(FTWRL),全局读锁,让整个库处于只读状态,工作流程:
63556387
6388+ 1 . 上全局读锁(lock_global_read_lock)
6389+ 2 . 清理表缓存(close_cached_tables)
6390+ 3 . 上全局 COMMIT 锁(make_global_read_lock_block_commit)
6391+
6392+ 该命令主要用于备份工具做一致性备份,由于 FTWRL 需要持有两把全局的 MDL 锁,并且还要关闭所有表对象,因此杀伤性很大
63566393
63576394
63586395
@@ -6368,7 +6405,7 @@ MyISAM 存储引擎只支持表锁,这也是 MySQL 开始几个版本中唯一
63686405
63696406MyISAM 引擎在执行查询语句之前,会** 自动** 给涉及到的所有表加读锁,在执行增删改之前,会** 自动** 给涉及的表加写锁,这个过程并不需要用户干预,所以用户一般不需要直接用 LOCK TABLE 命令给 MyISAM 表显式加锁
63706407
6371- * 加锁命令:
6408+ * 加锁命令:(对 InnoDB 存储引擎也适用)
63726409
63736410 读锁:所有连接只能读取数据,不能修改
63746411
@@ -6396,7 +6433,7 @@ MyISAM 引擎在执行查询语句之前,会**自动**给涉及到的所有表
63966433
63976434![ ] (https://gitee.com/seazean/images/raw/master/DB/MySQL-MyISAM 锁的兼容性.png)
63986435
6399- 锁调度:MyISAM 的读写锁调度是写优先,因为写锁后其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞,所以 MyISAM 不适合做写为主的表的存储引擎
6436+ 锁调度:** MyISAM 的读写锁调度是写优先** ,因为写锁后其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞,所以 MyISAM 不适合做写为主的表的存储引擎
64006437
64016438
64026439
@@ -6532,14 +6569,16 @@ MyISAM 引擎在执行查询语句之前,会**自动**给涉及到的所有表
65326569
65336570#### 行级锁
65346571
6535- InnoDB 与 MyISAM 的** 最大不同** 有两点:一是支持事务;二是采用了行级锁,InnoDB 同时支持表锁和行锁
6572+ ##### 记录锁
6573+
6574+ InnoDB 与 MyISAM 的最大不同有两点:一是支持事务;二是采用了行级锁,** InnoDB 同时支持表锁和行锁**
65366575
6537- InnoDB 实现了以下两种类型的行锁:
6576+ 行级锁,也成为记录锁(Record Lock), InnoDB 实现了以下两种类型的行锁:
65386577
6539- - 共享锁 (S):又称为读锁,简称 S 锁,就是多个事务对于同一数据可以共享一把锁 ,都能访问到数据,但是只能读不能修改
6540- - 排他锁 (X):又称为写锁,简称 X 锁,就是不能与其他锁并存 ,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,只有获取排他锁的事务是可以对数据读取和修改
6578+ - 共享锁 (S):又称为读锁,简称 S 锁,多个事务对于同一数据可以共享一把锁 ,都能访问到数据,但是只能读不能修改
6579+ - 排他锁 (X):又称为写锁,简称 X 锁,不能与其他锁并存 ,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,只有获取排他锁的事务是可以对数据读取和修改
65416580
6542- 对于 UPDATE、DELETE 和 INSERT 语句,InnoDB 会** 自动给涉及数据集加排他锁** (行锁),在 commit 的时候会自动释放;对于普通 SELECT 语句,不会加任何锁
6581+ 对于 UPDATE、DELETE 和 INSERT 语句,InnoDB 会** 自动给涉及数据集加排他锁** (行锁),在 commit 的时候会自动释放(在事务中加的锁,会 ** 在事务中止或提交时自动释放 ** ) ;对于普通 SELECT 语句,不会加任何锁
65436582
65446583锁的兼容性:
65456584
@@ -6557,13 +6596,11 @@ SELECT * FROM table_name WHERE ... FOR UPDATE -- 排他锁
65576596
65586597
65596598
6599+ ****
65606600
65616601
6562- ***
6563-
65646602
6565-
6566- #### 锁操作
6603+ ##### 锁操作
65676604
65686605两个客户端操作 Client 1和 Client 2,简化为 C1、C2
65696606
@@ -6648,31 +6685,31 @@ SELECT * FROM table_name WHERE ... FOR UPDATE -- 排他锁
66486685
66496686 ![ ] (https://gitee.com/seazean/images/raw/master/DB/MySQL-InnoDB 锁操作6.png)
66506687
6651- 由于C1 、C2 操作的不同行,获取不同的行锁,所以都可以正常获取行锁
6688+ 由于 C1 、C2 操作的不同行,获取不同的行锁,所以都可以正常获取行锁
66526689
66536690
66546691
6655-
66566692
6657- ***
6693+
6694+ ****
66586695
66596696
66606697
66616698#### 锁分类
66626699
66636700##### 间隙锁
66646701
6665- 当使用范围条件检索数据,并请求共享或排他锁时,InnoDB 会给符合条件的已有数据进行加锁,对于键值在条件范围内但并不存在的记录,叫做间隙(GAP), InnoDB 会对间隙进行加锁,就是间隙锁
6702+ 当使用范围条件检索数据,并请求共享或排他锁时,InnoDB 会给符合条件的已有数据进行加锁,对于键值在条件范围内但并不存在的记录,叫做间隙(GAP), InnoDB 会对间隙进行加锁,就是间隙锁
66666703
66676704* 唯一索引加锁只有在值存在时才是行锁,值不存在会变成间隙锁,所以范围查询时容易出现间隙锁
66686705* 对于联合索引且是唯一索引,如果 where 条件只包括联合索引的一部分,那么会加间隙锁
66696706
6670- 加锁的基本单位是 next-key lock,该锁是行锁和这条记录前面的 gap lock 的组合,就是行锁加间隙锁
6707+ 加锁的基本单位是 next-key lock,该锁是行锁和 gap lock 的组合,可以保护当前记录和前面的间隙
66716708
6672- * 加锁遵循前开后闭原则
6673- * 假设有索引值 10、11、13,那么可能的间隙锁包括:(负无穷,10] 、(10,11] 、(11,13] 、(13,20,正无穷),锁住索引 11 会同时对间隙 (10,11] 、(11,13 ] 加锁
6709+ * 加锁遵循左开右闭原则
6710+ * 假设有 10、11、13,那么可能的间隙锁包括:(负无穷,10] 、(10,11] 、(11,13] 、(13,20,正无穷),锁住索引 11 会对 (10,11] 加锁
66746711
6675- 间隙锁优点:RR 级别下间隙锁可以解决事务的 ** 幻读问题** ,通过对间隙加锁,防止读取过程中数据条目发生变化
6712+ 间隙锁优点:RR 级别下间隙锁可以解决事务的一部分的 ** 幻读问题** ,通过对间隙加锁,可以防止读取过程中数据条目发生变化
66766713
66776714间隙锁危害:当锁定一个范围的键值后,即使某些不存在的键值也会被无辜的锁定,造成在锁定的时候无法插入锁定键值范围内的任何数据,在某些场景下这可能会对性能造成很大的危害
66786715
@@ -6703,51 +6740,108 @@ SELECT * FROM table_name WHERE ... FOR UPDATE -- 排他锁
67036740
67046741
67056742
6706- ***
6743+ ****
67076744
67086745
67096746
67106747##### 意向锁
67116748
6712- InnoDB 为了支持多粒度的加锁,允许行锁和表锁同时存在,支持在不同粒度上的加锁操作,InnoDB 增加了意向锁(Intention Lock )
6749+ InnoDB 为了支持多粒度的加锁,允许行锁和表锁同时存在,支持在不同粒度上的加锁操作,InnoDB 增加了意向锁(Intention Lock)
67136750
67146751意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁,意向锁分为两种:
67156752
67166753* 意向共享锁(IS):事务有意向对表中的某些行加共享锁
6717-
67186754* 意向排他锁(IX):事务有意向对表中的某些行加排他锁
67196755
6720- InnoDB 存储引擎支持的是行级别的锁,因此意向锁不会阻塞除全表扫描以外的任何请求,表级意向锁与行级锁的兼容性如下所示:
6756+ ** IX,IS 是表级锁** ,不会和行级的 X,S 锁发生冲突,意向锁是在加表级锁之前添加,为了在加表级锁时可以快速判断表中是否有记录被上锁,比如向一个表添加表级 X 锁的时:
6757+
6758+ - 没有意向锁,则需要遍历整个表判断是否有锁定的记录
6759+ - 有了意向锁,首先判断是否存在意向锁,然后判断该意向锁与即将添加的表级锁是否兼容即可,因为意向锁的存在代表有表级锁的存在或者即将有表级锁的存在
6760+
6761+ 兼容性如下所示:
67216762
67226763![ ] ( https://gitee.com/seazean/images/raw/master/DB/MySQL-意向锁兼容性.png )
67236764
6724- 插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,即多个事务在相同的索引间隙插入时如果不是插入间隙中相同的位置就不需要互相等待。假设某列有索引值2,6,只要两个事务插入位置不同,如事务 A 插入 3,事务 B 插入 4,那么就可以同时插入
6765+ ** 插入意向锁** Insert Intention Lock 是在插入一行记录操作之前设置的一种间隙锁,是行级锁
6766+
6767+ 插入意向锁释放了一种插入信号,即多个事务在相同的索引间隙插入时如果不是插入间隙中相同的位置就不需要互相等待。假设某列有索引值 2,6,只要两个事务插入位置不同,如事务 A 插入 3,事务 B 插入 4,那么就可以同时插入
67256768
67266769
67276770
67286771***
67296772
67306773
67316774
6732- ##### 死锁
6775+ ##### 自增锁
67336776
6734- 当并发系统中不同线程出现循环资源依赖,线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁
6777+ 系统会自动给 AUTO_INCREMENT 修饰的列进行递增赋值,实现方式:
67356778
6736- 死锁情况:线程 A 修改了 id = 1 的数据,请求修改 id = 2 的数据,线程 B 修改了 id = 2 的数据,请求修改 id = 1 的数据,产生死锁
6779+ * AUTO_INC 锁:表级锁,执行插入语句时会自动添加,在该语句执行完成后释放,并不是事务结束
6780+ * 轻量级锁:为插入语句生成 AUTO_INCREMENT 修饰的列时获取该锁,生成以后释放掉,不需要等到插入语句执行完后释放
67376781
6738- 解决策略 :
6782+ 系统变量 ` innodb_autoinc_lock_mode ` 控制采取哪种方式 :
67396783
6740- * 直接进入等待直到超时,超时时间可以通过参数 innodb_lock_wait_timeout 来设置,但是时间的设置不好控制,超时可能不是因为死锁,而是因为事务处理比较慢,所以一般不采取该方式
6741- * 主动死锁检测,发现死锁后** 主动回滚死锁链条中的某一个事务** ,让其他事务得以继续执行,将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑
6784+ * 0:全部采用 AUTO_INC 锁
6785+ * 1:全部采用轻量级锁
6786+ * 2:混合使用,在插入记录的数量确定是采用轻量级锁,不确定时采用 AUTO_INC 锁
67426787
67436788
67446789
67456790****
67466791
67476792
67486793
6794+ ##### 隐式锁
6795+
6796+ 一般情况下 INSERT 语句是不需要在内存中生成锁结构的,会进行隐式的加锁,保护的是插入后的安全
6797+
6798+ 注意:如果插入的间隙被其他事务加了间隙锁,此次插入会被阻塞,并在该间隙插入一个插入意向锁
6799+
6800+ * 聚簇索引:索引记录有 trx_id 隐藏列,表示最后改动该记录的事务 id,插入数据后事务 id 就是当前事务。其他事务想获取该记录的锁时会判断当前记录的事务 id 是否是活跃的,如果不是就可以正常加锁;如果是就创建一个 X 的锁结构,该锁的 is_waiting 是 false,为自己的事务创建一个锁结构,is_waiting 是 true(类似 Java 中的锁升级)
6801+ * 二级索引:获取数据页 Page Header 中的 PAGE_MAX_TRX_ID 属性,代表修改当前页面的最大的事务 ID,如果小于当前活跃的最小事务 id,就证明插入该数据的事务已经提交,否则就需要获取到主键值进行回表操作
6802+
6803+ 隐式锁起到了延迟生成锁的效果,如果其他事务与隐式锁没有冲突,就可以避免锁结构的生成,节省了内存资源
6804+
6805+ INSERT 在两种情况下会生成锁结构:
6806+
6807+ * 重复键:在插入主键或唯一二级索引时遇到重复的键值会报错,在报错前需要对对应的聚簇索引进行加锁
6808+ * 隔离级别 <= Read Uncommitted,加 S 型 Record Lock
6809+ * 隔离级别 >= Repeatable Read,加 S 型 next_key 锁
6810+
6811+ * 外键检查:如果待插入的记录在父表中可以找到,会对父表的记录加 S 型 Record Lock。如果待插入的记录在父表中找不到
6812+ * 隔离级别 <= Read Committed,不加锁
6813+ * 隔离级别 >= Repeatable Read,加间隙锁
6814+
6815+
6816+
6817+
6818+
6819+ ***
6820+
6821+
6822+
67496823#### 锁优化
67506824
6825+ ##### 优化锁
6826+
6827+ InnoDB 存储引擎实现了行级锁定,虽然在锁定机制的实现方面带来了性能损耗可能比表锁会更高,但是在整体并发处理能力方面要远远优于 MyISAM 的表锁,当系统并发量较高的时候,InnoDB 的整体性能远远好于 MyISAM
6828+
6829+ 但是使用不当可能会让 InnoDB 的整体性能表现不仅不能比 MyISAM 高,甚至可能会更差
6830+
6831+ 优化建议:
6832+
6833+ - 尽可能让所有数据检索都能通过索引来完成,避免无索引行锁升级为表锁
6834+ - 合理设计索引,尽量缩小锁的范围
6835+ - 尽可能减少索引条件及索引范围,避免间隙锁
6836+ - 尽量控制事务大小,减少锁定资源量和时间长度
6837+ - 尽可使用低级别事务隔离(需要业务层面满足需求)
6838+
6839+
6840+
6841+ ****
6842+
6843+
6844+
67516845##### 锁升级
67526846
67536847索引失效造成行锁升级为表锁,不通过索引检索数据,InnoDB 会将对表中的所有记录加锁,实际效果和** 表锁** 一样实际开发过程应避免出现索引失效的状况
@@ -6781,21 +6875,20 @@ InnoDB 存储引擎支持的是行级别的锁,因此意向锁不会阻塞除
67816875
67826876
67836877
6784- ##### 优化锁
6878+ ##### 死锁
67856879
6786- InnoDB 存储引擎实现了行级锁定,虽然在锁定机制的实现方面带来了性能损耗可能比表锁会更高,但是在整体并发处理能力方面要远远优于 MyISAM 的表锁,当系统并发量较高的时候,InnoDB 的整体性能远远好于 MyISAM
6880+ 不同事务由于互相持有对方需要的锁而导致事务都无法继续执行的情况称为死锁
67876881
6788- 但是使用不当可能会让InnoDB 的整体性能表现不仅不能比 MyISAM 高,甚至可能会更差
6882+ 死锁情况:线程 A 修改了 id = 1 的数据,请求修改 id = 2 的数据,线程 B 修改了 id = 2 的数据,请求修改 id = 1 的数据,产生死锁
67896883
6790- 优化建议 :
6884+ 解决策略 :
67916885
6792- - 尽可能让所有数据检索都能通过索引来完成,避免无索引行锁升级为表锁
6793- - 合理设计索引,尽量缩小锁的范围
6794- - 尽可能减少索引条件及索引范围,避免间隙锁
6795- - 尽量控制事务大小,减少锁定资源量和时间长度
6796- - 尽可使用低级别事务隔离(需要业务层面满足需求)
6886+ * 直接进入等待直到超时,超时时间可以通过参数 innodb_lock_wait_timeout 来设置,但是时间的设置不好控制,超时可能不是因为死锁,而是因为事务处理比较慢,所以一般不采取该方式
6887+ * 主动死锁检测,发现死锁后** 主动回滚死锁链条中较小的一个事务** ,让其他事务得以继续执行,将参数 ` innodb_deadlock_detect ` 设置为 on,表示开启该功能。较小的意思就是事务执行过程中插入、删除、更新的记录条数
67976888
6889+ 通过执行 ` SHOW ENGINE INNODB STATUS ` 可以查看最近发生的一次死循环,全局系统变量 ` innodb_print_all_deadlocks ` 设置为 on,就可以将每个死锁信息都记录在 MySQL 错误日志中
67986890
6891+ 死锁一般是行级锁,当表锁发生死锁时,会在事务中访问其他表时直接报错,破坏了持有并等待的死锁条件
67996892
68006893
68016894
@@ -6805,6 +6898,8 @@ InnoDB 存储引擎实现了行级锁定,虽然在锁定机制的实现方面
68056898
68066899#### 锁状态
68076900
6901+ 查看锁信息
6902+
68086903``` mysql
68096904SHOW STATUS LIKE ' innodb_row_lock%' ;
68106905```
@@ -6829,7 +6924,7 @@ SHOW STATUS LIKE 'innodb_row_lock%';
68296924
68306925``` mysql
68316926SELECT * FROM information_schema .innodb_locks ; # 锁的概况
6832- SHOW ENGINE INNODB STATUS; # InnoDB整体状态,其中包括锁的情况
6927+ SHOW ENGINE INNODB STATUS\G ; # InnoDB整体状态,其中包括锁的情况
68336928```
68346929
68356930![ ] ( https://gitee.com/seazean/images/raw/master/DB/MySQL-InnoDB查看锁状态.png )
0 commit comments