普通索引和唯一索引该如何选择
我们先来谈谈change buffer
当需要更新一个数据页的时候,若数据页在内存中有就直接更新,若这个数据还没有在内存中,在不影响数据一致性的前提下,InnoDB会将这些数据缓存在change buffer中,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据读入内存中,然后执行change buffer中与这个页有关的操作。
change buffer实际上为可持久化的数据,在内存中有拷贝,也被存入磁盘中。操作到原数据页的操作叫merge。
除了访问这个操作可以触发merge,系统有后台线程也会定期merge,在关闭数据库的时候也会merge。
什么条件下可以用change buffer呢?
对于唯一性约束来看,先要判断是否唯一性约束,即为不能重复,比如现在插入(4,400)到当前数据库,会先判断是否有(4,400)这个记录,而这必须要将数据页读入到内存才能判断,但是我们都已经读入内存了,那还不如直接进行内存更新,这样来的更快些。没有必要使用change buffer,使用change buffer还会增加维护他的成本。
所以只有普通索引才能使用change buffer,唯一索引不能使用。
change buffer 用的是buffer pool的内存,因此不能无限增大,是有限的,change buffer的大小可以使用指令innodb_change_buffer_max_size来调整,change buffer最多占change pool 的百分之五十。
当我们不使用change buffer使用InnoDB来更新,会使用IO流,这样会耗费巨大的成本。change buffer减少了磁盘的访问次数,使得更新性能极大地提升。
change buffer的使用场景
普通索引的写多读少的业务有利,若每次更新的时候都进行查询,都会触发merge,这不仅不会减少IO访问次数,而且加上维护change buffer 的成本,change buffer反而起到了副作用。
change buffer 和 redo log
k是唯一索引
比如我们在表中执行insert into t(ID,k)values (id1,k1),(id2,k2)
他会做下面的操作
k1被在内存中找到,直接进行内存更新
k2没有被找到,就在change buffer中记录需要写入k2
然后在讲上述两个动作写入redo log
redo log是节约随机写磁盘的IO消耗 到一定量写入
change buffer 节省随机读的IO消耗 读的时候写入
为什么MySQL会选错索引
我们先来了解一下explain,explain语句用于获取查询计划的信息,它描述了MySQL如何查询并访问表的数据
1. 查询表的执行计划
2. 表的读取顺序
3. 连接方式
4. 索引引用的使用情况或存储函数
5. 查询优化器考虑的因素
执行Explain语句,MySQL返回一张表格,包含执行计划的信息
下面来了解一下CALL语句
在MySQL中,CALL
语句用于调用存储过程或存储函数。存储过程是一组预编译的SQL语句,可以在数据库中进行存储和重复使用。存储函数类似于函数,可以接受参数并返回一个值。
使用CALL
语句可以执行存储过程或存储函数。语法如下:
CALL procedure_name(arguments);
其中,procedure_name
是存储过程或存储函数的名称,arguments
是传递给存储过程或存储函数的参数。
例如,假设有一个名为get_customer_details
的存储过程,它接受一个顾客ID作为参数,并返回该顾客的详细信息。你可以使用以下语句调用该存储过程:
CALL get_customer_details(123);
这将执行名为get_customer_details
的存储过程,并将参数值123
传递给它。根据存储过程的定义,它将执行相应的操作并返回结果。
需要注意的是,CALL
语句必须在数据库连接上下文中执行,并且必须具有足够的权限来执行存储过程或存储函数。
strat snapshot为开启一个快照
使用它意味着事务在开启的时候会创建一个一致性快照,这意味着事务在开启的时候会将数据库状态进行冻结,并且只看到快照中的数据,其他线程在对数据库所做的更改对于该事务是不可见的,确保事务执行期间不会受到其他事务的影响。
slow log是一个用于记录数据库查询性能的日志,它记录了执行超过了特定阈值的事务。
CREATE TABLE `t` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`),
KEY `b` (`b`)
) ENGINE=InnoDB;
先创建一个这样的表然后向表中插入10万条记录
现执行select * from t where a between 10000 and 20000;
通过看慢日志可以看到其使用了a的索引是符合预期的
但是如果我们再执行下面的语句就会变的不一样。
set long_query_time=0;
select * from t where a between 10000 and 20000; /*Q1*/
select * from t force index(a) where a between 10000 and 20000;/*Q2*/
通过看慢查询日志可以发现,Q1扫描了10万行,走了全表扫描,使用了40毫秒,Q2扫了10001行,使用了21毫秒。
也就是说在没有使用force去指定索引使用的时候,MySQL用错了索引,选择索引是优化器做的事情,那么优化器的逻辑是什么呢?
对于扫描行数来说,扫描的行数越少,访问磁盘的次数越少优化就越成功。扫描行数并不是唯一的判断标准,是由排序等因素判断
但是这句简单的查询语句,必定是行数出现了问题。
扫描行数判断是使用采样统计来进行计算的,对于索引来说,当一个索引上的不同值越多,索引的区分度就越好,一个索引上不同值的个数被称为“基数”。
MySQL通过选择N个数据页,统计这些页面上的不同值得到一个平均值,然后乘以这个索引的页面数就得到了基数。
MYSQL中有两种索引统计的方式
设置参数innodb_stats_persistent的值来选择
1. on:统计信息会持久化存储,这个时候默认N=20,M=10
2.off:统计信息只会存储到内存中,这个时候N=8,M=16
由于是采样统计,所以他是不准确的
而且扫描的时候Q1扫描出来的rows为104620,Q2为37116,Q1扫出来的符合预期,Q2不符合,
那为什么放着三万行的扫描计划不用,而去扫描全局呢?
使用索引a,每次从a手上拿一个值,都要返回主键上查出整行的数据,这个代价也需要算进去,优化器会去估算着两个选择的代价,很明显他认为全局扫描更有利,当然从执行时间上来看,这并是不最优的。
当统计信息不对的时候,可以使用analyze table t的命令来重写统计索引信息。
索引选择异常和处理
第一种方法就是像我们的第一个例子一样,采用force index去强行采用一个索引。第二种方法是考虑修改语句,MySQL去使用我们期望的索引
order by b limit 1
order by b,a limit 1
修改之后,要求按照b,a排序,就意味着使用者两个索引都需要排序,因此扫描行数成为了影响决策的主要条件。
第三种方法是,在有些场景下,我们可以新建一个更合适的索引,来提供给优化器做选择,或删掉误用的索引。
如何给字符串加索引
现在若在表中去搜索一个字符串,当这个字符串没有索引的时候,就会进行全局索引。我们当然也可以去给他添加索引,MySQL是支持前缀索引的,如果我们写的索引不指定前缀长度,那么索引就会包含整个字符串。
alter table User add index index1(name);
alter table User add index index2(name(5));
在第一个语句创建的index1索引里面,包含了每个记录的整个字符串,第二个语句值取出、前5个字符来包含,这样一来就可以使得索引内存变小。
但是随之而来的存在着问题,那就是第二个索引有可能会增加扫描的次数,在使用第一个索引的时候,因为是全部的字符串,直接对比是否相等即可,使用第二个索引的时候,有可能name的前五个字符是相同的,这样就会使得扫描次数增加,增加返回主键进行扫描的次数。
为了解决这个问题,我们只需要定义好长度就可以即节省空间,又不用增加太多的查询成本。我们只需要之前知道到哪一位的区分度的最高即可。
前缀索引对覆盖索引的影响
如果语句要求返回id和name字段,当我们使用索引一的时候,因为使用的是name整个字段的索引,查到结果后就直接返回了,不需要到id再去查一次,但是如果使用索引二,就不得不返回到id索引去判断name字段的值。
就算你的索引二将范围开到了最大,包括了整个name字符,他也会去id判断,因为系统并不确定前缀索引的定义是否包含了完整的信息。
其他方式
当我们不存储name,而存储身份证号去查找的话,因为身份证的前几位都是一样的,我们设置前缀索引的用处不大,这个时候我们就可以去使用倒序存储来实现,使用MySQL的reverse的方法来实现
select fieldlist form t where id_card =reverse('input_id_card_string')
还有一种方法是使用hash字段,你可以在表上再创建一个整数字段,来保存身份证的校验码,同时在这个字段上创建索引,当你想要查抄身份证的时候,直接去查找他的校验码即可。