直接用 createCommand() 执行原生 SQL,增删改统一用 execute() 返回影响行数,查询用 queryAll()/queryOne()/queryScalar() 区分结果类型,务必绑定参数防注入,复杂条件用表达式数组,获取自增 ID 须调 getLastInsertID()。

直接用 createCommand() 就行,不用绕模型、不走 AR,但必须注意参数绑定和返回值类型差异。
原生 SQL 增删改统一用 execute()
插入、更新、删除都走同一个方法,返回的是影响的行数(int),不是数据本身:
-
insert语句必须用createCommand()->insert()或拼完整 SQL,不能只传 SQL 字符串再调execute()—— 否则 PDO 不会自动转义,容易 SQL 注入 - 推荐写法:
Yii::$app->db->createCommand()->insert('user', ['name' => 'tom', 'age' => 25])->execute(); - 手动拼 SQL 时,务必用命名占位符(如
:name)+bindParam()或传参数组:Yii::$app->db->createCommand("INSERT INTO user (name, age) VALUES (:name, :age)", [':name' => 'tom', ':age' => 25])->execute(); - 批量插入用
batchInsert(),比循环单条insert()快得多,也更安全
原生 SQL 查询必须分清 query()、queryAll()、queryOne()
它们返回的东西完全不同,混用会导致运行时错误或逻辑错乱:
-
query()返回yii\db\Command自带的DataReader对象,要读数据得手动调read()或readAll(),日常几乎不用 -
queryAll()直接返回二维数组(每行是关联数组),适合查列表:Yii::$app->db->createCommand("SELECT id, name FROM user")->queryAll(); -
queryOne()返回一维数组(单行),适合查唯一记录:Yii::$app->db->createCommand("SELECT * FROM user WHERE id = 1")->queryOne(); - 如果只想要某个字段的单个值(比如统计数、最大 ID),别用
queryOne()再取键,直接用queryScalar():Yii::$app->db->createCommand("SELECT COUNT(*) FROM user")->queryScalar();
createCommand() 的 where 条件写法容易踩坑
原生 SQL 拼接中,where 部分最容易出安全问题或语法错误:
- 字符串值必须加引号,但
createCommand()->delete('user', 'status = 0')这种写法是危险的——它没做任何转义,用户输入直接进 SQL - 正确做法是把条件写成数组:
Yii::$app->db->createCommand()->delete('user', ['status' => 0])->execute();,框架会自动处理引号和转义 - 复杂条件(含 OR、IN、NULL 判断)得用表达式对象:
['or', ['status' => 0], ['is_deleted' => 1]],而不是手拼"status = 0 OR is_deleted = 1" - 用命名占位符时,
WHERE子句必须写在 SQL 字符串里,参数单独传:createCommand("SELECT * FROM user WHERE name = :name", [':name' => $input])->queryOne()
获取刚插入的 ID 要用 getLastInsertID(),不是返回值
execute() 返回的是影响行数(比如 insert 成功返回 1),不是新记录 ID。主键是自增时,必须额外调一次:
- 先执行插入:
$cmd = Yii::$app->db->createCommand()->insert('user', ['name' => 'jack']); $cmd->execute(); - 再取 ID:
$id = Yii::$app->db->getLastInsertID();—— 注意这不是$cmd->getLastInsertID(),而是 DB 组件的方法 - 如果用了事务,
getLastInsertID()仍有效;但跨连接(比如主从分离)时不可靠,此时建议改用SELECT LAST_INSERT_ID()显式查
真正麻烦的不是写法,而是混合使用原生 SQL 和 AR 时的事务边界、连接复用、以及调试时看不到完整 SQL —— 建议所有原生操作都显式调 getRawSql() 看最终语句,尤其上线前。


















