union 在 hyperf + mysql 中可用,但需手写原生 sql 并严格控制列数、类型兼容性与 union all 选择,query builder 不支持链式 union,字段名以首 select 为准,order by/limit 仅作用于最终结果。

直接说结论:UNION 在 Hyperf + MySQL 场景下能用,但默认客户端(hyperf/database 基于 PDO)不自动处理列名对齐、类型隐式转换或去重逻辑——这些都得你手写 SQL 控制,否则查出来字段错位、NULL 被吞、甚至报 SQLSTATE[HY000]: General error。
Hyperf 中执行 UNION 必须手写原生 SQL
Hyperf 的 Query\Builder(比如 $this->db->table('a')->select(...))不支持链式调用多个 SELECT 拼 UNION。它只生成单个 SELECT 语句,强行组合会语法错误。
- ✅ 正确做法:用
$this->db->select()或$this->db->query()->execute()执行完整字符串 SQL - ❌ 错误尝试:
$this->db->table('a')->select('id','name')->union($this->db->table('b')->select('id','title'))—— 这不是 Laravel,Hyperf 没实现这个 API - ⚠️ 注意:传入的 SQL 字符串里不能带 PHP 变量拼接,必须用参数绑定,否则有注入风险
列数、顺序、类型不一致时 MySQL 直接报错
Hyperf 不做预校验,错全由 MySQL 抛出,常见报错如:ERROR 1222 (21000): The used SELECT statements have a different number of columns 或 Truncated incorrect DOUBLE value(类型隐式转换失败)。
- 列数必须严格相等:
SELECT id, name FROM users和SELECT user_id, nickname FROM admins看似一样,但字段名不同不影响,数量和类型才关键 - 类型要兼容:比如
VARCHAR(20)和TEXT可 union,但INT和JSON就不行;数值列混NULL时,MySQL 会按“最宽类型”推导,可能截断 - 建议统一显式转换:
CAST(nickname AS CHAR)、COALESCE(age, 0),避免依赖隐式行为
UNION vs UNION ALL 性能差异在 Hyperf 里更明显
Hyperf 默认开启 PDO 的 PDO::ATTR_EMULATE_PREPARES = false,意味着 MySQL 真实执行计划会被完整暴露。用 UNION 时,Explain 常见 Using temporary; Using filesort,而 UNION ALL 几乎总是 Using where 单次扫描。
- 业务上真需要去重?优先考虑应用层 dedup(
array_unique处理结果),而不是让 MySQL 做;尤其当子查询本身已无重复(如分表查询、状态互斥筛选) - 想在外层去重又保留
UNION ALL性能?套一层子查询:SELECT DISTINCT * FROM (SELECT ... UNION ALL SELECT ...) AS t - Hyperf 日志里看到慢查询,先
EXPLAIN FORMAT=TRADITIONAL跑一遍原生 SQL,别猜
ORDER BY 和 LIMIT 只能放在最后,且字段名以第一个 SELECT 为准
Hyperf 执行后返回的 Hyperf\Database\Query\Builder 结果对象,字段键名来自第一个 SELECT 的 AS 别名(或原始列名)。如果第二个 SELECT 用了不同别名,最终 PHP 数组里对应键会是第一个的,不是你写的那个。
-
ORDER BY必须写在最后一个SELECT后面,不能写在中间子句里 - 想对每个子查询单独排序再合并?不行。只能用子查询封装:
(SELECT * FROM a ORDER BY ts DESC LIMIT 10) UNION ALL (SELECT * FROM b ORDER BY ts DESC LIMIT 10) -
LIMIT同理:整个 UNION 结果限制用LIMIT,子查询限制必须包在括号里
真正麻烦的不是语法,而是 Hyperf 不报错地把类型不匹配的字段“凑合”查出来——比如 user_id INT 和 admin_id VARCHAR union 后,PHP 拿到的可能是字符串化的数字,后续做 in_array(123, $ids) 就失效。这种隐性问题,只有压测时数据量上来才暴露。











