使用IN子句的Prepared Statement查询MySQL8.0分区表无返回行问题
问题描述
- 存在两张结构完全一致的表:
t1为普通表,t2为按DAYOFMONTH(created_at)设置范围分区的分区表,两张表插入了完全相同的日期数据 - 通过PHP MySQLi执行两条预编译查询语句:
- 使用
IN子句的查询 - 使用
OR多条件的查询
- 使用
- 查询结果出现差异:
t1的两条语句均返回2行目标数据t2使用OR条件的查询返回2行数据,但使用IN子句的查询返回0行
- 补充测试结果:
- 当
IN子句仅传入单个日期值时,t2的预编译查询可正常返回数据;传入多个值则失效 - 使用MySQL CLI或MySQLi非预编译语句执行
IN子句查询时,t2可正常返回结果 - 尝试通过
bind_param绑定多参数的方式,也无法解决该问题
- 当
- 运行环境:MySQL 8.0.32、PHP 8.1.15、Fedora Linux 37
原因分析
- 分区修剪与预编译
IN子句的兼容性问题:MySQL的分区修剪(Partition Pruning)逻辑在处理预编译语句中的多值IN子句时,无法正确解析绑定的参数组,导致无法匹配到存储目标数据的分区,最终返回空结果。 - 预编译参数解析逻辑差异:预编译语句中,
IN子句的多参数会被MySQL作为整体参数处理,而基于DAYOFMONTH(created_at)的分区规则无法与这种参数形式联动,跳过了所有包含数据的分区;而非预编译语句或单值预编译语句中,MySQL能直接解析值并执行正确的分区筛选。 - 特定版本bug:该问题大概率是MySQL 8.0.32版本中,预编译语句与分区表交互的特定兼容性bug,在非预编译场景或更高版本中可能已修复。
解决方法
- 改用
OR条件替代IN子句:将IN ('2024-05-01', '2024-05-02')改写为created_at = '2024-05-01' OR created_at = '2024-05-02',利用OR条件能正常触发分区修剪的特性完成查询。 - 动态生成安全的
IN子句(需防注入):如果业务依赖IN语法,可在PHP中动态拼接包含占位符的IN子句,再通过bind_param绑定所有参数,示例代码:
注:此方式需确保输入的日期值经过严格校验,避免SQL注入风险。$dates = ['2024-05-01', '2024-05-02']; $placeholders = implode(',', array_fill(0, count($dates), '?')); $stmt = $mysqli->prepare("SELECT * FROM t2 WHERE created_at IN ($placeholders)"); $types = str_repeat('s', count($dates)); $stmt->bind_param($types, ...$dates); $stmt->execute(); - 调整分区策略:将分区键改为直接使用
created_at(例如按月份或日期范围分区),而非基于DAYOFMONTH()的计算值,减少分区修剪与预编译语句的冲突。 - 升级MySQL版本:尝试将MySQL升级到8.0.32之后的稳定版本,查看官方修复日志,确认是否已解决该预编译语句与分区表的交互bug。
内容的提问来源于stack exchange,提问作者Jens
相关产品推荐
相关产品推荐

