MySQL实战45讲_44 | 说说这些好问题

359 阅读3分钟

44 | 说说这些好问题

distinct 和 group by的性能

在第37篇文章《什么时候会使用内部临时表?》中,如果只需要去重,不需要执行聚合函数,distinct 和group by哪种效率高一些呢? 我来展开一下他的问题:如果表t的字段a上没有索引,那么下面这两条语句:

select a from t group by a order by null;
select distinct a from t;

的性能是不是相同的?

首先需要说明的是,这种group by的写法,并不是SQL标准的写法。标准的group by语句,是需要在select部分加一个聚合函数,比如:

select a,count(*) from t group by a order by null

这条语句的逻辑是:按照字段a分组,计算每组的a出现的次数。在这个结果里,由于做的是聚合计算,相同的a只出现一次。

没有了count(* )以后,也就是不再需要执行“计算总数”的逻辑时,第一条语句的逻辑就变成是:

按照字段a做分组,相同的a的值只返回一行。而这就是distinct的语义,所以不需要执行聚合函数时,distinct 和group by这两条语句的语义和执行流程是相同的,因此执行性能也相同。 这两条语句的执行流程是下面这样的。

  1. 创建一个临时表,临时表有一个字段a,并且在这个字段a上创建一个唯一索引;
  2. 遍历表t,依次取数据插入临时表中:
  • 如果发现唯一键冲突,就跳过;
  • 否则插入成功;
  1. 遍历完成后,将临时表作为结果集返回给客户端。

备库自增主键问题

第39篇文章《自增主键为什么不是连续的?》中,在binlog_format=statement时,语句A先获取id=1,然后语句B获取id=2;接着语句B提交,写binlog,然后语句A再写binlog。这时候,如果binlog重放,是不是会发生语句B的id为1,而语句A的id为2的不一致情况呢?

首先,这个问题默认了“自增id的生成顺序,和binlog的写入顺序可能是不同的”,这个理解是正确的。

其次,这个问题限定在statement格式下,也是对的。因为row格式的binlog就没有这个问题了,Write rowevent里面直接写了每一行的所有字段的值。

而至于为什么不会发生不一致的情况,我们来看一下下面的这个例子。

create table t(id int auto_increment primary key);
insert into t values(null);

image.png

可以看到,在insert语句之前,还有一句SETINSERT_ID=1。这条命令的意思是,这个线程里下一次需要用到自增值的时候,不论当前表的自增值是多少,固定用1这个值。

这个SETINSERT_ID语句是固定跟在insert语句之前的,主库上语句A的id是1,语句B的id是2,但是写入binlog的顺序先B后A,那么binlog就变成:

SET INSERT_ID=2;
语句B;
SET INSERT_ID=1;
语句A;

在备库上语句B用到的INSERT_ID依然是2,跟主库相同。

因此,即使两个INSERT语句在主备库的执行顺序不同,自增主键字段的值也不会不一致。