1:数据库索引和选择性的关系
在讨论数据库索引的时候,经常会提到“选择性”(selectivity)这个概念。“选择性”是描述列值数据分布情况的一个重要属性。“选择性”和“基数”(cardinality)是两个密不可分的概念。“基数”就是一列中唯一值的数量,对于有唯一约束的列,“基数”等于表的总行数。
怎么计算索引的选择性?
选择性 = 基数/总行数 * 100%
选择性是什么意思?
上面的公式应该怎么理解?假设表中有Sex(性别)这一列,列值只有两种可能—`Male`(男性)和`Female`(女性),那么,Sex列的基数(cardinality)就是2。如果这张表中有10000条行记录,那么Sex列的索引的选择性就是2/10000 * 100% = 0.02%。
为什么“选择性”对索引很重要,数据库怎么利用“选择性”的?
“选择性”的高低可以衡量列值的可能性,换句话说,在给定的样本集里有多少个不同的值。
我们思考一下,低选择性意味着什么?低选择性意味着列值没有太多变化(或没有太多的可能性)。例如,Sex列的选择性,只有非常低的0.02%,这就意味着,Sex列的列值有很少的不同值。
选择性对数据库索引有什么影响?
数据库的查询优化器会根据索引的“选择性”来判断是否使用索引执行查询。也就是说,你在某列上创建了索引,这不意味着数据库就一定会使用这个索引,因为有时全表扫描是更高效的选择。
什么时候不应该使用数据库索引?
什么时候不应该使用数据库索引?当“选择性”比较低的时候!为什么低选择性的时候不适合使用索引呢?设想一下,现在我们要查询所有女性的名字,由于性别只有男性和女性两种情况,所以女性占比是50%的可能性很大。那我们就假设确实有50%(5000)是女性。如果查找索引的话,数据库为了查找出所有女性就需要访问索引5000次。要知道访问索引也是需要消耗时间和资源的。所以这种情况,直接去做全表扫描可能会更快一些。可以看到,数据库的查询优化器会根据“选择性”的高低来决定使用索引还是直接全表扫描。
“选择性”等于多少才会使用索引?
这个问题很难回答,它因数据库而异。
当然,“选择性”很高时,应该使用索引。例如,我们要查询的某列,其“选择性”是100%,说明该列的列值都是唯一的。这时,如果只查询其中的一行,使用索引是最高效的。同时,这也是执行全表扫描最坏的情形。:
2:哪些情况可创建索引:
索引是建立在数据库表中的某些列的上面。在创建索引的时候,应该考虑在哪些列上可以创建索引,在哪些列上不能创建索引。一般来说,应该在这些列上创建索引:
在经常需要搜索的列上,可以加快搜索的速度;
在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;
在经常用在连接(join)的列上,这些列主要是一些外键,可以加快连接的速度;
在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;
在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;
在经常使用在where子句中的列上面创建索引,加快条件的判断速度。
同样,对于有些列不应该创建索引。一般来说,不应该创建索引的的这些列具有下列特点:
第一,对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
第二,对于那些只有很少数据值的列也不应该增加索引。也就是上面说的选择性低,这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
第三,对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
第四,当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。也就是更新操作比较多的业务,建立索引不一定效率更好。
3:我们常用的btree索引和hash索引有区别吗?
一般我们都会用btree索引。
从索引原理角度考虑。B-Tree索引,是一般性的索引,适用范围更广。但是B-Tree索引不适合大范围数据扫描。B-Tree索引,至少需要至少两次查询,第一次扫描索引,拿到ROWID后,然后从数据块中去获取数据行。而Hash索引,只需要根据查询的值进行一次Hash函数计算,就可以得到ROWID的位置,也就是说肯定只需要计算一次就能拿到ROWID。因此,Hash所以你更适合=值查找。
哈希支持随机读取操作,但不支持顺序扫描;B-Tree支持顺序扫描!
4:全表扫描就有点垃圾了啊。
可以使用索引有效的避免全表扫描。
那哪些情况会造成全表扫描呢?
1:如果查询中没有where子句,会导致全表扫描。
2:有些情况,及时创建了索引,还是会导致全表扫描。
2.1:使用不等于操作(!= 或 <>)。
例如: WHERE NAME <> 'Jesus'
因为索引只能用于查找表中有什么,而不能用于查找表中没有什么。
2.2:使用`NOT`操作符。
例如:WHERE NOT NAME 'Jesus' 。原因同上
2.3:通配符出现在字符串比较的开始位置。
例如:WHERE NAME LIKE '%programmer%' 。
以哪个字母开始都不清楚,索引也无能为力了。
2.4:最左前缀匹配原则
对于多列的混合索引:,只有符合最左前缀的索引才能被查询优化器使用。
比如,某个3列的混合索引(col1,col2,col3),
只有下面三种情况可以使用到索引: (col1), (col1, col2),和 (col1, col2, col3)。
对于下面的查询语句:
SELECT * FROM tbl_name WHERE col1=val1;
SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2;
SELECT * FROM tbl_name WHERE col2=val2;
SELECT * FROM tbl_name WHERE col2=val2 AND col3=val3;
只有前两个SELECT语句使用到了索引,
第3个和第4个查询虽然使用了索引列,但是(col2)和(col2,col3)不是混合索引(col1, col2, col3)的最左前缀。
3:如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)
注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
4:like查询是以%开头,将不会使用索引
5:如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引。
(要特别注意这个,这一点还是比较坑的)
5:索引区别
普通索引:最基本的索引,没有任何限制
唯一索引:与"普通索引"类似,不同的就是:索引列的值必须唯一,但允许有空值。
主键索引:它是一种特殊的唯一索引,不允许有空值。
全文索引:针对较大的数据,生成全文索引很耗时好空间。
组合索引:为了更多的提高mysql效率可建立组合索引,遵循”最左前缀“原则。
其中还有一种索引叫覆盖索引:
覆盖索引就是从索引中直接获取查询结果,要使用覆盖索引需要注意select查询列中包含在索引列中;where条件包含索引列或者复合索引的前导列;查询结果的字段长度尽可能少。
覆盖索引:覆盖索引是指索引的叶子节点已包含所有要查询的列,因此不需要访问表数据(回表~~又学个名词)
覆盖索引是指索引的叶子节点已包含所有要查询的列,因此不需要访问表数据,能极大地提高性能。
其实理解覆盖索引很简单:
索引既数据
个人理解,覆盖索引指的是索引中已包含需要查询的数据,执行查询时无需扫描数据文件,扫描索引文件即可。联合索引,在多个属性,建立为一个索引,加快多条件查询。
比如我在user_info表中为name字段建立了索引
我这样查询:
Select * from user_info where name="zhangsan"
这种是正常查询,并没有使用覆盖索引。
那么如何使用覆盖索引呢?
Select name user_info where name="zhangsna"
这种是覆盖索引,我的查询结果的字段,可以直接从索引中获取。
注意:
1、覆盖索引也并不适用于任意的索引类型,索引必须存储列的值
2、Hash 和full-text索引不存储值,因此MySQL只能使用B-TREE
3、并且不同的存储引擎实现覆盖索引都是不同的
4、并不是所有的存储引擎都支持它们
5、如果要使用覆盖索引,一定要注意SELECT 列表值取出需要的列,不可以是SELECT *,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降,不能为了利用覆盖索引而这么做
所以,知道有这么个东西就行了,一般实际生产中都用户到覆盖索引的。
注意:数据量特别大的时候,最好不要用联合查询(join),即使你做了索引。
6:十万个为什么?
6.1:为什么加索引后会使查询变快?
因为加索引之后,会使数据库的所有数据变成平衡树的结构,查一次数据,只需要很少的IO次数就可以查到。
所以,查询效率变快。
6.2:为什么加索引后会使写入、修改、删除变慢?
因为平衡树这个结构必须一致维持在一个正确的状态,增删改数据都会改变平衡树各个节点中的索引的数据内容,
破坏树结构,
因此,每次数据改变时,都必须重新梳理索引的结构,以确保它的正确,
这会带来不小的性能开销,所以会变慢。
6.3:使用索引有什么代价?
首先,占用空间。你的表越大,索引需要的空间就越大。
另外会影响数据库性能,你对数据表进行增删改操作,同样的操作还要在索引上执行一次。
6.4:B+树索引和哈希索引的明显区别是?
1:如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值;
当然了,这个前提是,键值都是唯一的。
如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,直到找到相应的数据;
2:如果是范围查询检索,这时候哈希索引就毫无用武之地了,
因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索;
3:同理,哈希索引也没办法利用索引完成排序,
以及like ‘xxx%’ 这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询);
4:哈希索引也不支持多列联合索引的最左匹配规则;
5:在有大量重复键值情况下,哈希索引的效率也是极低的,因为存在所谓的哈希碰撞问题。
6.5:NULL 的问题?
NULL会导致索引形同虚设,所以在设计表结构时应避免NULL 的存在,一般都要设置not null(用其他方式表达你想表达的NULL,比如 -1?)
比如:查询用is null
,is not null
是不走索引的。
如果列值有null,应该是可以走索引的,只是sql中不能用is null 或者 is not null 这种写法而已。
其实:
官方一点的说法应该是:
应尽量避免在where 子句中对字段进行 null值判断,否则将导致引擎放弃使用索引而进行全表扫描。
不建议列上允许为空。最好限制not null
,并设置一个默认值,比如0
和''
空字符串等,如果是datetime类型,可以设置成'1970-01-01 00:00:00'
这样的特殊值。
但我本地试了:
is null 或者 is not null 在任何情况下都不会用索引。
如果某一列默认为null,也就是null列,是可以用到索引的,只要正常的查询。