Android Room在编译时关于外键列不属于索引的警告,其含义是什么?

59

我最近在使用Google I/O上宣布的Android Architecture组件中的Android Room持久性库。似乎一切工作正常,但是我遇到了以下错误:

 

警告:tagId列引用外键,但它不是索引的一部分。每当修改父表时,这可能会触发完整的表扫描,因此强烈建议您创建覆盖此列的索引。

我的数据库有3个表:NoteTagJoinNotesTags。笔记与标签之间是多对多关系,因此需要使用JoinNotesTags表来处理映射关系。这些表很简单:

  • Note.idTag.id 都是主键
  • JoinNotesTags.noteId 引用 Note.id
  • JoinNotesTags.tagId 引用 Tag.id

外键约束在JoinNotesTags表上定义。下面是JoinNotesTags表的CREATE TABLE语句:

"CREATE TABLE IF NOT EXISTS `JoinNotesTags` (
    `id` INTEGER PRIMARY KEY AUTOINCREMENT, 
    `noteId` INTEGER, 
    `tagId` INTEGER, 
    FOREIGN KEY(`noteId`) REFERENCES `Note`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , 
    FOREIGN KEY(`tagId`) REFERENCES `Tag`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION 
)"

这是该类相应的@Entity注释:

@Entity(
        indices = arrayOf(Index(value = *arrayOf("noteId", "tagId"), unique = true)),
        foreignKeys = arrayOf(
                ForeignKey(
                        entity = Note::class,
                        parentColumns = arrayOf("id"),
                        childColumns = arrayOf("noteId"),
                        onDelete = ForeignKey.CASCADE),
                ForeignKey(
                        entity = Tag::class,
                        parentColumns = arrayOf("id"),
                        childColumns = arrayOf("tagId"))
        )
)

正如您从@Entity注释中可以看到的那样,tagIdnoteId一起包含在复合唯一索引中。 我已确认这个索引在自动生成的JSON模式文件中正确定义:

"CREATE UNIQUE INDEX `index_JoinNotesTags_noteId_tagId` 
    ON `JoinNotesTags` (`noteId`, `tagId`)"

所以我的问题是:这个警告只是 Room Library(仍处于 alpha 版本)中的一个 bug 吗 —— 也就是说,编译时分析没有注意到 tagId 是这个复合索引的一部分吗?还是我真的有一个需要解决的索引问题,以避免全表扫描?


我们需要看到您的 表格 定义,而不是 Java 代码。具体来说,我认为错误不在于 tagId 列没有索引,而是它引用了一个未建立索引的外键。 - Tim Biegeleisen
@TimBiegeleisen,问题已更新,包括JoinNotesTags表的定义。但是为了记录,Tag.id是主键。此外,警告显示问题出在tagId列上,而不是它所引用的父表中的列(Tag.id)。 - mikejonesguy
4个回答

84
当在 Kotlin 代码中时:

Before

@ColumnInfo(name = "question_id")
var questionId: Long

之后

@ColumnInfo(name = "question_id", index = true) //just add index = true
var questionId: Long

3
这对我很有效。为了更易读,答案可以简化为只包含 @ColumnInfo 行前后的内容。其余部分,尤其是 Retrofit 的 @Expose@SerializedName,是无关紧要的,在大多数情况下也不会在数据库实体中使用。 - Pilot_51

54

为了加快查询速度,您需要给列添加索引。以下是一个示例:

@Entity(indices = {@Index("artist_id")})
public class Artist{
    @NonNull
    @PrimaryKey
    @ColumnInfo(name = "artist_id")
    public String id;
    public String name;
}

26

当您修改Tag表时,数据库可能需要在JoinNotesTags表中查找相应的行。为了提高效率,这需要在tagId列上建立一个索引

您的复合索引对此没有用处;由于索引工作的方式,要搜索的列必须是索引中最左边的列。

您应该只在tagId列上添加索引。 (您可以交换复合索引中的列顺序,但这样做会出现与noteId相同的问题。)


47
你能添加一个样例插图吗? - Idee
2
引用您的话:“您应该在只有 tagId 列上添加索引”。这是否意味着 noteId 和 tagId 应该是分开的索引? - Ashish Krishnan
3
是的,两者都应该是索引中最左边的列。 - CL.
明白了!谢谢。 - Ashish Krishnan

1
如果即使索引已经正确定义,仍然出现此错误,则存在其他编译错误。要修复它,只需注释定义关系表中外键的代码,然后进行项目构建,并修复其他编译错误并取消注释给定的代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接