天天看点

TokuDB · 捉虫动态 · MRR 导致查询失败

最近有用户在使用 tokudb 时,遇到了一个查询报错的问题,这里给大家分享下。

具体的报错信息是这样的:

表结构如下:

从报错信息来看,是引擎层返回错误的,难道是 tokudb 数据出问题了么,我们首先要确认的是用户数据是否还能访问。

从表结构来看,出错的语句应该走了二级索引,那么我们强制走 pk 是否能访问数据呢。

上面的测试可以说明走 pk 是没问题呢,那么问题可能在二级索引。

同时我们在观察用户的其它 sql 时发现,二级索引也是可以访问数据的。

比如下面这种:

都是走二级索引,为什么有的会报错呢,这 2 条语句有啥区别呢,explain 看下:

把这个优化关掉呢?

可以看到,关掉优化器的 mrr 后,语句就返回正常了。因此基本可以判断是 mrr 导致的。

下面我们从源码层面分析下看,到底是怎么回事。

根据报错信息,来 gdb 跟踪,发现导致报错的栈是这样的,可以看到是在 mrr 执行初始化阶段:

具体在 dsmrr_impl::dsmrr_init 中的逻辑是这样的:

我们对应看下 tokudb 里条件下推接口实现:

可以看到 <code>ha_tokudb::idx_cond_push</code> 是会将原条件在返回给 server 的。因此就导致了 <code>dsmrr_impl::dsmrr_init</code> 返回错误码 1 (got error 1 from storage engine)。

而 <code>handler:idx_cond_push()</code> 接口是允许引擎层返回非 null 值的,引擎层认为自己没有完全过滤结果集,那么是可以返回条件给 server 层,让 server 层再做一次过滤的:

因此这个问题是 mrr 在实现上的一个 bug,没有考虑引擎在icp时返回非 null 的情况。

另外我们在查问题时发现,如果 mysqld 重启或者通过 flush table 关闭表的话,查询是不会出错的:

从 explain 结果看,是因为没有用到 mrr,这又是为什么呢?

我们看下优化器是如何选择是否用mrr优化的,在 <code>dsmrr_impl::choose_mrr_impl()</code> 这个函数里是有这样的逻辑的:

可以看到,mrr 选择条件是这样的:

如果引擎的 cache 比表大的话,是不会用 mrr 优化的;

如果引擎没有 cache,默认用 100m,用于自己不管理 cache 引擎,如 myisam;

如果要查询的行数不超过50的话,也是不会用 mrr 优化的;

这个 cache 对 innodb 来说,就是 <code>innodb_buffer_pool_size</code>;对 tokudb 来说,就是 <code>tokudb_cache_size</code>。但是 tokudb handler 层没有实现 <code>get_memory_buffer_size()</code> 这个接口,导致一直用 100m 做为 cache 来判断,这个是 tokudb handler 实现的上的一个bug。

而 <code>data_file_length</code> 这个是值是内存信息,在表刚关闭重新打开的时候,是0,所以不会用mrr优化。

另外还有一个判断条件时,如果要求排序的话,也是不会用 mrr 优化的,这也就是为什么我们刚开始发现的,语句中用了 order by 后,explain 结果中就没有 mrr了。

从上面的分析来看,满足下面条件语句会被影响:

语句访问的是 tokudb 表,并且走的二级索引,有回表操作;

表大小超过 100m;

简单的判断方法是,explain 结果中有 <code>using index condition; using where; using mrr</code>,并且语句报错 got error 1 from storage engine。

临时的解决方法是关闭优化器的 mrr 或者 icp:

继续阅读