MySQL InnoDB引擎索引长度受限怎么办

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL InnoDB引擎索引长度受限怎么办相关的知识,希望对你有一定的参考价值。

mysql 5.7 开始,开发人员改变了 InnoDB 构建二级索引的方式,采用自下而上的方法,而不是早期版本中自上而下的方法了。在这篇文章中,我们将通过一个示例来说明如何构建 InnoDB 索引。最后,我将解释如何通过为 innodb_fill_factor 设置更合适的值。

索引构建过程

在有数据的表上构建索引,InnoDB 中有以下几个阶段:1.读取阶段(从聚簇索引读取并构建二级索引条目)2.合并排序阶段3.插入阶段(将排序记录插入二级索引)在 5.6 版本之前,MySQL 通过一次插入一条记录来构建二级索引。这是一种“自上而下”的方法。搜索插入位置从树的根部(顶部)开始并达到叶页(底部)。该记录插入光标指向的叶页上。在查找插入位置和进行业面拆分和合并方面开销很大。从MySQL 5.7开始,添加索引期间的插入阶段使用“排序索引构建”,也称为“批量索引加载”。在这种方法中,索引是“自下而上”构建的。即叶页(底部)首先构建,然后非叶级别直到根(顶部)。

示例

在这些情况下使用排序的索引构建:

    ALTER TABLE t1 ADD INDEX(or CREATE INDEX)

    ALTER TABLE t1 ADD FULLTEXT INDEX

    ALTER TABLE t1 ADD COLUMN, ALGORITHM = INPLACE

    OPIMIZE t1

    对于最后两个用例,ALTER 会创建一个中间表。中间表索引(主要和次要)使用“排序索引构建”构建。

    算法

    在 0 级别创建页,还要为此页创建一个游标

    使用 0 级别处的游标插入页面,直到填满

    页面填满后,创建一个兄弟页(不要插入到兄弟页)

    为当前的整页创建节点指针(子页中的最小键,子页码),并将节点指针插入上一级(父页)

    在较高级别,检查游标是否已定位。如果没有,请为该级别创建父页和游标

    在父页插入节点指针

    如果父页已填满,请重复步骤 3, 4, 5, 6

    现在插入兄弟页并使游标指向兄弟页

    在所有插入的末尾,每个级别的游标指向最右边的页。提交所有游标(意味着提交修改页面的迷你事务,释放所有锁存器)

    为简单起见,上述算法跳过了有关压缩页和 BLOB(外部存储的 BLOB)处理的细节。

    通过自下而上的方式构建索引

    为简单起见,假设子页和非子页中允许的 最大记录数为 3

    CREATE TABLE t1 (a INT PRIMARY KEY, b INT, c BLOB);

    INSERT INTO t1 VALUES (1, 11, 'hello111');

    INSERT INTO t1 VALUES (2, 22, 'hello222');

    INSERT INTO t1 VALUES (3, 33, 'hello333');

    INSERT INTO t1 VALUES (4, 44, 'hello444');

    INSERT INTO t1 VALUES (5, 55, 'hello555');

    INSERT INTO t1 VALUES (6, 66, 'hello666');

    INSERT INTO t1 VALUES (7, 77, 'hello777');

    INSERT INTO t1 VALUES (8, 88, 'hello888');

    INSERT INTO t1 VALUES (9, 99, 'hello999');

    INSERT INTO t1 VALUES (10, 1010, 'hello101010');

    ALTER TABLE t1 ADD INDEX k1(b);

    InnoDB 将主键字段追加到二级索引。二级索引 k1 的记录格式为(b, a)。在排序阶段完成后,记录为:

    (11,1), (22,2), (33,3), (44,4), (55,5), (66,6), (77,7), (88,8), (99,9), (1010, 10)

    初始插入阶段

    让我们从记录 (11,1) 开始。

    在 0 级别(叶级别)创建页

    创建一个到页的游标

    所有插入都将转到此页面,直到它填满了

    箭头显示游标当前指向的位置。它目前位于第 5 页,下一个插入将转到此页面。

    还有两个空闲插槽,因此插入记录 (22,2) 和 (33,3) 非常简单

    对于下一条记录 (44,4),页码 5 已满(前面提到的假设最大记录数为 3)。这就是步骤。

    页填充时的索引构建

    创建一个兄弟页,页码 6

    不要插入兄弟页

    在游标处提交页面,即迷你事务提交,释放锁存器等

    作为提交的一部分,创建节点指针并将其插入到 【当前级别 + 1】 的父页面中(即在 1 级别)

    节点指针的格式 (子页面中的最小键,子页码) 。第 5 页的最小键是 (11,1) 。在父级别插入记录 ((11,1),5)。

    1 级别的父页尚不存在,MySQL 创建页码 7 和指向页码 7 的游标。

    将 ((11,1),5) 插入第 7 页

    现在,返回到 0 级并创建从第 5 页到第 6 页的链接,反之亦然

    0 级别的游标现在指向兄弟页,页码为 6

    将 (44,4) 插入第 6 页

    下一个插入 - (55,5) 和 (66,6) - 很简单,它们转到第 6 页。

    插入记录 (77,7) 类似于 (44,4),除了父页面 (页面编号 7) 已经存在并且它有两个以上记录的空间。首先将节点指针 ((44,4),8) 插入第 7 页,然后将 (77,7) 记录到同级 8 页中。

    插入记录 (88,8) 和 (99,9) 很简单,因为第 8 页有两个空闲插槽。

    下一个插入 (1010,10) 。将节点指针 ((77,7),8) 插入 1级别的父页(页码 7)。

    MySQL 在 0 级创建同级页码 9。将记录 (1010,10) 插入第 9 页并将光标更改为此页面。

    以此类推。在上面的示例中,数据库在 0 级别提交到第 9 页,在 1 级别提交到第 7 页。

    我们现在有了一个完整的 B+-tree 索引,它是自下至上构建的!

    索引填充因子

    全局变量 innodb_fill_factor 用于设置插入 B-tree 页中的空间量。默认值为 100,表示使用整个业面(不包括页眉)。聚簇索引具有 innodb_fill_factor=100 的免除项。 在这种情况下,聚簇索引也空间的 1 /16 保持空闲。即 6.25% 的空间用于未来的 DML。

    值 80 意味着 MySQL 使用了 80% 的页空间填充,预留 20% 于未来的更新。如果 innodb_fill_factor=100 则没有剩余空间供未来插入二级索引。如果在添加索引后,期望表上有更多的 DML,则可能导致业面拆分并再次合并。在这种情况下,建议使用 80-90 之间的值。此变量还会影响使用 OPTIMIZE TABLE 和 ALTER TABLE DROP COLUMN, ALGOITHM=INPLACE 重新创建的索引。也不应该设置太低的值,例如低于 50。因为索引会占用浪费更多的磁盘空间,值较低时,索引中的页数较多,索引统计信息的采样可能不是最佳的。优化器可以选择具有次优统计信息的错误查询计划。

    排序索引构建的优点

    没有页面拆分(不包括压缩表)和合并

    没有重复搜索插入位置

    插入不会被重做记录(页分配除外),因此重做日志子系统的压力较小

    缺点

    ALTER 正在进行时,插入性能降低 Bug#82940,但在后续版本中计划修复。

    请点击输入图片描述

参考技术A MySQL InnoDB引擎索引长度受限怎么办
解决办法是在建索引时限制索引prefix的大小:
例如:create index yarn_app_result_i4 on yarn_app_result (flow_exec_id(100), another_column(50));
这样,在创建索引时就会限制使用的每个列的最大长度。如上的例子中,在创建联合索引时,最多使用列flow_exec_id中前100个字符创建索引,最多使用another_column中前
50个字符创建索引。这样子,就可以避免索引长度过大的问题。本回答被提问者采纳

MySQL innodb引擎深入讲解

参考技术A

表空间(ibd文件),一个MySQL实例可以对应多个表空间,用于存储记录,索引等数据。

段,分为数据段、索引段、回滚段,innodb是索引组织表,数据段就是B+Tree的叶子节点,索引段为非叶子节点,段用来管理多个区。

区,表空间的单元结构,每个区的大小为1M,默认情况下,innodb存储引擎页大小为16K,即一个区中一共有64个连续的页。

页,是innodb存储引擎磁盘管理的最小单元,每个页的大小为16K,为了保证页的连续性,innodb存储引擎每次从磁盘申请4~5个区。

行,innodb存储引擎数据是按行进行存储的。Trx_id 最后一次事务操作的id、roll_pointer滚动指针。

i nnodb的内存结构 ,由Buffer Pool、Change Buffer和Log Buffer组成。

Buffer Pool : 缓冲池是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池么有数据,则从磁盘加载并缓存),然后再以一定频率刷新磁盘,从而减少磁盘IO,加快处理速度。

缓冲池以page页为单位,底层采用链表数据结构管理page,根据状态,将page分为三种类型:

1、free page 即空闲page,未被使用。

2、clean page 被使用page,数据没有被修改过。

3、dirty page 脏页,被使用page,数据被修改过,这个page当中的数据和磁盘当中的数据 不一致。说得简单点就是缓冲池中的数据改了,磁盘中的没改,因为还没刷写到磁盘。

Change Buffer :更改缓冲区(针对于非唯一二级索引页),在执行DML语句时,如果这些数据page没有在Buffer Pool中,不会直接操作磁盘,而会将数据变更存在更改缓冲区Change Buffer中,在未来数据被读取时。再将数据合并恢复到Buffer Pool中,再将合并后的数据刷新到磁盘中。

二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引页,同样,删除和更新可能会影响索引树中不相邻的二级索引页。如果每一次都操作磁盘,会造成大量磁盘IO,有了Change Buffer之后,我们可以在缓冲池中进行合并处理,减少磁盘IO。

Adaptive Hash Index: 自适应hash索引,用于优化对Buffer Pool数据的查询,InnoDB存储引擎会监控对表上各索引页的查询,如果观察到hash索引可以提升速度,则建立hash索引,称之为自适应hash索引。无需人工干预,系统根据情况自动完成。

参数:innodb_adaptive_hash_index

Log Buffer: 日志缓冲区,用来保存要写入到磁盘中的log日志数据(redo log、undo log),默认大小为16M,日志缓冲区的日志会定期刷新到磁盘中,如果需要更新,插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘IO。

参数: innodb_log_buffer_size 缓冲区大小

innodb_flush_log_at_trx_commit 日志刷新到磁盘时机

innodb_flush_log_at_trx_commit=1 表示日志在每次事务提交时写入并刷新到磁盘

2 表示日志在每次事务提交后写入,并每秒刷新到磁盘一次

0 表示每秒将日志写入并刷新到磁盘一次。

InnoDB 的磁盘结构,由系统表空间(ibdata1),独立表空间(*.ibd),通用表空间,撤销表空间(undo tablespaces), 临时表空间(Temporary Tablespaces), 双写缓冲区(Doublewrite Buffer files), 重做日志(Redo Log).

系统表空间(ibdata1): 系统表空间是更改缓冲区的存储区域,如果表是在系统表空间而不是每个表文件或者通用表空间中创建的,它也可能包含表和索引数据。

参数为: innodb_data_file_path

独立表空间(*.ibd): 每个表的文件表空间包含单个innodb表的数据和索引,并存储在文件系 统上的单个数据文件中。 参数: innodb_file_per_table

通用表空间: 需要通过create tablespace 语法创建,创建表时 可以指定该表空间。

create tablespace xxx add datafile \'file_name\' engine=engine_name

create table table_name .... tablespace xxx

撤销表空间(undo tablespaces): MySQL实例在初始化时会自动创建两个默认的undo表空间(初始大小16K,undo_001,undo_002),用于存储undo log 日志

临时表空间(Temporary Tablespaces): innodb使用会话临时表空和全局表空间,存储用 户创建的临时表等数据。

双写缓冲区(Doublewrite Buffer files): innodb引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入缓冲区文件中,便于系统异常时恢复数据。

重做日志(Redo Log): 是用来实现事务的持久性,该日志文件由两部分组成,重做日志缓冲区(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中,当事务提交之后会把修改信息都会存储到该日志中,用于在刷新脏页到磁盘时,发送错误时,进行数据恢复使用。以循环方式写入重做日志文件,涉及两个文件ib_logfile0,ib_logfile1。

那内存结构中的数据是如何刷新到磁盘中的? 在MySQL中有4个线程负责刷新日志到磁盘。

1、Master Thread, mysql核心后台线程,负责调度其它线程,还负责将缓冲池中的数据异 步刷新到磁盘中,保持数据的一致性,还包括脏页的刷新,合并插入缓冲、undo页的回 收。

2、IO Thread,在innodb存储引擎中大量使用了AIO来处理IO请求,这样可以极大地提高数 据库的性能,而IO Thead主要负责这些IO请求的回调。

4个读线程 Read thread负责读操作

4个写线程write thread负责写操作

1个Log thread线程 负责将日志缓冲区刷新到磁盘

1个insert buffer线程 负责将写入缓冲区内容刷新到磁盘

3、Purge Thread,主要用于回收事务已经提交了的undo log,在事务提交之后,undo log 可能不用了,就用它来回收。

4、Page Cleaner Thread, 协助Master Thread 刷新脏页到磁盘的线程,它可以减轻主线程 的压力,减少阻塞。

事务就是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失效。

事务的4大特性分为:

如何保证事务的4大特性,原子性,一致性和持久性是由innodb存储引擎底层的两份日志来保证的,分别是redo log和undo log。对于隔离性是由锁机制和MVCC(多版本并发控制)来实现的。

redo log,称为重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。该日志文件由两部分组成: 重做日志缓冲redo log buffer及重做日志文件redo log file,前者是在内存中,后者是在磁盘中,当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发送错误时,进行数据的恢复使用,从而保证事务的持久性。

具体的操作流程是:

1、客户端发起事务操作,包含多条DML语句。首先去innodb中的buffer pool中的数据页去查找有没有我们要更新的这些数据,如果没有则通过后台线程从磁盘中加载到buffer pool对应的数据页中,然后就可以在缓冲池中进行数据操作了。

2、此时缓冲池中的数据页发生了变更,还没刷写到磁盘,这个数据页称为脏页。脏页不是实时刷新到磁盘的,而是根据你配置的刷写策略进行刷写到磁盘的(innodb_flush_log_at_trx_commit,0,1,2三个值)。如果脏页在往磁盘刷新的时候出现了故障,会丢失数据,导致事务的持久性得不到保证。为了避免这种现象,当对缓冲池中的数据进行增删改操作时,会把增删改记录到redo log buffer当中,redo log buffer会把数据页的物理变更持久化到磁盘文件中(ib_logfile0/ib_logfile1)。如果脏页刷新失败,就可以通过这两个日志文件进行恢复。

undo log,它是用来解决事务的原子性的,也称为回滚日志。用于记录数据被修改前的信息,作用包括:提供回滚和MVCC多版本并发控制。

undo log和redo log的记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,当update一条记录时,它记录一条对应相反的update记录,当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。

undo log销毁: undo log 在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日子可能用于MVCC。

undo log存储: undo log 采用段的方式进行管理和记录,存放在前面介绍的rollback segment回滚段中,内部包含1024个undo log segment。

mvcc(multi-Version Concurrency Control),多版本并发控制,指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能,MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段,undo log日志、readView。

read committed 每次select 都生成一个快照读

repeatable read 开启事务后第一个select语句才是快照读的地方

serializable 快照读会退化为当前读。

mvcc的实现原理

DB_TRX_ID: 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID

DB_ROLL_PTR: 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个 版本

DB_ROW_ID: 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。

m_ids当前活跃的事务ID集合

min_trx_id: 最小活跃事务id

max_trx_id: 预分配事务ID,当前最大事务id+1,因为事务id是自增的

creator_trx_id: ReadView创建者的事务ID

版本链数据访问规则:

trx_id: 表示当前的事务ID

1、trx_id == creator_trx_id? 可以访问读版本-->成立的话,说明数据是当前这个事务更改的

2、trx_id 成立,说明数据已经提交了。

3、trx_id>max_trx_id?不可用访问读版本-> 成立的话,说明该事务是在ReadView生成后才开启的。

4、min_trx_id

以上是关于MySQL InnoDB引擎索引长度受限怎么办的主要内容,如果未能解决你的问题,请参考以下文章

MySQL InnoDB引擎索引长度受限怎么办

mysql数据库引擎innodb的主索引文件和表文件分开吗?如果不分开,那怎么查询?

Mysql Innodb存储引擎 select count 太慢,怎么优化

mysql里的range分区方式和主键冲突了怎么办?innodb中没有主键会造成啥影响?

怎么理解 MySQL 常见的两种存储引擎:MyISAM与InnoDB?

mysql 开发基础系列15 索引的设计和使用