当我们聊数据存储方案改进的时候,我们应该考虑哪些问题?找一个锚点
在一组数据中查询某项记录,无非顺讯查找、二分折半查找、二叉查找树、平衡二叉树、B树、B+树这样的一个演化过程;平均检索效率呈递增趋势;
B+树定义:是为磁盘或者其他直接存取辅助设备设计的一种平衡查找树,在B+树中,所有记录节点都是按键值的大小顺序存放在同一层的叶子节点,各叶子节点通过指针进行链接。B+树索引在数据库中的一个特点就是高扇出性。例如:InnoDB存储引擎中,每页的大小为16KB,因此数据库中B+树的高度一般都在2~4层,这意味着查找某一键值最多只需要2到4次IO操作;一般的磁盘查询时间也就0.02~0.04秒(按100次IO每秒计算);
B+树索引分为聚集索引和辅助索引(非聚集索引),区别仅在于存放数据的内容:
也就是说,如果通过辅助索引查找数据,当找到辅助索引的叶子节点后,可能还需要根据主键值查找聚集索引来得到数据,这种查找被称作书签查找(bookmark lookup)。因为辅助索引不包含整条数据,这也意味着辅助索引占用空间小,可以存放更多的键值,其高度一般小于聚集索引;
一般的经验是,在访问表中很少一部分行时使用B+树索引才有意义。对于如性别、类型、地区字段,他们的取值范围很小,称为低选择性;而像用户ID这种取值返回很广,几乎没有重复的字段,即高选择性,此种字段使用B+树索引是最合适的。。那么问题来了,怎么看索引是否是高选择性的呢?
可以通过SHOW INDEX语句中的Cardinality列来观察。该列值表示索引中唯一只记录数量的预估值;预估值,而非准确值。如果这个值很小,不应该作为索引值;这个值是搜索引擎通过采样预估统计出来的一个值;
B+树索引创建后,对该索引的使用应该只是通过该索引取得表中小部分数据。这时建立B+树索引才是有意义的,否则即使建立了Mysql优化器也能选择不使用索引;
联合索引:
我们可以创建单个列的索引,也可以创建2个列或2个以上列的联合索引;本质上联合索引还是一颗B+树,不同的是键值数量不是1,而是大于等于2。。例如,a、b两个列组成的联合索引,默认按a列进行排序,叶子节点存放(a,b)两个键值对的不同数据值。a是有序的、b是无序的,所以直接按b条件查询无法用到该索引;但是可以对第二个键值进行排序。。
联合索引特性:
覆盖索引:
InnoDB支持覆盖索引(covering index)也称索引覆盖(index covering),即从辅助索引中就可以得到查询的记录,而不需要查询聚集索引中的记录。使用覆盖索引的好处:
如果有使用覆盖索引,sql的EXPLAIN执行计划中exra字段提示Using index;
不使用索引查找的情况:
当我们要通过范围、join等操作,查询数据的整行信息时,没有办法使用的覆盖索引的情况、且查询的数据量超过占整个表的20%左右时,就会使用PRIMARY聚集索引进行全表扫描;例如:select * from table where a>'xxx' and a<'xxxx';
原因:首先查询整行信息,覆盖索引无法做到,因此对a索引检索到指定数据后,还需要进行一次书签查找来获取整行信息。虽然a索引是有序的,但书签查找是无序的,因此变为了磁盘的随机读取;如果访问的数据量很小,那么优化器会选择辅助索引,但是当访问数据量占整个表的数据的很大一部分(一般是20%左右),优化器会选择通过聚集索引来查找数据。
如果用户使用的是固态硬盘,随机读取的性能也是可以接受的,索引可以使用关键字FORCE INDEX来强制使用某个索引进行范围整行数据查找;例如:select * from table FORCE INDEX(a) where a>'xxx' and a<'xxxx';
INDEX HINT索引提示:
index hints可以人为显示的告诉mysql优化器要如何完成select,比如强制走某(些)索引或忽略某(些)索引。
其中,被指定的索引必须要有索引名。以下两种情况可能需要用到INDEX HINT:
MySQL数据库的优化器错误的选择了某个索引,导致SQL运行很慢。这个在最新版的数据库版本中非常少见。优化器在绝大部分情况下工作的非常有效和正确。
某些SQL语句可以选择的索引非常多,这时优化器选择执行计划时间的开销可能会大于SQL语句本身例如优化器分析Range查询本身就是比较耗时的操作。这时DBA或开发人员分析最优的索引选择,通过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)
例如:
测试用表:
mysql> CREATE TABLE hints_test(col1 int,
-> col2 int,
-> col3 int,
-> KEY idx_1(col1),
-> KEY idx_2(col2),
-> KEY idx_3(col3));
Query OK, 0 rows affected (0.09 sec)
随机插入一些数据:
mysql> SELECT * FROM hints_test;
+------+------+------+
| col1 | col2 | col3 |
+------+------+------+
| 1 | 2 | 3 |
| 2 | 2 | 3 |
| 2 | 3 | 3 |
| 3 | 3 | 5 |
| 3 | 1 | 2 |
| 2 | 1 | 1 |
| 2 | 3 | 3 |
| 4 | 4 | 4 |
| 6 | 5 | 3 |
+------+------+------+
9 rows in set (0.00 sec)
正常select(注,explain结果部分省略,下同)
mysql> EXPLAIN SELECT col1, col2, col3
-> FROM hints_test
-> WHERE col1=1 AND col2=2\G
*************************** 1. row ***************************
table: hints_test
type: ref
possible_keys: idx_1,idx_2
key: idx_1
key_len: 5
ref: const
加一个复合索引
mysql> ALTER TABLE hints_test ADD INDEX idx_1_2(col1,col2);
Query OK, 0 rows affected (0.57 sec)
Records: 0 Duplicates: 0 Warnings: 0
①指定使用idx_1_2索引
mysql> EXPLAIN SELECT col1, col2, col3
-> FROM hints_test
-> USE INDEX (idx_1_2)
-> WHERE col1=1 AND col2=2\G
*************************** 1. row ***************************
table: hints_test
type: ref
possible_keys: idx_1_2
key: idx_1_2
key_len: 10
ref: const,const
②忽略目前表中的三个索引
mysql> EXPLAIN SELECT col1, col2, col3
-> FROM hints_test
-> IGNORE INDEX (idx_1_2,idx_1,idx_2)
-> WHERE col1=1 AND col2=2\G
*************************** 1. row ***************************
table: hints_test
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
再次正常select:
此时col2走了索引,而order by col3没有走索引。
mysql> EXPLAIN SELECT col1, col2, col3
-> FROM hints_test
-> WHERE col2=2 ORDER BY col3\G
*************************** 1. row ***************************
table: hints_test
type: ref
possible_keys: idx_2
key: idx_2
key_len: 5
ref: const
③忽略idx_2索引,此时全表扫描:
mysql> EXPLAIN SELECT col1, col2, col3
-> FROM hints_test
-> IGNORE INDEX (idx_2)
-> WHERE col2=2
-> ORDER BY col3\G
*************************** 1. row ***************************
table: hints_test
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
④强制对order by语句使用idx_3索引:
mysql> EXPLAIN SELECT col1, col2, col3 FROM hints_test
-> FORCE INDEX FOR ORDER BY (idx_3)
-> IGNORE INDEX (idx_2)
-> WHERE col2=2
-> ORDER BY col3\G
*************************** 1. row ***************************
table: hints_test
type: index
possible_keys: NULL
key: idx_3
key_len: 5
ref: NULL
同理,除了WHERE和ORDER BY,可以同样对GROUP BY、JOIN操作进行USE、IGNORE、FORCE三种HINTS。
写法为:(USE, FORCE, IGNORE) and by scope (FOR JOIN, FOR ORDER BY, FOR GROUP BY).
Multi-Range Read (MRR):
Mysql数据库5.6版开始支持Multi-Range Read(MRR)优化。MRR的目的是减少磁盘的随机访问,并且将随机访问转换为较为顺序的访问,可为IO-bound类型的SQL查询语句带来性能的极大提升。MRR适用于range、ref、eq_ref类型的查询。
相关案例参考:https://www.cnblogs.com/vadim/p/7403544.html
Index Condition Pushdown(ICP):
Index Condition Pushdown (ICP)是MySQL用索引去表里取数据的一种优化。如果禁用ICP,引擎层会穿过索引在基表中寻找数据行,然后返回给MySQL Server层,再去为这些数据行进行WHERE后的条件的过滤。ICP启用,如果部分WHERE条件能使用索引中的字段,MySQL Server 会把这部分下推到引擎层。存储引擎通过使用索引条目,然后推索引条件进行评估,使用这个索引把满足的行从表中读取出。ICP能减少引擎层访问基表的次数和MySQL Server 访问存储引擎的次数。总之是 ICP的优化在引擎层就能够过滤掉大量的数据,这样无疑能够减少了对base table和mysql server的访问次数。关于到底是哪个filer下推,可以看SQL中的where条件,在数据库中提取与应用浅析
ICP的优化用于range, ref, eq_ref, and ref_or_null访问方法,当这些需要访问全表的行。这个策略可以用于INNODB和MyISAM表。
相关案例参考:https://www.cnblogs.com/zhoujinyi/archive/2013/04/16/3016223.html