只需调查一个MySQL查询,该查询返回的记录比应有的多。
基本查询是:
SELECT
a.id AS parent_id,
b.id AS child_one_id,
c.*
FROM
parent AS a
LEFT JOIN child_one AS b ON a.id = b.parent_id
LEFT JOIN child_two AS c ON (
a.id = c.parent_id
AND c.user_id = '1234'
AND c.deleted_at IS NULL
)
WHERE (
a.user_id = '1234' OR c.parent_id IS NOT NULL
);
即父表保留连接到两个子表,两个子表之间未指定任何关系,并且过滤器仅应用于返回用户拥有记录或用户通过child_two链接到记录的结果。>
据我对LEFT JOIN的理解,如果数据库在右侧表中找不到匹配的记录,它将生成一个“空”记录,且所有值都设置为NULL。
但是,在这种情况下,它会生成一些记录,其中“空”记录似乎是通过隐式类型转换完成的。
# parent_id, child_one_id, id, parent_id, created_at, updated_at, deleted_at
27 , 29 , , , , ,
27 , 32 , 0, 0, 0000-00-00 00:00:00, 0000-00-00 00:00:00,
27 , 47 , 0, 0, 0000-00-00 00:00:00, 0000-00-00 00:00:00,
30 , 26 , , , , ,
30 , 46 , 0, 0, 0000-00-00 00:00:00, 0000-00-00 00:00:00,
即对于某些记录,它将为child_two生成一个“ NULL”记录,但是对于其他记录,它似乎已经执行了类型转换以为child_two创建一个“归零”记录。
(而且在任何人问之前,不,在child_two中没有任何ID为零的记录!)
我还证明了有两种方法可以阻止生成归零记录:
1)从查询中删除child_one
2)将child_one的联接更改为“ LEFT JOIN child_one AS b ON (a.id = b.parent_id AND b.user_id = '1234')
”
但是,这不能解释为什么触发这种类型转换,或者为什么只为某些记录触发这种类型转换。遗憾的是,我尽最大努力锻炼自己的Google-fu未能挖掘出任何可能的解释。
因此,尽管我已经有了解决该问题的解决方案,但我还是非常想了解为什么/如何创建“清零”记录-我们在列表中使用“ LEFT JOIN x ... WHERE x.id IS NULL
”地方数!
更新: 我设法在某些临时表克隆中重现了该问题,减少了列数,每个表中的记录数也最少。
http://sqlfiddle.com/#!9/faa3d0/1
使用上述SQL,我已经能够在生产服务器上的多个架构上重现该问题。 但是,它不会在我们的测试或开发服务器上发生,也不会在sqlfiddle中发生。
所以这似乎是我们生产服务器所特有的。
有趣的一点是,如果我将主索引放在child_two上,则该问题不再发生。尽管解释计划表明查询仍在使用“ job_id”索引来检索数据。
当前,我们正在以下MySQL版本上运行:
并且sqlfiddle在MySQL 5.6上运行。
这表明这是Percona 5.7版本中的一个问题,该问题在5.7.11-4版本之后的某个时候得到解决。但是,我在发行说明中看不到任何潜在的候选者。
我们将在不久的将来升级生产服务器,因此希望该问题能够得到解决。同时,如果有人能够识别解决该问题的补丁程序(和/或可以用来缓解该问题的任何配置),将不胜感激!