1、只为用于搜索、排序或分组
的列创建索引
即:只为出现在WHERE子句中的列、连接子句中的连接列,或者出现在ORDER BY或GROUP BY子句
中的列创建索引。而出现在查询列表中的列就没必要建立索引了
2、为基数大的列创建索引
列的基数指的是:某一列中不重复数据的个数,比如性别列的基数就是2
为什么为基数大的列创建索引?
在记录行数一定的情况下,列的基数越大,该列中的值越分散,列的基数越小,该列中的值越集中。
比如如果某个建立了二级索引的列的重复值特别多,那么使用这个二级索引查出的记录还可能要做回表操作,这样性能损耗就很大了。
3、索引列的类型尽量小,尤其是主键的类型越小越好。
数据类型越小,在查询时进行的比较操作越快
数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以放下更多的记录,从而减少磁盘I/O带来的性能损耗,也就意味着可以把更多的数据页缓存在内存中,从而加快读写效率。
这个建议对于主键来说尤为重要,因为不仅是聚簇索引中会存储主键值,其他所有的二级索引的节点处都会存储一份记录的主键值,如果主键适用更小的数据类型,也就意味着节省更多的存储空间和更高效的I/O。
4、只对字符串的前几个字符进行索引(索引列前缀)
字符串越长,存在两个弊端:
解决办法:只对字符串的前几个字符进行索引
也就是说在二级索引
的记录中只保留字符串前几个字符。这样在查找记录时虽然不能精确的定位到记录的位置,但是能定位到相应前缀所在的位置,然后根据前缀相同的记录的主键值回表查询完整的字符串值,再对比就好了。
这样只在B+树中存储字符串的前几个字符的编码,既节约空间,又减少了字符串的比较时间,还大概能解决排序的问题。
5、让索引列在比较表达式中单独出现
,才能使用到索引
如果索引列在比较表达式中不是以单独列的形式出现,而是以某个表达式,或者函数调用形式出现的话,是用不到索引的。
6、使用自增主键
7、消除冗余和重复索引
8、索引并不是越多越好!应该限制每张表上的索引数量,建议单张表索引不超过 5 个。原因:
降低增删改的效率
。磁盘空间就越大
。9、字符字段最好不要做主键
使用联合索引时注意前缀匹配原则
,将查询条件和索引顺序
保持一致。
合理利用覆盖索引、索引下推机制
例如:尽量使用覆盖索引进行查询,避免回表
带来的性能损耗。
为了尽可能少的让聚簇索引发生页面分裂和记录移位
的情况,尽量使用自增主键
。
定位并删除表中的冗余索引
删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗
在使用 limit offset
查询缓慢时,可以借助索引来提高性能
不用外键,由程序保证约束
尽量不用UNIQUE
,由程序保证约束
也就是说,只为出现在WHERE子句中的列、连接子句中的连接列,或者出现在ORDER BY或GROUP BY子句中的列创建索引。而出现在查询列表中的列就没必要建立索引了:
SELECT birthday, country FROM person_name WHERE name = 'Ashburn';
像查询列表中的birthday、country这两个列就不需要建立索引,我们只需要为出现在WHERE子句中的name列创建索引就可以了。
列的基数指的是某一列中不重复数据的个数,比方说某个列包含值2, 5, 8, 2, 5, 8, 2, 5, 8,虽然有9条记录,但该列的基数却是3。即
在记录行数一定的情况下,列的基数越大,该列中的值越分散,列的基数越小,该列中的值越集中。
这个列的基数指标非常重要,直接影响我们是否能有效的利用索引。比如性别的基数为2,所有记录在该列中的值要么为男要么为女,那为该列建立索引作用不大,因为无法排序,无法进行快速查找。
而且如果某个建立了二级索引的列的重复值特别多,那么使用这个二级索引查出的记录还可能要做回表操作,这样性能损耗就更大了。
所以结论就是:最好为那些列的基数大的列建立索引,为基数太小列的建立索引效果可能不好。
以整数类型为例,有TINYINT、MEDIUMINT、INT、BIGINT这么几种,它们占用的存储空间依次递增,我们这里所说的类型大小指的就是该类型表示的数据范围的大小。能表示的整数范围当然也是依次递增,如果我们想要对某个整数列建立索引的话,在表示的整数范围允许的情况下,尽量让索引列使用较小的类型,比如我们能使用INT就不要使用BIGINT,能使用MEDIUMINT就不要使用INT~ 这是因为:
数据类型越小,在查询时进行的比较操作越快
数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以放下更多的记录,从而减少磁盘I/O带来的性能损耗,也就意味着可以把更多的数据页缓存在内存中,从而加快读写效率。
这个建议对于主键来说更加适用,因为不仅是聚簇索引中会存储主键值,其他所有的二级索引的节点处都会存储一份记录的主键值,如果主键适用更小的数据类型,也就意味着节省更多的存储空间和更高效的I/O。
我们知道一个字符串其实是由若干个字符组成,如果我们在MySQL中使用utf8
字符集去存储字符串的话,编码一个字符需要占用1~3个字节。假设我们的字符串很长,那存储一个字符串就需要占用很大的存储空间。在我们需要为这个字符串列建立索引时,那就意味着在对应的B+树中有这么两个问题:
B+树索引中的记录需要把该列的完整字符串存储起来,而且字符串越长,在索引中占用的存储空间越大。
如果B+树索引中索引列存储的字符串很长,那在做字符串比较时会占用更多的时间。
我们前边儿说过索引列的字符串前缀其实也是排好序的,所以索引的设计者提出了个方案:
只对字符串的前几个字符进行索引
也就是说在二级索引
的记录中只保留字符串前几个字符。这样在查找记录时虽然不能精确的定位到记录的位置,但是能定位到相应前缀所在的位置,然后根据前缀相同的记录的主键值回表查询完整的字符串值,再对比就好了。
这样只在B+树中存储字符串的前几个字符的编码,既节约空间,又减少了字符串的比较时间,还大概能解决排序的问题,何乐而不为,比方说我们在建表语句中只对name列的前10个字符进行索引可以这么写:
name(10)
就表示在建立的B+树索引中只保留记录的前10个字符的编码,这种只索引字符串值的前缀的策略是我们非常鼓励的,尤其是在字符串类型能存储的字符比较多的时候。
假设表中有一个整数列my_col
,我们为这个列建立了索引。下边的两个WHERE子句虽然语义是一致的,但是在效率上却有差别:
WHERE my_col * 2 < 4 # 不能用索引
WHERE my_col < 4/2 #可以用索引
第1个WHERE子句中my_col列并不是以单独列的形式出现的,这种情况下是使用不到为my_col列建立的B+树索引的。
而第2个WHERE子句中my_col列是以单独列的形式出现的,这样的情况可以直接使用B+树索引。
有时候有的同学有意或者无意的就对同一个列创建了多个索引,比方说这样写建表语句:
CREATE TABLE person_info(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
birthday DATE NOT NULL,
phone_number CHAR(11) NOT NULL,
country varchar(100) NOT NULL,
PRIMARY KEY (id),
KEY idx_name_birthday_phone_number (name(10), birthday, phone_number),
KEY idx_name (name(10))
);
我们知道,通过idx_name_birthday_phone_number索引就可以对name列进行快速搜索,再创建一个专门针对name列的索引就算是一个冗余索引,
另一种情况,我们可能会对某个列重复建立索引,比方说这样:
CREATE TABLE repeat_index_demo (
c1 INT PRIMARY KEY,
c2 INT,
UNIQUE uidx_c1 (c1),
INDEX idx_c1 (c1)
);
我们看到,c1既是主键、又给它定义为一个唯一索引,还给它定义了一个普通索引,可是主键本身就会生成聚簇索引,所以定义的唯一索引和普通索引是重复索引,这种情况要避免。