ci中order_by必须在get()前调用,多个order_by叠加生效,支持field()等原生函数需禁用转义,排序字段和方向须白名单校验以防sql注入。

CI 中 order_by 的实际调用位置和顺序限制
CodeIgniter 的 order_by 必须在 get() 或 result() 之前调用,且不能放在 limit() 之后 —— 否则会被忽略。CI 的查询链式调用是「构建阶段」,所有条件(where、order_by、limit)最终拼成一条 SQL,而执行时机只在 get() 或 query() 触发时。
常见错误现象:$this->db->get('users')->order_by('id', 'DESC') 这种写法无效,order_by 被调用时查询早已生成并执行完毕。
-
order_by()必须在get()前,例如:$this->db->order_by('created_at', 'DESC')->get('posts') - 多个
order_by()会叠加,后调用的排在排序优先级右侧(即次要排序):$this->db->order_by('status')->order_by('updated_at', 'DESC')等价于ORDER BY status, updated_at DESC - 不支持直接传入表达式或函数,如
order_by('LENGTH(title)')会报错或被当作文本字面量处理;需改用order_by('LENGTH(title)', '', FALSE)关闭自动转义(第三个参数设为FALSE)
用 order_by 实现字段值自定义顺序(非字母/数字)
CI 原生不提供类似 MySQL FIELD() 的函数封装,但可通过原始 SQL 片段绕过限制。关键点:避免引号包裹字段值列表,否则会被 CI 自动加反斜杠或单引号,导致语法错误。
使用场景:状态字段 status 值为 'draft'、'review'、'published',要求按此顺序排,而非字母序(draft 在 published 前,但字母序是 draft → published → review)。
- 正确写法:
$this->db->order_by("FIELD(status, 'draft', 'review', 'published')", '', FALSE) - 错误写法:
$this->db->order_by("FIELD(status, 'draft', 'review', 'published')")(CI 会把整个字符串当列名加反引号,变成`FIELD(status, 'draft', 'review', 'published')`,SQL 报错) - 兼容性注意:该方式强依赖 MySQL;换到 PostgreSQL 需改用
CASE WHEN,CI 中写法为:$this->db->order_by("CASE status WHEN 'draft' THEN 1 WHEN 'review' THEN 2 ELSE 3 END", '', FALSE)
order_by 和 limit / offset 的协作陷阱
CI 的 limit(10, 20) 表示 “取第 21–30 条”,但若没配 order_by,数据库返回结果无序,分页结果每次可能不同 —— 这不是 CI 的 bug,而是 SQL 标准行为。
更隐蔽的问题:当 order_by 字段存在重复值(如多人同分数),limit 可能漏掉或重复某几条记录,尤其跨页时。
- 必须搭配确定性排序:例如
$this->db->order_by('score', 'DESC')->order_by('id', 'ASC'),用主键补全唯一性 - 不要在
order_by中混用 ASC/DESC 与计算字段:如order_by('score * weight', 'DESC'),MySQL 可能无法走索引,CI 不报错但性能骤降 - CI 3.x 不支持
OFFSET ... FETCH语法,如需跳过前 N 条并取 M 条,只能靠limit(M, N),且N必须是整数,不能是变量表达式
在 CI 查询中安全注入动态排序字段和方向
用户可控的排序参数(如前端传 sort=price&dir=desc)直接拼进 order_by() 极易引发 SQL 注入。CI 的自动转义只保护值,不校验字段名或关键字。
不能这么做:$this->db->order_by($_GET['sort'], $_GET['dir']) —— 攻击者传 sort=id; DROP TABLE users-- 就完蛋。
- 白名单校验字段:
$allowed_fields = ['name', 'price', 'created_at']; if (!in_array($field, $allowed_fields)) { $field = 'id'; } - 白名单校验方向:
$dir = in_array(strtolower($dir), ['asc', 'desc']) ? strtoupper($dir) : 'ASC'; - 禁止使用别名或表达式作为排序字段名;若业务真需按别名排序(如
SELECT price*12 AS annual),应在 PHP 层映射回真实字段:if ($field === 'annual') $field = 'price';
真正麻烦的不是语法怎么写,而是排序字段重复、NULL 值分布、索引缺失这三者叠加时,order_by 表现会和预期严重偏离 —— CI 只负责拼 SQL,不替你查执行计划。











