如何从 SQLite 表中检索最后一个自动增量 ID?

Posted

技术标签:

【中文标题】如何从 SQLite 表中检索最后一个自动增量 ID?【英文标题】:How to retrieve the last autoincremented ID from a SQLite table? 【发布时间】:2011-01-08 18:53:32 【问题描述】:

我有一个带有列 ID(主键、自动增量)和内容(文本)的消息表。 我有一个表用户,其中包含用户名(主键、文本)和哈希列。 一条消息由一个发件人(用户)发送给多个收件人(用户),而一个收件人(用户)可以拥有多条消息。 我创建了一个包含两列的 Messages_Recipients 表:MessageID(指 Messages 表的 ID 列和 Recipient(指 Users 表中的 username 列)。该表表示收件人和消息之间的多对多关系。

所以,我的问题是这个。新消息的 ID 将在存储到数据库后创建。但是,我如何保存对刚刚添加的 MessageRow 的引用以检索这个新的 MessageID? 当然,我总是可以在数据库中搜索添加的最后一行,但在多线程环境中这可能会返回不同的行吗?

编辑:据我了解,SQLite 可以使用SELECT last_insert_rowid()。但是我如何从 ADO.Net 调用这个语句呢?

我的 Persistence 代码(messages 和 messagesRecipients 是 DataTables):

public void Persist(Message message)

    pm_databaseDataSet.MessagesRow messagerow;
    messagerow=messages.AddMessagesRow(message.Sender,
                            message.TimeSent.ToFileTime(),
                            message.Content,
                            message.TimeCreated.ToFileTime());
    UpdateMessages();
    var x = messagerow;//I hoped the messagerow would hold a
    //reference to the new row in the Messages table, but it does not.
    foreach (var recipient in message.Recipients)
    
        var row = messagesRecipients.NewMessages_RecipientsRow();
        row.Recipient = recipient;
        //row.MessageID= How do I find this??
        messagesRecipients.AddMessages_RecipientsRow(row);
        UpdateMessagesRecipients();//method not shown
     



private void UpdateMessages()

    messagesAdapter.Update(messages);
    messagesAdapter.Fill(messages);

【问题讨论】:

我使用 SQlite(ADO.Net 版本) ***.com/questions/531109/… 【参考方案1】:

sqlite3_last_insert_rowid() 在多线程环境中是不安全的(并在 SQLite 上记录了这一点) 不过好消息是您可以把握机会,见下文

ID 保留未在 SQLite 中实现,如果您知道数据中始终存在变体,您也可以避免使用自己的 UNIQUE 主键进行 PK。

注意: 看看 RETURNING 上的子句是否不能解决您的问题 https://www.sqlite.org/lang_returning.html 由于这仅在最新版本的 SQLite 中可用,并且可能有一些开销,因此请考虑使用这样一个事实,即如果您在对 SQLite 的请求之间插入了插入内容,那真的很糟糕

如果您绝对需要获取 SQlite 内部 PK,还请参阅,您能否设计自己的可预测 PK: https://sqlite.org/withoutrowid.html

如果需要传统的 PK AUTOINCREMENT,是的,您获取的 id 可能属于另一个插入的风险很小。小但不可接受的风险。

一种解决方法是调用两次 sqlite3_last_insert_rowid()

#1 在我的插入之前,然后在我的插入之后 #2

如:

int IdLast = sqlite3_last_insert_rowid(m_db);   // Before (this id is already used)

const int rc = sqlite3_exec(m_db, sql,NULL, NULL, &m_zErrMsg);

int IdEnd = sqlite3_last_insert_rowid(m_db);    // After Insertion most probably the right one,

在绝大多数情况下,IdEnd==IdLast+1。这是“幸福的道路”,您可以依赖 IdEnd 作为您要查找的 ID。

否则,您必须执行额外的 SELECT,您可以在其中使用基于 IdLast 到 IdEnd 的条件(如果有的话,WHERE 子句中的任何其他条件都可以添加)

使用ROWID(这是一个 SQlite 关键字)来选择相关的 id 范围。

"SELECT my_pk_id FROM Symbols WHERE ROWID>%d && ROWID<=%d;",IdLast,IdEnd); 

// 注意:ROWID>%zd 中的 >,因为我们已经知道 IdLast 不是我们要查找的那个。

由于对 sqlite3_last_insert_rowid 的第二次调用是在 INSERT 之后立即完成的,因此此 SELECT 通常最多只返回 2 或 3 行。 然后在 SELECT 的结果中搜索您插入的数据以找到正确的 id。

性能改进:由于对 sqlite3_last_insert_rowid() 的调用比 INSERT 快得多,(即使互斥锁可能会出错,这在统计上是正确的)我打赌 IdEnd 是正确的,并在最后展开 SELECT 结果.几乎在我们测试的所有情况下,最后一行都包含您要查找的 ID)。

性能改进:如果你有一个额外的 UNIQUE Key,然后将它添加到 WHERE 中以仅获取一行。

我尝试使用 3 个线程进行大量插入,它按预期工作,准备 + DB 处理占用了绝大多数 CPU 周期,结果是 mixup ID 的奇数在 1/1000 插入的范围内(情况其中IdEnd>IdLast+1)

所以额外的 SELECT 来解决这个问题的代价是相当低的。

否则说在绝大多数插入操作中使用 sqlite3_last_insert_rowid() 的好处是很大的,如果使用一些小心,甚至可以安全地在 MT 中使用。

警告:事务模式下的情况稍微有点尴尬。

SQLite 也没有明确保证 ID 将是连续的且不断增长的(除非 AUTOINCREMENT)。 (至少我没有找到这方面的信息,但是查看 SQLite 源代码就排除了)

【讨论】:

【参考方案2】:

来自@polyglot 解决方案的示例代码

SQLiteCommand sql_cmd;
sql_cmd.CommandText = "select seq from sqlite_sequence where name='myTable'; ";
int newId = Convert.ToInt32( sql_cmd.ExecuteScalar( ) );

【讨论】:

【参考方案3】:

根据android Sqlite get last insert row id还有一个查询:

SELECT rowid from your_table_name order by ROWID DESC limit 1

【讨论】:

这个选项有性能损失,因为在找到 rowid 之前执行排序算法 也许它不是最理想的,但是一旦关闭并重新打开连接,它实际上就可以工作,而其他两个解决方案不适合我。 以下变体避免了ORDER BY。应该更快,但我没有测试:SELECT MAX(rowid) FROM your_table_name【参考方案4】:

另一种选择是查看系统表sqlite_sequence。如果您使用自动增量主键创建任何表,您的 sqlite 数据库将自动拥有该表。此表用于 sqlite 跟踪自动增量字段,以便即使在您删除某些行或某些插入失败后它也不会重复主键(在此处阅读更多信息http://www.sqlite.org/autoinc.html)。

因此,使用此表还有一个额外的好处,即即使您插入了其他内容(当然是在其他表中!),您也可以找出新插入的项目的主键。在确保你的插入成功(否则你会得到一个错误的数字)之后,你只需要这样做:

select seq from sqlite_sequence where name="table_name"

【讨论】:

它在序列增加和行删除后起作用。 好答案。奇怪的是,在“SQLite 数据库浏览器”中,如果从“sqlite_sequence”中删除一行,您将不再获得某个表的最后一个 id。【参考方案5】:

我在多线程环境中使用 SELECT last_insert_rowid() 时遇到了问题。如果另一个线程插入到另一个具有 autoinc 的表中,last_insert_rowid 将返回新表中的 autoinc 值。

这是他们在文档中声明的地方:

如果在 sqlite3_last_insert_rowid() 函数正在运行时,一个单独的线程在同一数据库连接上执行新的 INSERT 并因此更改了最后一个插入 rowid,则 sqlite3_last_insert_rowid() 返回的值是不可预测的,并且可能不等于旧的或新的最后插入rowid。

来自sqlite.org doco

【讨论】:

我想您可以为每个线程使用单独的连接,但我注意到它对性能有很大影响。在我的场景中,我每秒只能执行大约 15 次插入。 我的猜测是单独的线程将是同一个问题。不幸的是,到目前为止似乎还没有线程安全的工作,我的直觉是多个事务甚至都不够......所以要小心:|【参考方案6】:

使用 SQL Server,您可以 SELECT SCOPE_IDENTITY() 获取当前进程的最后一个标识值。

使用 SQlite,看起来你会做一个自动增量

SELECT last_insert_rowid()

插入后立即。

http://www.mail-archive.com/sqlite-users@sqlite.org/msg09429.html

在回答您的评论以获取此值时,您可能希望使用 SQL 或 OleDb 代码,例如:

using (SqlConnection conn = new SqlConnection(connString))

    string sql = "SELECT last_insert_rowid()";
    SqlCommand cmd = new SqlCommand(sql, conn);
    conn.Open();
    int lastID = (Int32) cmd.ExecuteScalar();

【讨论】:

再次感谢您!不幸的是,它不起作用,因为需要在关闭用于更新 DataTable 的连接之前调用 last_insert_rowid() 函数。这可能是 SQLite 的一个怪癖…… 对不起,是的,这可能是真的。您是否尝试在 messagesAdapter.Update(messages); 之后执行它; @Dabblernl 似乎另一个答案更可靠,如果您觉得另一个答案更有帮助,您可能需要更改已接受的答案 但 RowId 并不总是与 PRIMARY KEY 相同。除了If the table has a column of type INTEGER PRIMARY KEY then that column is another alias for the rowid.

以上是关于如何从 SQLite 表中检索最后一个自动增量 ID?的主要内容,如果未能解决你的问题,请参考以下文章

如果一个表是自动增量的,你怎么能从一个 sqlite 表的元数据中得到?

如何使用 Swift 从 CoreData sqlite 表中获取特定的行数据

SQLite 自动增量 - 如何插入值?

SQLite:查询纬度/经度增量

如何从表中检索我的最后一个插入行?

从3个表中检索数据如何使用Laravel中的第一个表从最后一个表中检索数据