PHP 中 SELECT 正常但 INSERT 未写入 MySQL
明确结论
问题就在这行代码:
$this->db->autocommit(FALSE);
它关闭了自动提交,但脚本执行完 INSERT 后并没有调用:
$this->db->commit();
所以,这条记录只存在于当前数据库连接尚未提交的事务中。随后执行的 SELECT 使用同一个连接,可以读到这条未提交的记录。等到连接在析构函数中关闭,未提交的事务会被回滚,记录也就不会保存到数据库。
这也是 $stmt->error 为空的原因。INSERT 本身已经执行成功,问题只是事务没有提交。
解决方法
方法一:显式提交事务
如果需要手动控制事务,应在所有操作成功后调用 commit(),出现异常时调用 rollback():
class RedeemAPI
{
private mysqli $db;
public function __construct()
{
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$this->db = new mysqli(
'localhost',
'root',
'password',
'testBasParmak'
);
$this->db->set_charset('utf8mb4');
$this->db->autocommit(false);
}
public function __destruct()
{
$this->db->close();
}
public function redeem(): bool
{
if (
!isset(
$_POST['users_id'],
$_POST['name'],
$_POST['url'],
$_POST['description']
)
) {
sendResponse(400, 'Invalid request');
return false;
}
$users_id = (int) $_POST['users_id'];
$name = $_POST['name'];
$url = $_POST['url'];
$description = $_POST['description'];
try {
$stmt = $this->db->prepare(
'INSERT INTO pictures
(name, description, url, users_id)
VALUES (?, ?, ?, ?)'
);
$stmt->bind_param(
'sssi',
$name,
$description,
$url,
$users_id
);
$stmt->execute();
$stmt->close();
$this->db->commit();
return true;
} catch (Throwable $e) {
$this->db->rollback();
error_log($e->getMessage());
sendResponse(500, 'Database operation failed');
return false;
}
}
}
其中最重要的是:
$this->db->commit();
应在确认需要作为一个整体执行的数据库操作全部成功后调用它。
方法二:保留自动提交
如果一次请求只执行一条独立的 INSERT,一般不需要手动管理事务。可以删除这行代码:
$this->db->autocommit(FALSE);
也可以明确开启自动提交:
$this->db->autocommit(true);
这样,每条成功执行的 SQL 都会自动提交。
简化后的写法如下:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$db = new mysqli(
'localhost',
'root',
'password',
'testBasParmak'
);
$db->set_charset('utf8mb4');
$users_id = (int) $_POST['users_id'];
$name = $_POST['name'];
$description = $_POST['description'];
$url = $_POST['url'];
$stmt = $db->prepare(
'INSERT INTO pictures
(name, description, url, users_id)
VALUES (?, ?, ?, ?)'
);
$stmt->bind_param(
'sssi',
$name,
$description,
$url,
$users_id
);
$stmt->execute();
printf("Inserted ID: %d\n", $db->insert_id);
$stmt->close();
$db->close();
为什么同一脚本中的 SELECT 能看到记录
事务中的修改对当前连接立即可见,但在提交之前,其他数据库连接通常看不到这些修改:
当前 PHP 连接:
INSERT → SELECT 可以看到 → 未 commit → 关闭连接 → rollback
phpMyAdmin 连接:
事务未提交,所以看不到该记录
因此,脚本中的 SELECT 能查到记录,并不代表记录已经提交。它只能说明当前事务可以读取自己刚写入的数据。
建议开启 mysqli 异常报告
只检查 $stmt->error,可能会漏掉连接、预处理语句或事务阶段的错误。可以在建立连接前启用严格错误报告:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
启用后,如果 prepare()、bind_param()、execute() 或 commit() 失败,程序会抛出异常,便于统一使用 try/catch 处理。
如果不使用异常模式,也应分别检查返回值:
if (!$stmt->execute()) {
printf("Execute failed: %s\n", $stmt->error);
}
提交操作同样需要检查:
if (!$this->db->commit()) {
printf("Commit failed: %s\n", $this->db->error);
}
数据表还需要注意的地方
表中的以下字段声明为 NOT NULL,但 INSERT 没有给它们赋值:
totalvoteup
totalvotedown
totalvoteneutral
在某些非严格 SQL 模式下,MySQL 可能会为这些整数列使用隐式值 0。在严格模式下,插入操作可能会被直接拒绝。更稳妥的做法是给这些计数字段设置默认值:
ALTER TABLE pictures
MODIFY totalvoteup int(11) NOT NULL DEFAULT 0,
MODIFY totalvotedown int(11) NOT NULL DEFAULT 0,
MODIFY totalvoteneutral int(11) NOT NULL DEFAULT 0;
也可以在 INSERT 中明确写入这些值:
INSERT INTO pictures (
name,
description,
url,
users_id,
totalvoteup,
totalvotedown,
totalvoteneutral
) VALUES (?, ?, ?, ?, 0, 0, 0)
不过,这并不是当前记录执行成功却没有持久化的主要原因。直接原因仍是自动提交被关闭,而代码没有执行 commit()。
备注:内容仅供参考。
