Android SQLite复合主键场景下唯一约束失败问题排查
看起来你遇到了个有点迷惑的问题——明明设置了4字段的复合主键(GAME、PLAYER、TYPE、MINUTE),但插入数据时却因为同一GAME+PLAYER组合触发了UNIQUE约束错误。我来帮你拆解可能的原因和解决办法:
可能的原因分析
1. 插入的四条字段值其实完全重复了
复合主键的约束是四个字段的组合必须唯一,而非只要GAME+PLAYER不重复就行。如果你插入的新记录,GAME、PLAYER、TYPE、MINUTE这四个值和表中已有记录完全一致,那必然会触发UNIQUE约束错误。你可能误以为只要GAME+PLAYER重复就触发,但实际上是四个字段的组合重复才会冲突。
2. 实际表结构和你写的建表语句不一致
SQLite的CREATE TABLE IF NOT EXISTS只会在表不存在时执行创建逻辑。如果之前你曾经用不同的主键规则(比如只把GAME+PLAYER设为主键)创建过这个表,后来修改了建表语句但没有重建表,那么表的实际主键还是旧的规则。这时候插入同一GAME+PLAYER的记录自然会触发冲突。
3. 表上存在额外的UNIQUE约束
除了主键之外,可能你(或者之前的代码)给表添加了单独的UNIQUE索引,比如只针对GAME和PLAYER的联合唯一约束。这种情况下,即使复合主键是四个字段,额外的约束也会限制GAME+PLAYER的组合唯一。
排查和解决步骤
第一步:验证实际表结构
执行以下SQL命令查看表的实际结构,确认主键字段是否正确:
PRAGMA table_info(TABLE_EVENT);
查看结果中的pk列,确认GAME、PLAYER、TYPE、MINUTE这四个字段的pk值都是1(SQLite会给复合主键的每个字段标记pk为1,顺序对应主键定义的顺序)。
第二步:检查已有数据
针对你要插入的GAME和PLAYER值,查询表中已有记录:
SELECT * FROM TABLE_EVENT WHERE GAME = ? AND PLAYER = ?;
对比查询结果中的TYPE和MINUTE值,看看是不是和你要插入的新记录完全一致。如果是,那就是正常的主键冲突,需要调整TYPE或MINUTE的值。
第三步:查看所有索引
执行以下命令查看表上的所有索引,确认有没有额外的UNIQUE约束:
PRAGMA index_list(TABLE_EVENT);
如果发现有只包含GAME和PLAYER的UNIQUE索引,那就是这个额外索引导致的冲突,需要删除它:
DROP INDEX index_name; -- 替换成对应的索引名
第四步:重建表(如果表结构错误)
如果确认表结构和你写的建表语句不一致,需要重建表(注意先备份数据):
- 重命名旧表:
ALTER TABLE TABLE_EVENT RENAME TO TABLE_EVENT_OLD;
- 用正确的建表语句创建新表:
CREATE TABLE IF NOT EXISTS TABLE_EVENT ( GAME INTEGER NOT NULL, PLAYER INTEGER NOT NULL, TYPE INTEGER NOT NULL, MINUTE INTEGER NOT NULL, PRIMARY KEY ( GAME, PLAYER, TYPE, MINUTE ), -- 其他字段定义... );
- 把旧表的数据迁移到新表(确保数据符合新的主键规则):
INSERT INTO TABLE_EVENT SELECT * FROM TABLE_EVENT_OLD;
- 删除旧表:
DROP TABLE TABLE_EVENT_OLD;
内容的提问来源于stack exchange,提问作者Automatik

