You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

插入name10时触发UNIQUE约束失败,求助SQLite问题排查

SQLite UNIQUE约束异常问题排查

问题描述

创建表结构如下:

CREATE TABLE IF NOT EXISTS image_table ("name" INTEGER PRIMARY KEY, "data" BLOB) WITHOUT ROWID;

随后准备插入语句,通过循环插入主键为name0、name1……name19的记录,但插入到name10时触发UNIQUE约束失败错误——即使主键值明显不重复,且绑定参数时传入的长度正确。后续改用TEXT类型主键后问题依然存在,且将绑定参数的销毁策略从SQLITE_STATIC改为SQLITE_TRANSIENT时,错误会提前到插入name1时触发。

最小复现代码(MRVC)

//#include "sqlite3.h"
#include <iostream>
#include <string>

int main()
{
    sqlite3* db;

    if (sqlite3_open("D:/images.db", &db) != SQLITE_OK)
        return 0;

    /* CREATE SQL STATEMENT */
    const char statement[] = R"(
CREATE TABLE IF NOT EXISTS image_table ("name" TEXT PRIMARY KEY, "data" BLOB) WITHOUT ROWID;
)";

    constexpr auto sizeofstatement = sizeof(statement);
    sqlite3_stmt* create_table_statement;
    int result = sqlite3_prepare_v2(db, statement, sizeof(statement), &create_table_statement, nullptr);
    if (result != SQLITE_OK) std::cout << "Failed to compile SQL statement";

    /* EXECUTE STATEMENT, CREATE TABLE */
    result = sqlite3_step(create_table_statement);

    if (!(result == SQLITE_OK || result == SQLITE_DONE))
    {
        std::cout << sqlite3_errmsg(db);
        return 0;
    }

    sqlite3_finalize(create_table_statement);

    const char insert_statement[] = R"(
        INSERT INTO image_table (name, data) VALUES (?1, ?2);
)";
    sqlite3_stmt* insert_row_statement;
    result = sqlite3_prepare_v2(db, insert_statement, sizeof(insert_statement), &insert_row_statement, nullptr);
    if (result != SQLITE_OK)
    {
        std::cout << sqlite3_errmsg(db);
        return 0;
        int a = 3;
    }

    const char blob_data[] = "This is blob data yada yada yada";

    for (int i = 0; i < 20; ++i)
    {
        std::string key_name = std::string("name").append(std::to_string(i)).c_str();
        auto key_size = key_name.size();
        result = sqlite3_bind_text(insert_row_statement, 1, key_name.data(), key_name.size(), SQLITE_STATIC);// 若改为SQLITE_TRANSIENT,错误会在i=1(key为"name1")时触发
        result = sqlite3_bind_blob(insert_row_statement, 2, blob_data, sizeof(blob_data), SQLITE_STATIC);
        result = sqlite3_step(insert_row_statement);

        if (!(result == SQLITE_OK || result == SQLITE_DONE))
        {// 插入"name10"时触发UNIQUE约束错误
            std::cout << "Error occurred, key_name: " << key_name << "\n";
            std::cout << sqlite3_errmsg(db) << "\n";
        }
    }

    sqlite3_finalize(insert_row_statement);
}

问题根源

异常并非SQLite特性问题,而是代码中的两个错误导致:

  1. 悬空指针问题:
    代码中std::string key_name = std::string("name").append(std::to_string(i)).c_str();这一行存在错误——c_str()返回的是临时std::string对象的底层指针,临时对象在赋值语句结束后会被销毁,导致key_name持有悬空指针。后续使用key_name.data()时,内存内容未定义,可能和之前的主键值重复,触发UNIQUE约束。
  2. 未重置语句绑定状态:
    循环中重复使用同一个sqlite3_stmt*对象,但每次执行sqlite3_step后未调用sqlite3_reset重置语句的绑定状态,导致后续绑定操作可能无法正确覆盖之前的参数值,造成主键重复的假象。

修复方案

  1. 修正字符串初始化:
    直接拼接字符串,避免悬空指针:
    std::string key_name = "name" + std::to_string(i);
    
  2. 循环内重置语句:
    在sqlite3_step执行完成后调用sqlite3_reset,确保下一次循环可以重新绑定参数:
    result = sqlite3_step(insert_row_statement);
    // 错误处理逻辑...
    sqlite3_reset(insert_row_statement);
    
  3. 调整绑定策略(可选):
    修复字符串问题后,SQLITE_STATIC或SQLITE_TRANSIENT都可以正常使用;若使用SQLITE_TRANSIENT,SQLite会主动复制参数内容,进一步避免内存问题。

内容的提问来源于stack exchange,提问作者Zebrafish

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 07:54:52