#mysql5.7 innodb默认存储引擎
一、关于索引
二、最佳实践
三、避坑实践
一、关于索引
1.索引的作用
-提高查询效率
-数据分组、排序
-避免回表查询
-优化聚集查询
-用于多表join关联查询
-利用唯一性约束、保证数据唯一性
-innodb行锁实现
#索引的“作用”(副作用)
-增加io成本
-增加磁盘空间
-不适合的索引,或索引过多,都不是好事
#索引类型
-BTREE (B+ tree或B-tree),INNODB &MYISAM
-Fractal TREE,TokuDB
-HASH(用于内存的存储引擎),HEAP,NDB,INNODB AHI
-RTEE
-FULLTEXT
#针对b+tree索引
#聚集索引
-一种特殊的索引,其key值顺序决定了表数据行的物理顺序(相对)顺序
-每张表只能建一个聚集索引,除了tokudb引擎
-innodb中,聚集索引即表,表即聚集索引(innodb存储了整行的数据)
-myisam没有聚集索引的概念
#聚集索引优先选择列
-int、bigint
-数据连续(单调顺序)递增、自增
#不建议的聚集索引类型
-频繁的修改列
-新增数据太过随机或离散
-uuid、字符串、随机数。不建议设置聚集索引
#主键索引(具体的索引列)
-由一个列或多个列组成
-用于唯一表示表中的某一条记录&外键约束
-主键值不能重复,也不能包含null
#主键选择建议:
-对业务透明,无意义,免受业务变化的影响
-几乎不修改和删除
-自增int、bigint最佳
#innodb聚集索引选择次序原则
1.显示声明的主键
2.第一个not nullable的唯一索引
3.上述两者都没,则选择内置rowid作为聚集索引(实例级,6bytes
#全局rowID
rowid作为聚集索引缺点:
1)整个实例来分配的rowid
2)不是每个表单独分配的rowid
3)rowid是全局的rowid,每个表的rowid都是跳跃的
#innodb主键特点:
-索引定义时,不管有无显式包含主键,实际都会存储主键值;
-在5.6.9以后,优化器异能自动识别索引末尾的主键值(index Extensions)
-,在这之前则需要显式加上主键才可以被识别;
-where c1=? and pk=?
-where ci=? order by pk(主键列)
#辅助索引
-又称为非聚集索引,或者二级索引,俗称普通索引
-当通过innodb辅助索引查找数据时,辅助索引会通过页级的指针找到主键索引的主键,
-然后通过该主键索引找到相应的行数据
ex:c1=>(c1.pk)=>pk=>row data
#聚集索引
ex:
create table x(
id int(10) unsigned not null auto_increment,
uid int(10) unsiged not null default ‘0’, #uid为辅助索引
user varchar(20) not null default ‘‘,
passwd varchar(40) not null default ‘‘,
primary key(id), #id为聚集索引
unique key uid(uid),#唯一的辅助索引
)ENGINE=Innodb
以下视图是开了,innodb—monitor内部结构显示的信息
TABLE:....
COLUMNS:...
uid:...
user:...
DB_ROW_ID:...
DB_ROLL_PTR:...
INDEX:name PRIMARY,id 54,fields 1/6,uniq 1,tyep 3 #聚集索引选择主键为聚集索引,总共6个列,显示定义一个列
root page 3,appr.key vals 0,leaf pages 1,size pages 1
FILEDS:id DB_TRX_ID DB_ROLL_PTR uid user passwd #聚集索引后面跟着两个隐藏列分别为:DB_TRX_ID事物ID,DB_ROLL_PTR回滚指针,还有其他的功能列
INDEX:name uid.id 55,fields 1/2,uniq 2,type 2 #辅助索引,总共2个类,定义了一个列。实际存储为两个列。辅助索引总是跟着主键的值,才能根据uid找到主键的值。
root page 4,appr.key vals 0,leaf pages 1,size pages 1
FIELDS:uid id
#innodb索引图
[ ]
+---+---+ internal nodes
| | |
[ ] [ ] [ ]
[3 ] [ ] [99 ] [ ] [4700]
--> [TID] --> [ ] --> [TID] --> [ ] --> [TID ] --> inndb clustered
[RP ] [ ] [RP ] [ ] [RP ] indexleaf nodes
【93 】 【 】 【8 】 【 】 【13 】
[ ]Primary key columns (col1)
[TID]Transaction ID
[RP ]Rollback Pointer
【 】Non-Pk columns(col2)
#联合索引
-多列组成,所以也叫多列索引
-适合where条件中的多列组合
-有时候,还可以用于避免回表(覆盖索引)
-MySQL还不支持多列不同排序规则(MySQL8.0起支持)
nnodb_table_monitor
-联合索引建议:
-where条件中,经常同时出现的列放在联合索引中
-把选择性(过滤性、基数)大的列放在联合索引的最左边
ex:
a=? and b=? and c=?
a>? and b=? and c=?
建议:(b,c,a)
#部分索引
- 部分索引的原因
-char/varchar太长全部做索引的话,效率太差,存在浪费
-或者blob/text类型不能整列作为索引列,因此需要使用前缀索引
-部分说要选择建议
-统计平均值
-满足80%~90%覆盖度就够
-缺点
-无法利用前缀索引完成排序
ex:
username varchar(50)
统计平均长度
selelct avg(length(username)) from tt;
50*3=150
username(18) 18*3=54
*3-表示utf8以三个字节表示的
#覆盖索引
-通过索引数据结构,即可直接返回数据,不需要回表
-执行计划中,Extra列会显示关键字using index
#innodb索引特点
- 索引最大长度767bytes
-启用innodb_large_prefix,增加到3072bytes,只针对DYNAMIC、COMPRESSED格式管用
ex:
show table status like ‘x1‘\G
--------------1.row---------------
Name:x1
Engine:innodb
Version:10
Row_format:Dynamic
Rows:14
Avg_row_length:1170
-对于REDUNDANT、COMPACT格式,最大索引长度还是767bytes
-MyISAM表索引最大长度是1000bytes
-最大排序长度默认是1024(max_sort_length)
#索引统计
-show index from table
-select * from information_sechma.statistics
-mysql.innodb_index_stats
-innodb_stats_auto_recalc
-默认启用,当修改数据量>10%,自动更新统计信息
-innodb_stats_persistent
-统计信息持久化存储,默认启用
-innodb_stats_persistent_sample_pages
-统计信息持久化存储时,每次采集20个page
ex:
随机采集,不连续。五个索引,采集100page。每个表,每个分区,每个索引采集20个page
-inoodb_stats_on_metadata
-默认禁用,访问meta data时更新统计信息
-innodb_stats_persistent=0
-统计信息不持久化,每次动态采集,存储在内存中,重启实效(需要新统计),不推荐
-innodb_stats_transient_sample_pages
-动态采集page,默认8个
-每个表设定统计模式
-create/alter table ...stats_persistent=1,stats_auto_recalc=1,
stats_sample_pages=200;
-参考:细说ANALZE TABLE
#EXPLAIN之type
-all
-全表扫描,最糟糕的情况
-index
-全索引扫描,大部分情况下,一样糟糕
#EXPLAIN之key_len
-正常的等于索引列字节长度
-字符串类型需要同时考虑字符集因素
-若允许null,再+1
-变长类型(varchar),在+2
-key_len只计算利用索引完成数据过滤时索引长度
-不包括用于GROUP BY/ORDER BY 的索引长度
ex:
-int 4 bigint 8
-index:(int_col1,bigint_col2)
-where int_col1=? order by bigint_col2 #key_len 长度为4,不是4+8=12。key_len不包括order by后面的长度
#EXPLAIN之extra
-Using filesort
-没有办法利用现有索引进行排序,需要额外排序
-建议:根据排序需要,创建相应合适的索引
-Using index
-利用覆盖索引,无需回表即可取得结果数据
-Using temporary
-需要用临时表存储结果集,通常是因为group by的列上没有索引。也有可能是因为同时有group by和order by,但group by和order by的列又不一样
#查看索引利用率
-查看每个索引使用情况
#select index_name,rows_selected,rows_updated,
rows_deleted from schema_index_statistics where
table_schema=’world’ and table_name=’city’;
+---------------+----------------+----------------+--------------+
|index_name | rows_selected| rows_updated | rows_deleted |
| PRIMARY | 33131 | 0 | 0 |
| CountryCode | 103 | 0 | 0 |
|+---------------|----------------+-----------------+--------------+
#mysql 5.7 默认启用p_s
#查看冗余索引
#select * from schema_redundant_indexes where
table_schema=’test’ and table_name=’tutf8’\G
*******************1.row***********************
table_schema:test
table_name:t1
redundant_index_name:c2_2
redundant_index_columns:c2
redundant_index_non_unique:1
dominant_index_name:c2
doinant_index_columns:c2
dominant_index_non_unique:1
subpart_exists:1
sql_drop_index:ALTER TABLE ‘test’.’t1’ DROP INDEX ‘c2_2’
ex:
index1:c1
index2:c1,c2
index1是index2的冗余索引
#冗余索引不一定可以删除,特殊场景视情况分析
-index k1(a,b,c)
-index k2(a,b)
-一般认为,k2是k1的冗余索引
-但下面的sql则只有k2才管用
- where a=? and b=? and pk=?;
-where a=? and b=? order by pk;
#查看无用索引
#select * from schema_unused_indexes
where object_schema=’test’ and object_name=’t1’;
+------------------+------------------+-------------+
| object_schema | objdect_name | index_name |
+------------------+------------------+--------------+
| test | t1 | k1 |
+------------------+-------------------+-------------+
#索引为何不可用
-通过索引扫描的记录数超过20%~30%,可能会变成全表扫描
-联合索引中,第一个查询条件不是最左索引列
-模糊查询条件列最左以通配符%开始
-HEAP表使用HASH索引时,使用范围检索或者ORDER BY
-多表关联时,排序字段不属于驱动表,无法利用索引完成排序
-JOIN查询时,关联列数据类型(字符集)不一定也会导致索引不可用
#类型隐式转换
#desc select * from t1 where u2=123\G
*****************1.row***************
id:1
select_type:SIMPLE
table:t1
partitions:NULL
type:ALL
possible_keys:u2
key:NULL
key_len:NULL
ref:NULL
rows:102176
filtered:10.00
Extra:Using where
#us=123 出现隐式转换无法利用到索引,视为ASCII
us=’123’ 可以利用索引,支持字符串
#MYSQL 5.6/5.7
#desc select * from t1 where gmt_create between date_add(now(),interval - 1 minute)
and date_add(now(),interval 15 minute)\G
**************1.row**********************
id:1
select_type:SIMPLE
table:t1
type:ALL
possible_keys:ind_gmt_create
key:NULL
rows:2849555
Extra:Using where
|Warning | 1739 | Cannot use range access on index ‘index_gmt_create’
due to type or collation conversion on field ‘gmt_create’
#gmt_create 日期时间会出现隐式转换,在底层被修改为int类型
二、索引最佳实践
-mysqld进程cpu占用长期较高时,99.99%时因为索引不当导致
#top 执行时:
%CPU 362.3 121.7
COMMAND mysqld
这种现象基本都是用不到索引,高频度列用不到索引
-mysql内用户线程状态经常看到Sending data,也基本上是因为索引不当导致
#mysqladmin pr|grep -v Sleep
+----+-------+--------------+--------+-----------+-------+-------------+------------+
| id | user | host | db | command | time | state | info |
+-----+-------+--------------|+-----+|---------+-------+-------------+--------------|
| 25 | x | 10.x.8519 | db | Query | 68 |Sending data | select ...|
| 26 | x | 10.x.8520 | db | Query | 65 |Sending data | select ...|
| 28 | x | 10.x.8522 | db | Query | 130 |Sending data | select ...|
| 27 | x | 10.x.8521 | db | Query | 167 |Sending data | select ...|
| 36 | x | 10.x.8727 | db | Query | 174 |Sending data | select ...|
+|-----+------+---------------|------|-----------|------|---+-------+---|----------+-|
三、避坑实践
-所有主要列都创建单独索引
-长varchar列创建索引
-基数低的列单独创建索引,或者放在联合索引的最左边
-mysql不支持位图索引
#相关参数优化
-sort-buffer-size/join-buffer-size/read-rnd-buffer-size,4~8MB为宜
-optimizer_swich=”index_condition_pushdown=on,mrr=on,mrr_cost_based=off,batched_key_access=on”
-tmp-table-size=max-heap-table-size,100MB左右为宜
-log-queries-not-using-indexes& log_throttle_queries_not_using_indexes
#MySQL8.0+新变化
-倒叙索引 #(a asc,b desc)
-不可见索引 #不常用索引
#附资料
-由浅入深理解innodb的索引实现
-mysql索引值聚集索引
-B+ 树索引和哈希索引的区别
-10分钟让你明白mysql是如何利用索引的
-老斯基带你解锁mysql8.0索引新姿势
-mysql排序内部原理探秘
-index extensions特性介绍
-细说ANALZE TABLE
-解读EXPLAIN执行计划中的key_len
-innblock|innodb page 观察利器
-听说join的列类型一定要一样
-UPDATE能走索引还会锁全表吗
-索引、提交频率对innodb表写入速度的影响
-为什么innodb表要建议自增列做主键
-典型性索引引发CPU负载飙升问题
-UPDATE时主键冲突引发的思考
-单节点可用方案http://github.com/zhishutech/