必须使用参数化查询(如$this->db->query("WHERE status = ?", $param)),禁用字符串拼接;模型中需显式加载数据库并用dbprefix()适配表名,返回结果须调用result_array()等方法提取,不可直接遍历对象。

自定义控制器里怎么安全执行原生 SQL
直接用 $this->db->query() 是最常用方式,但必须注意参数绑定和转义问题。CI 不会自动转义字符串拼接的 SQL,只对占位符(?)做处理。
- 正确写法:使用数组传参,让 CI 自动转义 ——
$this->db->query("SELECT * FROM users WHERE status = ? AND role IN ?", array('active', array('admin', 'editor'))) - 错误写法:字符串拼接 ——
$this->db->query("SELECT * FROM users WHERE name = '" . $name . "'"),极易 SQL 注入 - 单个参数不用数组:
$this->db->query("SELECT * FROM users WHERE id = ?", $id) - 查询后记得检查结果:
if ($query && $query->num_rows() > 0) { ... }
自定义模型中封装原生查询要注意什么
在自定义模型(如 User_model)里写原生查询,核心是别漏掉 $this->db 实例,且不能依赖构造函数自动加载数据库 —— 某些 CI 版本下模型不自动加载 DB 类。
- 务必显式加载:
$this->load->database()或确保已在autoload.php中配置$autoload['libraries'] = array('database') - 返回结果建议统一用
result_array(),避免后续在视图里混用对象/数组访问方式 - 带条件的复杂查询建议拆成变量,提高可读性:
$sql = "SELECT u.*, p.title FROM users u JOIN posts p ON u.id = p.author_id WHERE u.status = ? ORDER BY p.created_at DESC";<br>$query = $this->db->query($sql, array('active')); - 别在模型里硬编码表名,用
$this->db->dbprefix('users')替代'users',适配前缀配置
为什么 get() 和 query() 返回的数据结构不一样
$this->db->get() 返回的是 CI 的 CI_DB_result 对象,而 $this->db->query() 同样返回该对象,但很多人误以为“原生 query 就是数组”——其实不是,调用方式决定取值方式。
- 两者都必须用
result()、result_array()、row()等方法取数据,不能直接foreach($query) -
$query->result()→ 对象数组:$row->username -
$query->result_array()→ 关联数组:$row['username'] - 查单行推荐
row_array(),比result_array()[0]更安全(空结果不报错)
调试原生 SQL 时最容易忽略的两件事
开发阶段靠 $this->db->last_query() 看语句很有效,但上线后常因缓存或多库切换导致看到的不是真实执行的 SQL。
-
last_query()只返回最近一次执行的语句,如果中间穿插了其他查询(比如日志记录、权限校验),就不是你想要的那条 - 多数据库连接时,每个 DB 实例有独立的
last_query(),要用对应实例调用,比如$obj->last_query()而非$this->db->last_query() - 真正要确认执行逻辑,建议在
query()前加日志:log_message('debug', 'SQL: ' . $sql . ' | Params: ' . print_r($params, true))
原生 SQL 在自定义类里不是不能用,而是得守住参数绑定和实例生命周期这两条线 —— 漏掉任意一个,轻则数据错乱,重则被注入。











