MySQL深入——4

普通索引和唯一索引该如何选择

我们先来谈谈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字段,你可以在表上再创建一个整数字段,来保存身份证的校验码,同时在这个字段上创建索引,当你想要查抄身份证的时候,直接去查找他的校验码即可。

你可能感兴趣的:(Mysql数据库基础及深入理解,mysql,数据库)