PAYATHON 2026

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()。

备注:内容仅供参考。