Files
rikako-note/mysql/mysql文档/mysql_索引.md
2025-02-19 20:47:10 +08:00

596 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

- [innodb索引与算法](#innodb索引与算法)
- [索引算法](#索引算法)
- [二分查找](#二分查找)
- [B+树索引](#b树索引)
- [聚簇索引](#聚簇索引)
- [非数据页](#非数据页)
- [辅助索引](#辅助索引)
- [辅助索引查找](#辅助索引查找)
- [辅助索引查找分析](#辅助索引查找分析)
- [索引创建](#索引创建)
- [algorithm](#algorithm)
- [lock](#lock)
- [online DDL](#online-ddl)
- [Cardinality](#cardinality)
- [Cardinality](#cardinality-1)
- [innodb存储引擎的Cardinality](#innodb存储引擎的cardinality)
- [Cardinality采样](#cardinality采样)
- [索引使用](#索引使用)
- [联合索引](#联合索引)
- [覆盖索引](#覆盖索引)
- [覆盖索引对于统计场景的好处](#覆盖索引对于统计场景的好处)
- [优化器选择不使用索引](#优化器选择不使用索引)
- [辅助索引不能覆盖](#辅助索引不能覆盖)
- [索引提示](#索引提示)
- [Use index](#use-index)
- [Multi-Range Read](#multi-range-read)
- [multi-range read优势](#multi-range-read优势)
- [范围查询和join查询优化](#范围查询和join查询优化)
- [拆分键值对](#拆分键值对)
- [未开启MRR](#未开启mrr)
- [开启MRR优化](#开启mrr优化)
- [mrr控制](#mrr控制)
- [read\_rnd\_buffer\_size](#read_rnd_buffer_size)
- [Index Condition Pushdown(ICP)](#index-condition-pushdownicp)
- [关闭ICP](#关闭icp)
- [开启ICP](#开启icp)
- [innodb hash](#innodb-hash)
- [innodb中的哈希](#innodb中的哈希)
- [自适应hash索引](#自适应hash索引)
- [全文检索](#全文检索)
- [倒排索引](#倒排索引)
- [inverted file index](#inverted-file-index)
- [full inverted index](#full-inverted-index)
- [innodb全文检索](#innodb全文检索)
- [辅助表](#辅助表)
- [FTS Index Cache (全文检索索引缓存)](#fts-index-cache-全文检索索引缓存)
- [查看指定倒排索引的辅助表分词信息](#查看指定倒排索引的辅助表分词信息)
- [FTS Index Cache更新和写盘时机](#fts-index-cache更新和写盘时机)
- [分词删除](#分词删除)
- [OPTIMIZE TABLE](#optimize-table)
- [innodb\_ft\_cache\_size](#innodb_ft_cache_size)
- [FTS Document id](#fts-document-id)
- [innodb全文检索示例](#innodb全文检索示例)
- [innodb full-text design](#innodb-full-text-design)
- [innodb full-text index tables](#innodb-full-text-index-tables)
- [辅助索引表](#辅助索引表)
- [table\_id hex](#table_id-hex)
- [index\_id hex](#index_id-hex)
# innodb索引与算法
innodb存储引擎支持如下集中常见的索引
- B+树索引
- 对于B+树索引,其只能找到被查找数据所在的页,然后数据库把页读入到内存中,再在内存中进行查找
- 全文索引
- 哈希索引
- innodb中哈希索引是自适应的innodb存储引擎会根据目前表的使用情况自动生成哈希索引
## 索引算法
### 二分查找
如前所示mysql数据页中存在page directory而page directory中slots是按照主键顺序来进行排放的。
`对于数据的查找是通过对page directory中的槽进行二分查找得到的`
## B+树索引
inoodb中索引大多是B+树的实现,`且具有高扇出的特性`。故而,在数据库中,`B+树的高度一般都在2~4层即在查找某一行记录时最多只需要2~4次IO。`
innodb中B+树索引可以分为聚簇索引和辅助索引其都是基于B+树实现,`所有的数据都存放在叶子节点中`
> 聚簇索引和辅助索引的区别在于叶子节点中是否存放的是整行的记录信息。
### 聚簇索引
在innodb中表是由索引组织的即表中的数据按照主键的顺序存放。
聚簇索引是按照每张表的主键构建一颗B+树,而叶子节点存放的则是整张表的行记录数据,`故而,聚簇索引的叶子节点也被称为数据页`
> 和B+树一样,每个数据页(叶子节点)都通过双向链表来进行链接。
> `每张表只能拥有一个聚簇索引`,并且,在多数情况下,查询优化器倾向于采用聚簇索引。`因为聚簇索引能够直接在叶子节点获取到行记录。`
#### 非数据页
在非数据页中存放的是key以及指向数据页的页号。
聚簇索引的存储并非是物理上连续的,`而是通过双向链表来维护逻辑上的顺序`
> 数据页与数据页之间通过双向链表来维护顺序排序,而位于同一个页中的记录之间也是通过双向链表来维护排序的。
对于聚簇索引,其`按照主键排序进行查找``按主键进行范围查找`时,查询速度很快。
### 辅助索引
对于辅助索引叶子节点并不包含行记录的全部数据。在辅助索引的叶子节点中叶子节点除了包含辅助索引的key外`每个叶子节点中的索引行还包含一个书签bookmark`。bookmark可以告知inodb存储引擎哪里能找到和索引相对应的行数据。
> 实际上innodb存储引擎的辅助索引其bookmark就是相应行数据的聚簇索引key`。
辅助索引并不会影响行数据在聚簇索引上的组织,故而每张表可以含有一个聚簇索引和多个辅助索引。
#### 辅助索引查找
当通过辅助索引来查找数据时innodb会遍历辅助索引的B+树数据结构,并从叶子节点中获取指向的主键索引值,之后再通过主键索引来查找到完整的行记录。
#### 辅助索引查找分析
如果聚簇索引和辅助索引的B+树高度同样为3那么当根据辅助索引来查找关联的主键时需要花费3次IO。此时再根据主键来从聚簇索引中获取对应行数据需要另外3次IO。
故而根据辅助索引来获取行记录大概需要6次IO。
### 索引创建
通过alter table语法可以选择索引的创建方式
```sql
alter table tbl_name
| add {index|key} [index_name]
[index_type] (index_col_name, ...)
algorithm [=] {default|inplace|copy}
lock [=] {default|none|shared|exclusive}
```
#### algorithm
通过`algorithm`可以指定创建或删除索引的算法:
- copy通过创建临时表的方式定义新的表结构并将原表中的数据移动到新的表最后删除原表并将临时表重命名
- 该算法将会耗费大量事件,并且在执行时表不可被访问
- inplace索引创建和删除操作不采用临时表
- default: 按照`old_alter_table`参数来判断是通过inplace还是copy来创建或删除索引默认`old_alter_table`为off即是采用inplace算法
#### lock
lock部分代表索引创建或删除时对表的加锁情况
- none执行索引的创建或删除操作时对目标表不添加任何的锁在执行过程中事务仍然能够进行读写操作不会阻塞。该加锁情况能够获取最大程度的并发度
- share在执行过程中对表添加s锁`s锁会和x锁以及ix锁发生冲突但是能够兼容s锁和is锁`
- 故而在加锁方式为share的场景下执行索引的创建或删除时其他事务仍然能够执行读取操作但是写操作则是会被阻塞。
- 如果存储引擎不支持share模式会返回相应的错误信息
- exclusive在exclusive模式下执行索引的创建和删除操作时会对目标表添加一个x锁此时其他事务对该表的读和写操作都不会被执行
- default
- default模式下首先会判断当前操作能否使用none
- 如果不能则判断是否能够使用shared
- 如果仍然不能那么最后会判断能否使用exclusive
#### online DDL
innodb存储引擎实现online DDL的原理是在执行索引的创建或删除操作时`insert, update, delete`这些DML写操作的日志写入到一个缓存中等到索引创建完成后再将这些DML语句重做到表上日志内容类似于redo log
该缓存的大小可以通过`innodb_online_alter_log_max_size`来进行控制,默认大小为`128M`
如果再执行索引创建和更新的表比较大,并且在`online alter`执行过程中存在大量的写事务,那么当`innodb_online_alter_log_max_size`指定的空间不足以存放时,会抛出错误。
如果发生上述错误,可以考虑增加`inodb_online_alter_log_max_size`参数的值;也可以设置`alter table``lock`模式为`share`那么在执行过程中不会有写事务的发生不需要进行DML日志的记录。
### Cardinality
并不是所有的字段都适合创建B+树索引。例如对于性别字段、类型字段等它们的选择范围都很小这种字段不适合创建B+树索引。
> 性别字段中只有`M`和`F`两种可选值,故而执行`select * from student where sex = 'F'`这类sql可能会得到表中50%的数据这种情况下为性别字段添加B+树索引完全没有必要。
而对于`bigint`类型的id字段其选择范围很广且没有重复这类字段适合使用B+树索引。
#### Cardinality
通过`show index`语句可以查看索引是否拥有高选择性,`Cardinality`列代表索引中`不重复记录数量的预估值`
`Cardinality`是一个预估值,并不是准确的值,且`Cardinality / rows_in_table`的值应该尽可能的接近1。如果该值非常小那么用户应考虑该索引的创建是否有必要。
#### innodb存储引擎的Cardinality
在生产环境下索引的操作可能十分频繁如果每次操作都对cardinality进行统计那么将会带来巨大的开销。`故而数据库针对Cardinality的统计都是通过采样来进行的`
在innodb中针对cardinality统计信息的更新发生在两个操作中`insert, update`。innodb针对cardinality的更新策略为
- 表中1/16的数据已经发生变化
- 自上一次统计cardinality信息时起表中1/16的数据已经发生过变化此时需要重新统计cardinality
- stat_modified_counter >= 2000,000,000
- 如果针对表中某一行数据进行频繁的更新那么该场景不会适配第一种更新策略故而在innodb内部维护了一个计数器stat_modified_counter用于标识发生变化的次数当计数器的值大于`2000,000,000`也需要更新cardinality
#### Cardinality采样
默认情况下innodb会针对8个叶子节点进行采样采样过程如下
- 取得B+树索引中叶子节点的数量记为A
- 随机获取索引8个叶子节点统计每个页不同记录的个数记为P1, P2, ... , P8
- 根据采样信息给出Cardinality的预估值`(P1 + P2 + ... + P8) * A/8`
## 索引使用
### 联合索引
联合索引代表对表上的多个列进行索引。
示例如下:
```sql
create table t (
id bigint not null auto_increment,
a int not null,
b int not null,
primary key(id),
index idx_a_b (a, b)
);
```
对于上述联合索引`(a,b)`,各查询语句使用索引的情况如下:
- `where a = xxx and b = xxx`
- 该语句会使用联合索引
- `where a = xxx`
- 该语句同样可以使用联合索引
- `where b = xxx`:
- 该语句无法使用联合索引
### 覆盖索引
innodb存储引擎支持覆盖索引即通过辅助索引即可得到查询记录无需再次查询聚簇索引。
> 通常来说,辅助索引中叶子节点的记录大小要远小于聚簇索引中叶子节点的记录大小。故而,在范围统计的场景下(`select count(1)`),辅助索引叶子节点中,一个页包含更多的记录,表扫描时需要读取的页数页更少。
>
> 故而在部分场景下使用覆盖索引能够节省大量的io操作。
对于辅助索引而言,其叶子节点的记录中包含`(primary key1, primary key2, ..., key1, key2)`等信息,故而,如下语句都可以通过`一次辅助联合索引查询来完成`:
- `select key2 from table where key1 = xxx`
- 会命中辅助索引且辅助索引中包含key2的值故而无需再次查询聚簇索引
- `select primary key2,key2 from table where key1 = xxx`
- `select primary key1,key2 from table where key1 = xxx`
- `select primary key1, primary key2, key2 from key1 = xxx`
#### 覆盖索引对于统计场景的好处
例如对于表buy_log
```sql
create table buy_log (
userid int unsigned not null,
buy_date date,
index `idx_userid` (userid),
index `idx_userid_buy_date` (userid, buy_date)
);
```
预置如下数据:
```sql
insert into buy_log(userid, buy_date)
values (1, '2022-01-01'),
(2,'2022-03-04'),
(3, '2024-12-31'),
(4, '2024-07-11'),
(5, '2025-01-01');
```
执行`explain select count(*) from buy_log;`语句时由于where条件为空其并不会命中索引但是语句执行的结果如下所示
| id | select\_type | table | partitions | type | possible\_keys | key | key\_len | ref | rows | filtered | Extra |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 1 | SIMPLE | buy\_log | null | index | null | idx\_userid | 4 | null | 5 | 100 | Using index |
上述返回结果中,`possible_keys`列为null但是实际执行时优化器却选择了`idx_userid`索引Extra列值为`Using index`,代表优化器选择了覆盖索引。
> 由于覆盖索引中记录大小更小故而使用覆盖索引能够节省io提升性能。
并且,对于`explain select count(*) from buy_log where buy_date >= '2023-01-01' and buy_date <= '2024-01-01';`语句,其返回结果如下
| id | select\_type | table | partitions | type | possible\_keys | key | key\_len | ref | rows | filtered | Extra |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 1 | SIMPLE | buy\_log | null | index | idx\_userid\_buy\_date | idx\_userid\_buy\_date | 8 | null | 5 | 20 | Using where; Using index |
正常来说,`idx_userid_buy_date`索引组成为`(userid, buy_date)`,当根据`buy_date`来进行查找时,不应该命中索引。
但是,由于辅助索引中同一页包含记录更多,故而上述语句仍然使用了`idx_userid_buy_date`覆盖索引。
### 优化器选择不使用索引
在某些情况下当使用explain语句进行分析时会发现优化器没有选择索引去查找数据而是通过全盘扫描聚簇索引。这种情况通常发生在范围查找join连接等操作时。
#### 辅助索引不能覆盖
对于辅助索引不能进行覆盖的场景,优化器仅当`查询少量数据`时选择辅助索引。
优化器不选择辅助索引的原因是,`根据辅助索引再次查询聚簇索引时,会带来大量的随机读`。由于辅助索引和聚簇索引的排序不同在辅助索引中连续的数据很可能在聚簇索引中是分散的。如果使用辅助索引那么在根据主键查询聚簇索引时可能带来大量的随机读主键可能随机分布在各个数据页中这样会带来大量的io。
故而,`当辅助索引需要读取大量的记录,并且辅助索引不能覆盖时,优化器会倾向直接在聚簇索引中进行顺序查找。`顺序读通常要远快于随机读。
> 如果mysql server实例部署在固态硬盘上随机读取的速度很快同时确认使用辅助索引的性能更佳可以使用`force index`来强制使用某个索引,使用示例如下:
>
> ```sql
> select * from orderdetails force index (orderID) where orderid > 10000 and order id < 102000
> ```
### 索引提示
mysql支持索引提示如下两种场景可能会用到索引提示
- mysql错误的选择了某个索引导致sql语句运行较慢
- 某个sql可选择索引非常多此时优化器选择执行计划的耗时可能大于sql语句执行的耗时
mysql数据库中索引提示的语法如下
```sql
table_name [[as] alias] [index_hint_list]
-- 其中index_hint_list的含义如下
index_hint_list:
index_hint[, index_hint]...
-- index_hint的含义则如下
index_hint:
USE {INDEX|KEY}
[{FOR {JOIN | ORDER BY | GROUP BY}}] ([index_list])
| IGNORE {INDEX | KEY}
[{FOR {JOIN | ORDER BY | GROUP BY}}] (index_list)
| FORCE {INDEX|KEY}
[{FOR {JOIN | ORDER BY | GROUP BY}}] (index_list)
-- index_list含义如下
index_list:
index_name, [, index_name]...
```
创建`t_demo`示例如下:
```sql
create table t_demo (
a int not null,
b int not null,
key idx_a (a),
key idx_b (b)
);
insert into t_demo(a,b)
values
(1,1),
(1,2),
(2, 3),
(2, 4),
(1,2);
```
### Use index
执行如下语句`explain select * from t_demo where a=1 and b = 2;`,其返回执行计划如下:
| id | select\_type | table | partitions | type | possible\_keys | key | key\_len | ref | rows | filtered | Extra |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 1 | SIMPLE | t\_demo | null | index\_merge | idx\_a,idx\_b | idx\_b,idx\_a | 4,4 | null | 1 | 83.33 | Using intersect\(idx\_b,idx\_a\); Using where; Using index |
易知优化器选择了使用`idx_a``idx_b`两个索引来完成该查询且Extra列包含了`Using intersect(b,a)`,代表查询结果根据两个索引得到的结果进行求交集的数学运算。
可以通过`Use Index`来提示使用`idx_a`索引:
```sql
explain select * from t_demo use index (idx_a) where a=1 and b = 2;
```
得到结果如下:
| id | select\_type | table | partitions | type | possible\_keys | key | key\_len | ref | rows | filtered | Extra |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 1 | SIMPLE | t\_demo | null | ref | idx\_a | idx\_a | 4 | const | 3 | 20 | Using where |
上述结果显示执行方案选用了`idx_a`
> 在使用`use index`语法之后,仍然有可能出现优化器没有使用该索引的情况,此时可以使用`force index`语法,来强制指定使用的索引。
## Multi-Range Read
mysql支持Multi-Range Read优化用于减少随机的磁盘访问将随机访问转化为较为顺序的数据访问。对于io-bound的sql查询其能带来性能的较大提升。
mutli-range read优化适用于`range, ref, eq_ref`类型的查询。
### multi-range read优势
- MRR能够令数据访问变得较为顺序在查询辅助索引时对于得到的查询结果按照主键进行排序并按照主键排序的顺序进行书签查找
- 减少缓冲池中页被替换的次数
- 批量处理对于key的查询操作
### 范围查询和join查询优化
对于innodb的范围查询和join查询MRR工作方式如下
- 将查询到的辅助索引键值存放在缓冲中,此时缓存中的数据按照辅助索引排序
- 将缓存中的键值按照rowId进行排序
- 根据rowId的排序来实际访问数据文件
> 当innodb的缓冲池不足以存放表中所有数据时频繁的离散读写会导致缓冲池中的页被换出又被不断读入。
>
> `如果按照主键顺序进行访问,可以将该类行为降至最低,页面不会被换出缓冲区后又被读入`。
### 拆分键值对
除了上述优化外对某些范围查询mrr还能够进行拆分优化这样在拆分过程中直接能过滤掉一些不符合查询条件的数据。
例如:
```sql
select * from t where key_part1 >= 1000 and key_part1 < 2000 and key_part2 = 1000;
```
表t存在`(key_part1, key_part2)`的联合索引。
#### 未开启MRR
如果未开启mrr那么此时查询类型为`RANGE`mysql优化器会将key_part位于`[1000, 2000)`范围内的全部数据都取出即是key_part2不为1000。在去除后再按key_part2条件对取出数据进行过滤。
> 这将会导致有大量无用数据被取出。如果存在大量数据且key_part2不为1000那么使用mrr将会有大量性能提升。
#### 开启MRR优化
在使用了MRR优化后优化器会先对查询条件进行拆分`key_part1 >= 1000 and key_part1 < 2000 and key_part2 = 1000`条件拆分为`(1000, 1000), (1001, 1000), ..., (1999, 1000)`,在根据拆分后的条件进行查询。
#### mrr控制
是否开启mrr可以通过参数`optimizer_switch`中的flag来控制当mrr为`on`代表启用mrr优化。
`mrr_cost_based`标记代表`是否通过cost based的方式来选择是否启用mrr`
如果将`mrr`设置为`on``mrr_cost_based`设置为`off`代表总是启用mrr优化。
#### read_rnd_buffer_size
`read_rnd_buffer_size`用来控制键值的缓冲区大小当大于该值时则执行器则对已经缓存的数据根据rowId来进行排序并通过rowId来获取数据。该值默认为`256K`.
## Index Condition Pushdown(ICP)
可以通过`optimizer_switch`中的flag来控制`ICP`是否开启,如`index_condition_pushdown``on`代表icp开启。
例如,表`people`包含`zip_code, last_name, first_name)`索引,执行如下语句
```sql
select * from people where zipcode = '95054' and last_name like '%asahi%' and address like '%street%';
```
### 关闭ICP
如果ICP优化没有开启那么数据库首先会根据索引查询`zipcode`为95054的记录然后查询出后再根据查询出的结果过滤where的后两个条件`last_name like '%asahi%' and address like '%street%'`
### 开启ICP
当开启ICP后数据库会将where的部分过滤条件放在存储引擎层在索引获取数据同时就会进行where条件的过滤。
## innodb hash
在innodb中采用除法散列的哈希方法通过`k % m`将关键字`k`映射到`m`个槽中的一个,即
```
hash(k) = k % m
```
### innodb中的哈希
innodb存储引擎采用哈希算法对字典进行查找冲突机制采用链表方式哈希函数采用`k % m`的方式进行散列。
对于缓冲池页的哈希表缓冲池中的page页都有一个chain指针指向相同哈希值的页。而对于`m`的取值,其规则如下:
- m的取值应略大于2倍的缓冲池页数量的质数
- 例如,若`innodb_buffer_pool_size`大小为10M那么缓冲池页数量为 `10 * 1024 / 16 = 640`可容纳240个缓冲页故而对于缓冲池页内存的哈希表来说需要分配的`槽个数m`为大于`640 * 2`的质数,即`1399`
### 自适应hash索引
自适应hash索引可以通过参数`innodb_adaptive_hash_index`来进行开启或关闭并且自适应hash索引的使用情况可以通过`show engine innodb status`来进行查看:
```
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
insert 0, delete mark 0, delete 0
discarded operations:
insert 0, delete mark 0, delete 0
0.00 hash searches/s, 320660.58 non-hash searches/s
```
可以根据`hash searches/s``non-hash searches/s`来查看自适应hash索引的使用情况。
## 全文检索
### 倒排索引
全文检索通常使用`倒排索引`(inverted index)来实现和B+树索引一致,倒排索引也是一种索引结构。
倒排索引在辅助表(auxiliary table)中存储了`单词``单词自身在一个或多个文档中所在位置`之间的映射关系。通常,倒排索引通过关联数组可以实现,拥有两种表现形式:
- inverted file index: {单词单词所在文档id}
- full inverted index: {单词, (单词所在文档id, 在文档中的具体位置)}
#### inverted file index
对于inverted file index其存储的数据结构如下所示
| Number | Text | Documents |
| :-: | :-: | :-: |
| 1 | code | 1,4 |
| 2 | days | 3,6 |
| 3 | hot | 1,4 |
其中,`Documents`存储的是包含查询关键字的文档id数组。
> 对于inverted file index其仅保存文档的id
#### full inverted index
对于full inverted index其存储的是`(文档id文档中位置)``pair`
full inverted index的存储结构示例如下所示;
| Number | Text |Documents |
| :-: | :-: | :-: |
| 1 | code | (1:6), (4:8) |
| 2 | days | (3:2), (6:2) |
| 3 | hot | (1:3), (4:4) |
> full inverted index 相较于 inverted file index除了存储文档id之外还存储单词所在文档的位置信息。
### innodb全文检索
innodb支持full inverted index形式的全文检索。
在innodb中`(DocumentId, Position)`视为`ilist`。故而,在全文检索的表中,存在两个字段,`word``ilist`,并在`word`字段设有索引。`并且innodb在ilist中存放了position信息故而可以支持proximity search`
#### 辅助表
innodb中倒排索引需要将`word`存放在辅助表中,并且,为了提高全文检索的并行能力,共存在`6`张辅助表每张表根据word的Latin进行分区。
> 辅助表为持久化的表,存放在磁盘中。
#### FTS Index Cache (全文检索索引缓存)
FTS Index Cache为红黑树结构根据`(word, ilist)`进行排序。
在插入数据时,即使插入数据已经更新了对应的数据库表,但是对`全文索引`更新可能仍位于`FTS Index Cache`即辅助表尚未被更新。Innodb会批量对辅助表进行更新而非每次插入后都立即更新索引表。
当对全文检索进行查询时辅助表首先会将FTS Index Cache中对应word字段合并到辅助表中然后再在辅助表中执行查询操作。
> 上述FTS Index Cache操作类似Insert Buffer其能提高innodb的性能并且其由红黑树排序后再执行批量插入其产生的辅助表相对较小。
#### 查看指定倒排索引的辅助表分词信息
innodb允许用户查看指定倒排索引辅助表的分词信息可以通过设置`innodb_ft_aux_table`来查看倒排索引的辅助表。
```sql
set global innodb_ft_aux_table='{schema_name}/{table_name}';
```
执行完上述语句后,可以在`information_schema.innodb_ft_index_table`中查询表的分词信息。
#### FTS Index Cache更新和写盘时机
Innodb在事务提交时将分词写入到`FTS Index Cache`中,然后再根据批量更新将`FTS Index Cache`写入到磁盘。
在事务提交时FTS Index Cache中数据会同步到磁盘的辅助表中。但是当数据库发生宕机时FTS Index Search中的数据可能尚未被同步到磁盘中。
在上述宕机情况下下次数据库重启时若用户对表进行全文检索操作插入或查询innodb会自动读取未完成的文档并然后进行分词操作再次将分词结果放入到FTS Index Cache中。
#### 分词删除
对于文档中分词的删除操作,在事务提交时,不删除辅助表中的数据,而只是删除`FTS Index Cache`中的记录。对于被删除的记录会根记录其FTS Dcoument Id并且将该id保存在`deleted辅助表`中。
> 在删除文档时,文档内容从一般表中被删除,但是`索引辅助表`中的数据并不会被删除,相反的,还会将`FTS_DOC_ID`添加到`deleted辅助表`中。
#### OPTIMIZE TABLE
由上述内容可知对文档的DML并不会实际删除`索引中的数据`,只是会在`deleted辅助表`中添加`FTS_DOC_ID`,故而在应用程序运行时,`索引会变得越来越大`
mysql允许通过`optimize table`命令来实际将已删除记录从索引中删除。
由于`optimize table`语句不仅会删除索引中的数据,还会执行其他操作,例如`Cardinality`重新统计等。
> 如果用户仅希望对倒排索引进行操作,可以设置`innodb_optimize_fulltext_only`参数。
>
> 可以执行如下语句
> ```sql
> set global innodb_optimize_fulltext_only=1;
> optimize table xxx;
> ```
如果被删除文档很多那么optimize table可能会花费大量时间这将对程序的运行造成影响。
用户可以通过参数`innodb_ft_num_word_optimize`来限制每次实际删除的分词数量该参数默认值为2000.
#### innodb_ft_cache_size
可以通过`innodb_ft_cache_size`来控制FTS Index Cache的大小默认大小为`32M`。当缓冲被填满后,会将缓存中`(word, ilist)`数据同步到位于磁盘的辅助表中。
适当增加`innodb_ft_cache_size`参数的值能够提升全文检索的性能,但是在宕机时,位同步到磁盘中的数据可能需要更长时间来进行恢复。
#### FTS Document id
在innodb存储引擎中为了支持全文检索必须存在一个字段和辅助表中的`word`进行对应。在innodb中对应字段为`FTS_DOC_ID`
`FST_DOC_ID`字段的字段类型必须为`BIGINT UNSIGNED NOT NULL`并且innodb存储引擎会自动为该列添加名为`FST_DOC_ID_INDEX`的唯一索引`unique index`
### innodb全文检索示例
full-text index基于`text based columns`char, varchar, text来进行创建用于加速针对`text based colums`列的dml操作。
一个full-text index可以定义为`create table`语句的一部分,也可以通过`alter table``create index`语句添加到已经存在的表中。
full-text查询通过`match() ... against`预发来触发。
#### innodb full-text design
innodb全文索引基于倒排索引进行设计。倒排索引存储了一系列的words对每个word都存在一个list与之对应list中的元素为`包含word的文档`
为了支持`proximity search`word在list中出现的位置信息也同样被保存。
#### innodb full-text index tables
当innodb full-text index被创建时一系列index tables都会同时被创建示例如下所示。
创建表并指定fulltext索引
```sql
create table fs_text (
id bigint not null auto_increment,
content longtext,
primary key (id),
fulltext index `idx_fts_fs_text` (content)
);
```
查询辅助表:
```sql
select * from information_schema.innodb_tables where name like 'innodb_demo%'
```
其中和full-text表相关的索引如下
| TABLE\_ID | NAME | FLAG | N\_COLS | SPACE | ROW\_FORMAT | ZIP\_PAGE\_SIZE | SPACE\_TYPE | INSTANT\_COLS | TOTAL\_ROW\_VERSIONS |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 1822 | innodb\_demo/fs\_text | 33 | 6 | 96 | Dynamic | 0 | Single | 0 | 0 |
| 1823 | innodb\_demo/fts\_000000000000071e\_being\_deleted | 33 | 4 | 97 | Dynamic | 0 | Single | 0 | 0 |
| 1824 | innodb\_demo/fts\_000000000000071e\_being\_deleted\_cache | 33 | 4 | 98 | Dynamic | 0 | Single | 0 | 0 |
| 1825 | innodb\_demo/fts\_000000000000071e\_config | 33 | 5 | 99 | Dynamic | 0 | Single | 0 | 0 |
| 1826 | innodb\_demo/fts\_000000000000071e\_deleted | 33 | 4 | 100 | Dynamic | 0 | Single | 0 | 0 |
| 1827 | innodb\_demo/fts\_000000000000071e\_deleted\_cache | 33 | 4 | 101 | Dynamic | 0 | Single | 0 | 0 |
| 1828 | innodb\_demo/fts\_000000000000071e\_00000000000005ad\_index\_1 | 33 | 8 | 102 | Dynamic | 0 | Single | 0 | 0 |
| 1829 | innodb\_demo/fts\_000000000000071e\_00000000000005ad\_index\_2 | 33 | 8 | 103 | Dynamic | 0 | Single | 0 | 0 |
| 1830 | innodb\_demo/fts\_000000000000071e\_00000000000005ad\_index\_3 | 33 | 8 | 104 | Dynamic | 0 | Single | 0 | 0 |
| 1831 | innodb\_demo/fts\_000000000000071e\_00000000000005ad\_index\_4 | 33 | 8 | 105 | Dynamic | 0 | Single | 0 | 0 |
| 1832 | innodb\_demo/fts\_000000000000071e\_00000000000005ad\_index\_5 | 33 | 8 | 106 | Dynamic | 0 | Single | 0 | 0 |
| 1833 | innodb\_demo/fts\_000000000000071e\_00000000000005ad\_index\_6 | 33 | 8 | 107 | Dynamic | 0 | Single | 0 | 0 |
#### 辅助索引表
其中,以`innodb_demo/fts_000000000000071e_00000000000005ad_index`开头的6张表用于存储倒排索引并被称为`辅助索引表`
当新增的文档被分割为`token`时,每个独立的`word`(也可被称为`token`被插入到辅助索引表中随着word被插入的还有`postion``DOC_ID`
word按照`第一个字符的字符集排序权重`被排序,并且在六张辅助索引表中进行分区。
> 倒排索引被分区在6张辅助索引表中用于支持索引的并行创建。默认情况下2个线程执行`tokenize`, `sort`,`将word和关联数据插入到index tables`操作。
>
> 如果想要指定操作上述流程的线程数量,可以对`innodb_ft_sort_pll_degree`参数进行配置。如果要在大表上创建full-text index可以考虑增加该参数。
##### table_id hex
辅助索引表命名通过`fts_`开头,并且后缀`index_#`。每个辅助索引表都通过`辅助索引表表名中16进制的值`来和`被索引表的table id`来进行关联。例如上述示例中16进制值`071e`代表十进制`1822`,而表`fs_text``table_id`刚好为`1822`
##### index_id hex
在6张辅助索引表中除了包含`table_id`的hex外下划线后还存在一个16进制数部分该部分为`05ad`,代表十进制`1453`,而索引`idx_fts_fs_text``index_id`刚好为`1453`
> 如果是`file per table tablespace`那么idnex table将会保存在其自己的tablespace中。