Mysql技术内幕(三)--InnoDB索引以及锁

Mysql技术内幕系列文章

Mysql技术内幕系列文章

一.索引算法

1.1 B+树

B+树是为磁盘或者其他直接存取辅助设备设计的一种平衡查找树。B+树中,所有记录节点都是按照键值的大小顺序存放在同一层的叶子节点上,并由各叶子节点指针进行连接。

例子:高度为2,每页可以存放4条记录:
在这里插入图片描述
B+树的插入操作:
B+树的插入必须保证插入后叶子节点中的顺序依然排序,并且不管怎么变化,B+树总是会保持平衡。但是为了保持平衡对于新插入的键值可能需要做大量的拆分页的操作。

B+树的删除操作:
B+树使用填充因子(fill factor)来控制树的删除变化。

因为B+树并不是本章的一个重点,所以我也打算只说这么多。

1.1.1 B+树索引

B+树索引在数据库当中有一个特点是高扇出性,其高度一般在2 ~ 4层,也就是说查找某一个键值的行记录时最多只需要2 ~ 4次IO。B+树索引可以分为聚簇索引和辅助索引。

聚簇索引

聚簇索引就是按照每张表的主键构造一颗B+树,叶子节点存放的是整张表的行记录数据,其叶子节点也称为数据页,而在非数据页的索引页中,存放的仅仅是键值以及指向数据页的偏移量,此外,每个数据页都通过一个双向链表来进行连接。由于实际的数据页只能按照一颗B+树进行排序,因此每张表只能拥有一个聚簇索引。

聚簇索引的存储并不是物理上连续的,而是逻辑上连续的,其中包含两点:

  • 页通过双向列表连接,页按照主键的顺序排序。
  • 每个页中的记录也是通过双向链表进行维护的,物理存储上可以同样不按照主键存储。

聚簇索引的好处:

  • 对于主键的排序查找和范围查找速度非常快,因为叶子节点的数据就是用户所需要查询的数据。
  • 如果想要查找主键某一范围内的数据,通过叶子节点的上层中间节点就可以得到页的范围,之后直接读取数据页即可。

辅助索引

辅助索引也就是非聚簇索引,叶子节点不包含行记录的全部数据。叶子节点除了包含键值以外,每个叶子节点的索引行中还包含了一个书签(bookmark)

书签:用来告诉InnoDB存储引擎哪里可以找到与索引相对应的行数据,书签作为一个行标识符(Row Identified RID),可以用如“文件号:页号:槽号”的格式来定位实际的行数据。
由于InnoDB存储引擎表是索引组织表,因此InnoDB存储引擎的辅助索引的书签就是相对应的行数据的聚簇索引键。

大家知道,聚簇索引是顺序读,非聚簇索引是离散读,而一般数据库就通过预读的方式来避免多次的离散度操作。

1.1.2 B+树索引的分裂

首先大家应该了解到,B+树的索引页的分裂并不总是从页的中间记录开始,因为这样可能导致页空间的浪费,举个例子:

数据页中的记录为:1,2,3,4,5,6,7,8,9。此时要插入记录10
假设插入操作会引起页的分裂,那么此时会将记录5作为分裂点记录,分裂后得到两个页:
P1:1,2,3,4
P2:5,6,7,8,9,10
又因为插入是根据自增顺序进行的,P1这个也中将不会在有记录被插入,从而导致空间的浪费,而P2又会再次进行分裂(当需要分裂的时候)。

然后对于InnoDB存储引擎而言,通过Page Header保存的插入顺序信息来决定是向左还是向右进行分裂,同时决定将哪一个点作为分裂点(若插入是随机的,则取页的中检记录作为分裂点的记录), 保存的信息如下:

  • PAGE_LAST_INSERT
  • PAGE_DIRECTION
  • PAGE_N_DIRECTION

1.2.3 B+树索引的管理

索引管理

索引的创建和删除可以通过两种方法:

  • Alter Table
ALTER TABLE [表名] ADD [INDEX | key] [索引名称]([需要加索引的列名称])
  • Create/Drop Index
CREATE INDEX [索引名称] on [表名]([需要加索引的列名称]);

例如,我有一张login表(主键为id):
在这里插入图片描述
我通过两种方式进行添加索引:

# username(10)表示只对前10个字段索引
ALTER TABLE login ADD INDEX myindex(username(10))CREATE INDEX unioIndex on login(username,login_time);

可以通过命令SHOW INDEX FROM [表名];的方式查看索引信息,如:
在这里插入图片描述
参考表:

属性名 含义
Table 索引所在的表名
Non_unique 非唯一的索引,可以看到primary key是0,因为其必须是唯一
Key_name 索引的名字,用户可以通过这个名字来执行Drop Index
Seq_in_index 索引中该列的位置(直观的看联合索引)
Column_name 索隐列的名称
Collation 列以什么方式存储在索引中,可以为A(B+树索引总是A)或者NULL(使用Heap存储引擎并且简历了Hash索引)
Cardinality 很重要,表示索引中唯一值的数目的估计值,需要其尽可能接近1,若非常小,可以考虑删除该索引
Sub_part 是否是列的部分被索引,参考username(10)
Packed 关键字如何被压缩,若没有被压缩,则为NULL
Null 是否索引的列中含有Null值
Index_type 索引的类型,包含BTREE和HASH
Comment 注释

Cardinality这个值非常关键,优化器会根据这个值来判断是否使用这个索引。这个值是不太准确的,也并非实时更新,若需要更新索引Cardinality的信息,可以使用ANALYZE TABLE [表名];进行刷新。

Fast Index Creation(FIC)

在Mysql5.5版本之前,Mysql数据库对于索引的添加或者删除的这类DDL操作,其操作过程为:

  1. 首先创建一张新的临时表,表结构为通过命令Alter Table新定义的结构。
  2. 然后把原表中的数据导入到临时表中。
  3. 接着删除原表。
  4. 最后把临时表重命名为原来的表名。

那么,如果一张表存放着大量的数据,进行删除或者添加索引的操作,这肯定会消耗很长的时间。因此有了一种快速索引创建的方式:FIC。

对于辅助索引的创建,InnoDB存储引擎会对创建索引的表加上一个S锁。而在创建过程中,不需要新建一个临时表。
对于其删除,InnoDB存储引擎只需要更新内部视图,并将辅助索引的空间标记为可用,同时删除Mysql数据库内部视图上对该表的索引定义即可。

注意:
FIC方式只限定于辅助索引,对主键的创建和删除同样需要重建一张表。

Online Schema Change

Online Schema Change(OSC):在线架构改变。指在事务的创建过程中,可以有读写事务对表进行操作。 实现OSC的步骤如下:

  1. intit:初始化阶段,对创建的表做一些验证工作(比如检查表是否有主键、存在触发器等)
  2. createCopyTable和alterCopyTable:创建和原始表结构一样的新表,并添加相同的索引和列。
  3. createDeltasTable:创建deltas表(记录之后对原表的所有DDL操作)
  4. createTriggers:对原表创建增删改的触发器,其触发记录会记录到deltas表中。
  5. startSnpshotXact:开启OSC操作的事务。
  6. selectTableIntoOutfile:将原表中的数据写入到新表。(分片)
  7. dropNCIndexes:导入到新表前,删除新表所有的辅助索引。
  8. loadCopyTable:将第六步导出的分片文件导入到新表中。
  9. replayChanges:将OSC过程中原表DML操作的记录(存放在deltas表中)应用到新表中。
  10. recreateNCIndexes:重新创建辅助索引。
  11. replayChanges:再次进行DML日志的回放操作。(这些日志是第十步过程中产生的日志)
  12. swapTables:将原表和新表交换名字,该过程会锁定两张表,不允许新的数据产生。

总而言之是啥呢?
OSC就是在事务创建过程中,对某一张表A的操作都会放到一张临时表M中,并且把这些操作都应用到一个表A的一个备份表B中。最后将A表和B表的名字互换。

1.2 Cardinality值

先来说下什么时候用B+树索引?在访问表中很少一部分时使用B+树索引才有意义,对于性别字段这一类取值范围小的,也就是低选择性的,添加B+树索引是没有必要的。那么怎样查看索引是否是高选择性呢?通过Cardinality来观察。

Cardinality表示索引中不重复记录数量的预估值,在InnoDB存储引擎中,Cardinality统计信息的更新发生在两个操作中:INSERTUPDATE。但同时,也不可能每次发生这俩操作就去更新Cardinality信息,这样会增加数据库系统的符合。因此InnoDB存储引擎有一个更新策略:

  • 表中1/16的数据已发生过变化。
  • stat_modified_counter(表示发生变化的次数) > 2 000 000 000。

再来说一下Cardinality的统计,通过采样的方法来完成:

  1. 取得B+树索引中叶子节点的数量,记为A。
  2. 随机取得B+树索引中的8个叶子节点,统计每个页不同记录的个数,记为P1,P2……P8。
  3. 根据采样信息给出Cardinality的预估值:Cardinality=(P1+P2……+P8)*A/8

备注:InnoDB存储引擎默认对8个叶子节点进行采用。

1.3 B+树索引的使用

联合索引
联合索引是指对表上的多个列进行索引。例如:

CREATE TABLE test(
	a INT,
	b INT,
	PRIMARY KEY (a),
	KEY unioIdx(a,b)# 联合索引
)ENGINE=INNODB

联合索引的构造:
从本质来说,联合索引也是一颗B+树,但不同的是联合索引的简直的数量不是1,而是大于等于2。

下面假设给定两个键值的名称,分别为a,b,使用B+树索引,如图:在这里插入图片描述
那么数据(1,1),(1,2),(2,1),(2,4),(1,1),(3,2)是根据(a,b)的顺序进行存放的,这里直接说结论:

  • 对于查询select * from table where a=xxx and b=xxx是可以使用联合索引(a,b)的。
  • 对于单个的a列查询select * from table where a=xxx也可以使用联合索引(a,b)的。
  • 对于单个b列查询select * from table where b=xxx不可以使用B+树索引。(因为每个数据对的第二个数字单独拿出来的顺序是:1,2,1,4,1,2显然不是排序的)

那么回过头说联合索引,联合索引的好处就是对上述情况的第二个键值进行了排序处理。

覆盖索引
覆盖索引即:从辅助索引中就可以得到查询的记录,而不需要查询聚集索引中的记录。

其次,我们知道辅助索引不包含整行记录的所有信息,因此其大小要远小于聚簇索引,因此可以减少大量IO的操作。

1.4 InnoDB中的哈希算法

InnoDB存储引擎中:

  • 使用哈希算法来对字典进行查找。
  • 哈希的冲突机制(哈希碰撞)使用链表方式解决
  • 哈希函数采用除法散列方式。

对于缓冲池页的哈希表来说,在缓冲池中的Page页都有一个chain指针,它指向相同哈希函数值的页。
对于除法散列来说,m(槽数)的取值为略大于2倍的缓冲池页数量的质数。
例如:若当前缓冲池大小为10M,则共有640个16KB的页,对于哈希表来说,需要分配640*2=1280个槽,然后离1280最近的质数为1399,那么在启动的时候会分配1399个槽的哈希表,用来哈希查询所在缓冲池中的页。

InnoDB存储引擎的缓冲池对于其中的页是怎么进行查找的?

InnoDB存储引擎的表空间都有一个space_id,用户所要查询的应该是某个表空间的某个连续16KB的页,即偏移量offset。InnoDB存储引擎将space_id左移20位,然后加上这个space_id和offset,即关键字K=space_id<<20+space_id+offset,然后通过除法散列到各个槽中。

1.5 InnoDB全文检索

全文检索通常使用倒排索引来实现。它在辅助表中存储了单词与单词自身在一个或者多个文档所在位置之间的映射。 这通常利用关联数组来实现,拥有两种表现形式:

  • inverted file index:表现形式为:{单词,单词所在文档的ID}
  • full inverted index:表现形式为:{单词,(单词所在文档的ID,在具体文档中的位置)}

Mysql在1.2.x版本开始,才开始支持全文检索,采用的是full inverted index的方式。

在InnoDB存储引擎中,将(DocumentId,Position)视为一个ilist。因此在全文检索的表中,有两个列,一个是word字段,一个是ilist字段,并且在word字段上有索引。
此外,InnoDB存储引擎在ilist字段中存放了Position信息(存储位置),因此可以进行Proximity Search(近似搜索),比如SELECT * from login where username LIKE '%3';

倒排索引需要将word存放在一张表上,这个表叫做Auxiliary Table(辅助表),并且共有6张表,存放于磁盘上。

InnoDB中的全文索引有俩重要概念:

  • FTS Index Cache:全文检索索引缓存。

FTS Index Cache是一个红黑树结构,根据(word,ilist)进行排序,意味着插入的数据已经更新了对应的表。 同时,在缓存中的word字段要想合并到Auxiliary Table(辅助表,存放word的表)中,这个合并过程类似于前面文章讲的Insert Buffer,批量插入。
当然,当需要进行全文检索的时候,辅助表会先把缓存中的word整合在一起,再从表中查询结果。

  • FTS Document ID:

为了支持全文检索,必须有一个列和word进行映射,在InnoDB中这个列被命名为FTS_DOC_ID,其类型必须是BIGINT UNSIGNED NOT NULL,并且InnoDB会自动在该列上添加一个名为FTS_DOC_ID_INDEX的唯一索引。

InnoDB存储引擎的全文检索存在的限制:

  • 每张表只能有一个全文检索的索引。
  • 由多列组合而成的全文检索的索引列必须使用相同的字符集和排序规则。
  • 不支持没有单词界定符的语言,如中文、日语。

全文检索的使用:
1.先创建表和插入数据。

CREATE TABLE fts_a(
	FTS_DOC_ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL,
	body TEXT,
	PRIMARY KEY(FTS_DOC_ID)
)INSERT into fts_a SELECT NULL,'Pease porridge in the pot';
INSERT into fts_a SELECT NULL,'Pease porridge hot,pease porridge cold';
INSERT into fts_a SELECT NULL,'Nine days old';
INSERT into fts_a SELECT NULL,'Some like it hot, some like it cold';
INSERT into fts_a SELECT NULL,'Some like it int the pot';
INSERT into fts_a SELECT NULL,'Nine days old';
INSERT into fts_a SELECT NULL,'I like code days';
# body字段是进行全文检索的字段,因此创建一个类型为FULLTEXT的索引。
CREATE FULLTEXT INDEX idx_fts ON fts_a(body);

2.通过设置参数innodb_ft_aux_table来查看分词对应的信息。

在这里插入图片描述
注意数据库的名称要对上

SET GLOBAL innodb_ft_aux_table='test/fts_a';
SELECT * FROM information_schema.INNODB_FT_INDEX_TABLE;

部分结果:
在这里插入图片描述
可以看到每个word都对应了一个DOC_ID和POSITION,并且还记录了这么几个重要的信息:

  • FIRST_DOC_ID:该word第一次出现的文档ID。
  • LAST_DOC_ID:最后一次出现的文档ID。
  • DOC_COUNT:该word在多少个文档中存在。

大家可以看,其中我的code单词,只出现了一次。

3.全文检索的使用:

# 全文检索通过Match函数进行查询,默认采用NATURAL LANGUAGE模式
# 代表查询代带有指定word的文档。
SELECT * FROM fts_a WHERE MATCH(body) AGAINST('Porridge' IN NATURAL LANGUAGE MODE);

加上Explain关键字后,可以看到结果,说明使用了全文检索的倒排索引。
在这里插入图片描述
这里再来讲一讲全文检索的另一个模式:Boolean当使用该修饰符的时候,查询字符串的前后字符会有特殊的含义。例如:

# +代表这个单词必须出现,-代表这个单词一定不存在
SELECT * FROM fts_a WHERE MATCH(body) AGAINST('+Pease -hot' IN BOOLEAN MODE);

其余的操作符还有:(只列举几个好记的)

操作符 含义
+ 该word必须存在
- 该word必须被排除
> 表示出现该单词时增加相关性
< 表示出现该单词时降低相关性
~ 允许出现该单词,但是出现时相关性为负
* 表示以该单词开头的单词

全文检索相关性的计算依据:

  • word是否在文档中出现。
  • word在文档中出现的次数。
  • word在索引列中的数量。
  • 多少个文档包含该word。

最后再总结式的讲一下文档中分词的插入和删除操作。
插入:

在事务提交的时候完成。

删除:

在事务提交的时候,不删除磁盘中辅助表Auxiliary Table的记录,而是删除FTS Cache Index中的记录,对于辅助表中被删除的记录,InnoDB会记录他的FTS Document ID,并把它保存在一张表中(DELETED Auxiliary Table)中

例如:

DELETE FROM test.fts_a WHERE FTS_DOC_ID=7;
SELECT * FROM information_schema.INNODB_FT_DELETED;

结果:验证了上述的结论
在这里插入图片描述
当然,也可以彻底删除倒排索引中该文档的分词信息:

SET GLOBAL innodb_optimize_fulltext_only=1;
OPTIMIZE TABLE fts_a;

二. 锁

InnoDB存储引擎锁的实现提供一致性的非锁定读、行级锁支持,还可以同时得到并发性和一致性。在数据库中,lock和latch都可以被称之为“锁”。

  • latch:轻量级锁,要求锁定的时间必须非常短,目的是保证并发线程操作临界资源的正确性。可以分为mutex(互斥量)rwlock(读写锁)
  • lock:对象:事务, 锁定的是数据库中的对象,如表、页。

两者对比表:

lock latch
对象 事务 线程
保护 数据库内容 内存数据结构
持续时间 整个事务过程 临界资源
模式 行锁、表锁、意向锁 读写锁、互斥量
死锁 通过waits-for graph、time out等机制进行死锁检测与处理 没有死锁检测和处理机制。仅通过应用程序加锁的顺序来保证没有死锁的情况发生。
存在于 Lock Manager的哈希表中 每个数据结构的对象中

2.1 InnoDB存储引擎中的锁

2.1.1 锁的类型

InnoDB存储引擎实现了如下两种标准的行级锁:

  • 共享锁(S Lock),允许事务读一行数据。
  • 排他锁(X Lock),允许事务删除或者更新一行数据。

再来说下两个概念,锁的兼容和不兼容。

锁兼容:如果一个事务T1已经获得了行m的S锁,那么事务T2可以立即获得行m的S锁。(因为读取并不会改变行m的数据)
锁不兼容:若有其他的事务T3想要获得行m的X锁,那必须等待事务T1和T2释放掉行m上的S锁。

注意:兼容是指对同一记录(row)锁的兼容性情况。
在这里插入图片描述
此外,InnoDB存储引擎还支持多粒度缩影,即允许事务在行级上的锁和表级上的锁同时存在,而为了支持在不同粒度上的加锁操作,InnoDB推出了意向锁,其锁定的对象可以分为多个层次,目前支持两种意向锁:

  • 意向共享锁(IS Lock):事务想要获得一张表中某几行的共享锁。
  • 意向排他锁(IX Lock):事务想要获得一张表中某几行的排他锁。

由于InnoDB支持的是行级锁,因此意向锁并不会堵塞除全表扫描以外的任何请求。 表级意向锁行级锁的兼容性如下图:
在这里插入图片描述
在InnoDB1.0开始,我们可以通过3张表来监控当前事务并分析可能存在的锁问题。
表1:INNODB_TRX

SELECT * from information_schema.INNODB_TRX;

结构:
在这里插入图片描述
表2:INNODB_LOCKS

SELECT * from information_schema.INNODB_LOCKS;

结构:
在这里插入图片描述
表3:INNODB_LOCK_WAITS

SELECT * from information_schema.INNODB_LOCK_WAITS;

结构:
在这里插入图片描述

2.1.2 一致性非读定锁

一致性非读定锁是指InnoDB存储引擎通过多版本控制的方式来读取当前执行时间数据库中行的数据。 作为InnoDB存储引擎的默认读取方式。

如果读取的行正在执行Delete或者Update操作,那么这时候读取操作不会因此去等待锁的释放,而是去读取该行的一个快照数据。 因为不需要等待访问的行上的X锁的释放,也因此叫非读定锁。

快照数据指的是该行的之前版本的数据(通过undo log来实现),一行记录可能有多个快照数据,一般称这种技术为行多版本技术,而由此带来的并发控制,称之为多版本并发控制,也就是我们听烂了的MVCC!

虽然一致性非读定锁作为默认的读取方式,但是在不同的事务隔离级别下,读取方式还是不一样的。事务隔离级别在重复读读提交两个级别下,使用的是一致性非读定锁。并且这两个级别下的快照数据定义也不一样:

  • Read Committed(读提交):非一致性读总是读取被锁定行的最新一份快照数据。
  • Repeatable Read(重复读):总是读取事务开始时的行数据版本。

2.1.3 一致性读定锁

默认配置下,即事务的隔离级别为重复读模式下,InnoDB存储引擎的Select操作使用一致性非锁定读,但是某些情况下,用户需要显式地对数据库读取操作进行加锁以保证数据逻辑的一致性。 因此InnoDB存储引擎对于Select语句支持了两种一致性的锁定读操作:

  • Select ··· For Update:对读取的行记录加一个X锁,则其他事务不能对已经锁定的行加上任何锁。
  • Select ··· Lock In Share Mode:对读取的行记录加一个S锁,其他事务则只能加S锁,若加X锁,则被堵塞。

2.1.4 自增长和锁

在InnoDB存储引擎的内存结构中,对每个含有自增长值的表都有一个自增长计数器(auto-increment counter) ,当对含有自增长计数器的表进行插入操作时,这个计数器会被初始化。

插入操作会依据这个自增长的计数器值来加1进行赋值,这个实现方式叫做AUTO-INC Locking。

其实这是一种特殊的表锁机制,为了提高插入的性能,锁不是在一个事务完成后才释放的,而是在完成对自增长值插入的SQL语句后立即释放

从Mysql5.1.22开始,InnoDB存储引擎提供了一种轻量级互斥量的自增长实现机制,该版本提供了参数innodb_autoinc_lock_mode来控制自增长的模式,默认值为1。

先来看下自增长的插入类型:
在这里插入图片描述
再来看下不同参数下的自增影响:
在这里插入图片描述
此外,在InnoDB存储引擎中,自增长值的列必须是索引,同时也必须是索引的第一个列

2.1.5 外键和锁

外键主要是用于引用完整性的约束检查。 并且,如果对于一个外键列,如果没有显式地对这个列加索引,InnoDB会自动对其加一个索引,从而避免表锁。

对于外键值的插入或者更新操作:

1.首先查询父表中的记录,select。
2.select操作通过select xxx lock in share mode的方式。
3.即主动对父表加一个S锁。

试想下,为什么外键值的插入或者更新不是使用一致性非锁定读的方式呢?
举个例子:(一致性读锁定)
在这里插入图片描述
若是一致性非读锁定,那么此时会话B会对父表中id为3的记录,是可以进行插入操作的,但如果A的事务提交,那么对于A来说,id为3的记录是删除的,对于B来说是存在的,那么就出现了不一致的情况。因此需要一致性读锁定。

2.2 锁的算法

InnoDB存储引擎共有3种行锁的算法,分别是:

  • Record Lock:单个行记录上的锁。
  • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身。
  • Next-Key Lock:Record Lock+Gap Lock,锁定一个范围,并且锁定记录本身。

Record Lock总是会去锁住索引记录,如果InnoDB存储引擎表在简历的时候没有设置任何一个索引,那么他会使用隐式的主键来进行锁定。

Next-Key Lock算法下,例如一个索引有10,11,13,20这4个值,那么这个索引可能被Next-Key Locking的区间为:
在这里插入图片描述

当然,既然有Next-Key Lock,也存在一个Previous-Key Lock。如果采用这种锁定技术,同样的上述索引,可锁定的区间为:
在这里插入图片描述

为什么要采用这个锁技术?其目的是为了解决Phantom Problem

Phantom Problem即幻像问题,也就是所谓的幻读是指在同一个事务下,连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL语句可能会返回之前不存在的行。
举个例子:

在这里插入图片描述在这里插入图片描述
可以看到,会话A执行第二次SQL的时候,会把会话B插入的语句返回给结果

那么Next-Key Locking算法怎么避免幻读?
额、

对于上述的SQL语句Select * from t where a > 2 for update; 锁住的不是5这个单个值,而是对(2,+∞)这个范围都加了X锁,因此对于这个范围的任何插入都是不被允许的,从而避免了幻读的产生。

InnoDB存储引擎默认的事务隔离级别是重复读,在该隔离级别下,采用的是Next-Key Lock。而在事务隔离级别为提交读的情况下,仅仅采用Record Lock。

注意:对于唯一键值的锁定,Next-Key Lock会降级为Record Lock,仅存在于查询所有的唯一索引列。

2.3 锁问题

1.脏读:在不同的事务下,当前事务可以读到另外事务未提交的数据。
案例:
在这里插入图片描述
2.不可重复读:在一个事务内多次读取同一个数据集合,而第二次读取的数据和第一次读取的数据不是一样的。
案例:
在这里插入图片描述
这里需要做一个区分(因为两个例子很像):

  • 脏读是读到未提交的数据。
  • 不可重复读读到的是已经提交的数据。

3.丢失更新
丢失更新简单来说就是一个事务的更新操作会被另一个事务的更新操作所覆盖。 ,从而导致数据的不一致。

  1. 事务T1将记录m更新为n1,但是事务T1没有提交。
  2. 与此同时,事务T2将记录m更新为n2,事务T2没有提交。
  3. 事务T1提交。
  4. 事务T2提交。

而为了避免这种丢失更新发生,需要让事务在这种情况下的操作变成串行化,而不是并行的操作。对于上述操作,可以给步骤1和2分别加一个X锁,那么事务T2就会进入堵塞。

2.4 死锁

死锁是指两个以及以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。

解决死锁的方式:

  • time out:即超时机制,当两个事务互相等待时,当一个等待时间超过innodb_lock_wait_timeout设置的某一个阈值时,其中一个事务进行回滚,另一个等待的事务就能够继续进行。

超时机制的优点:简单。
缺点:根据FIFO的顺序选择回滚对象,如果超时的事务所占的权重比较大(比如事务操作更新了很多行),那么此时占用的undo log比较大,若此事进行回滚,那么回滚的时间相比另一个事务所占用的时间可能要多很多。即不合理。

  • wait-for graph:等待图(InnoDB也采用这种方式)

wait-for graph要求数据库保存两种信息:

  • 锁的信息链表。
  • 事务等待链表。

其中,wait-for graph中,事务为图的节点。并且事务T1指向T2边的定义为:

  • 事务T1等待事务T2所占用的资源。
  • 事务T1最终等待T2所占用的资源。也就是事务之间在等待相同的资源,而事务T1发生在事务T2的后面。

事务发生死锁的概率与以下几点因素有关:

  • 系统中事务的数量(n),n越多发生死锁的概率越大。
  • 每个事务操作的数量(r),r越多发生思索的概率越大。
  • 操作数据的集合(R),越小则发生死锁的概率越大。

题外话,操作系统发生死锁的条件:

  • 互斥条件:某一个资源在一段时间内只能由一个进程占有,不能被两个及以上进程占有。
  • 不可抢占条件:进程所占有的资源在使用完毕之前,不会被其他的资源申请者占有,除非该资源自行释放。
  • 占有且申请条件:进程至少占有一个资源,但是又申请新的资源。
  • 循环等待条件:存在一个进程等待序列{P1,P2,…Pn},P1等待P2的资源,P2等待P3的资源…Pn等待P1的资源。即形成环。

2.5 锁升级

锁升级是指将当前锁的粒度降低。 如把一个表的1000个行锁升级为一个页锁。 但是InnoDB存储引擎不存在锁升级的问题,因为他并不是根据每个记录来产生行锁的,相反,它是根据每个事务访问的每个页对锁进行管理的,采用的位图的方式。

因此不管一个事务锁住页中一个记录还是多个记录,其开销都是一致的。

三.总结

最后总结一些事情:

  1. 首先,InnoDB是一种存储引擎,不是索引,不是索引,不是索引。
  2. 其次,InnoDB存储引擎下,包含两种索引类型,一个是BTREE(就是B+树),一个是HASH。
  3. 4个事务隔离级别:读未提交,读提交,重复读,串行化。
  4. InnoDB是行级锁:包括共享锁排他锁,MyISAM是表级锁。
  5. 3种锁问题:脏读、不可重复读(切忌不要和事务隔离级别中的重复读搞串了)、幻读。
  6. 3种锁算法:Record LockGap LockNext-Key Lock解决幻读)。
  7. 锁类型:一致性非读定锁(MVCC)、一致性读定锁(悲观锁,for update)、自增长锁(特殊的锁机制,锁不是在一个事务完成后才释放的,而是在完成对自增长值插入的SQL语句后立即释放)、外键锁(插入操作时,对父表进行lock in share mode操作)

猜你喜欢

转载自blog.csdn.net/Zong_0915/article/details/111060386