首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

Yii Query Builder 陷阱:insert/update/delete 返回值語義差異深度解析

     編輯:PHP 相關知識

問題現場還原

在使用 Yii 1.1 的 CDbCommand 執行寫操作時,開發者習慣用返回值判斷成功與否:

$affected = Yii::app()->db->createCommand()
    ->update('emg_landing', ['status' => 1], 'lid=:lid', [':lid' => $id]);

if ($affected > 0) {
    returnApi::success('更新成功');
} else {
    returnApi::error('更新失敗'); // 💥 這裡誤判!
}

現象:數據實際更新了,但代碼進入了「失敗」分支。排查半天無果,翻閱官方文檔才恍然大悟。

官方文檔定義的返回值語義

方法返回值含義判斷成功的正確條件
insert()受影響行數(通常為 1)>= 1 即成功
delete()受影響行數(刪除的行數)>= 1 即成功
update()真正被修改的行數>= 0 均可能成功!

核心差異解讀

  • insert/delete:只要語句執行無異常,必然改變了表的行數,返回值 >= 1 直接等價於成功。
  • update:WHERE 條件命中行數 ≠ 實際修改行數。若 SET col = val 中 val 與原值相同,MySQL 不會真正寫入磁盤,該行不計入「受影響行數」,返回 0。但 SQL 執行本身是成功的,數據邏輯上也「更新為相同值」。

實戰驗證代碼

<?php
// 假設表 user(id PK, name, status) 有一條 id=1, name='Tom', status=1
$db = Yii::app()->db;

// 情況 A:更新為不同值
$r1 = $db->createCommand()->update('user', ['status' => 2], 'id=1');
var_dump($r1); // int(1)  真正修改了

// 情況 B:更新為相同值
$r2 = $db->createCommand()->update('user', ['status' => 2], 'id=1');
var_dump($r2); // int(0)  數據無變化,但 SQL 執行成功!

// 情況 C:WHERE 不命中任何行
$r3 = $db->createCommand()->update('user', ['status' => 3], 'id=999');
var_dump($r3); // int(0)  同樣返回 0,但語義是「無匹配行」

正確的判斷邏輯封裝

<?php
/**
 * 安全執行更新並正確判斷結果
 * @return array ['success' => bool, 'info' => string, 'affected' => int]
 */
function safeUpdate($table, $columns, $condition, $params = []) {
    $cmd = Yii::app()->db->createCommand();
    $affected = $cmd->update($table, $columns, $condition, $params);
    $error = $cmd->getDbConnection()->errorInfo(); // [SQLSTATE, driverCode, driverMsg]
    
    if ($error[0] !== '00000') {
        return ['success' => false, 'info' => 'SQL錯誤: '.$error[2], 'affected' => $affected];
    }
    
    // 執行無異常即視為成功,affected=0 僅表示「無實際數據變更」
    return ['success' => true, 'info' => $affected > 0 ? '數據已變更' : '數據無變化(或條件不匹配)', 'affected' => $affected];
}

// 使用示例
$res = safeUpdate('emg_landing', ['status' => 1], 'lid=:lid', [':lid' => $id]);
if ($res['success']) {
    returnApi::success($res['info']);
} else {
    returnApi::error($res['info']);
}

進階:如何區分「無匹配」與「值未變」?

若業務強制要求區分(如「用戶不存在」vs「狀態已是目標值」),需額外查詢:

function updateWithDetail($table, $columns, $condition, $params) {
    $db = Yii::app()->db;
    // 1. 先查是否存在匹配行
    $exists = $db->createCommand()
        ->select('COUNT(*)')
        ->from($table)
        ->where($condition, $params)
        ->queryScalar();
    
    if (!$exists) {
        return ['code' => 'NOT_FOUND', 'msg' => '記錄不存在'];
    }
    
    // 2. 執行更新
    $affected = $db->createCommand()->update($table, $columns, $condition, $params);
    
    return $affected > 0 
        ? ['code' => 'UPDATED', 'msg' => '更新成功'] 
        : ['code' => 'NO_CHANGE', 'msg' => '數據已是目標值,無需更新'];
}

同理擴展:delete() 的邊界情況

delete() 返回刪除行數。若 WHERE 不命中,返回 0,此時 語義明確為「無匹配行可刪」,通常視為業務失敗(如「取消訂單」時訂單已不存在)。但若業務允許「冪等刪除」,則 0 也可視為成功。需根據具體業務定義。

總結速查表

場景推薦判斷邏輯
insert$affected >= 1 成功,否則拋異常/查錯誤碼
delete視業務:強一致性要求 >=1;冪等允許 >=0 並檢查 SQLSTATE
update永不直接用 affected 判斷成敗。檢查 SQLSTATE == '00000' 即成功,affected 只作日誌/統計用
💡 核心心法:受影響行數 ≠ 執行成敗。Yii/CDbCommand 只是透傳 PDO::exec() 返回值,PDO 文檔明確說明:「For UPDATE statements, the return value is the number of rows actually changed.」 理解數據庫引擎層面的「修改」定義,才能寫出不踩坑的代碼。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved