ZetCode

Python sqlite3.Cursor.lastrowid

上次修改时间:2025 年 4 月 15 日

本综合指南探讨了 Python 的 sqlite3.Cursor.lastrowid 属性,该属性提供最后插入行的行 ID。我们将介绍基本用法、常用模式和实际示例。

基本定义

SQLite 游标的 lastrowid 属性返回由 INSERT 操作修改的最后一行的行 ID。 当使用自动递增主键时,这特别有用。

主要特点:它是只读的,如果没有插入行则返回 None,并且仅适用于具有 INTEGER PRIMARY KEY 列的表。 该值是数据库特定的和连接特定的。

lastrowid 的基本用法

这是 lastrowid 的最简单用法,用于获取插入行的 ID。 我们将使用上下文管理器进行自动资源清理。

basic_lastrowid.py
import sqlite3

with sqlite3.connect('example.db') as conn:
    cursor = conn.cursor()
    cursor.execute('''CREATE TABLE IF NOT EXISTS users
                      (id INTEGER PRIMARY KEY, name TEXT, email TEXT)''')
    
    cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", 
                   ('Alice', 'alice@example.com'))
    
    print("Last inserted row ID:", cursor.lastrowid)

此示例创建一个具有自动递增 ID 列的表,插入一行,并打印新插入行的 ID。 with 语句确保正确清理。

lastrowid 在 INSERT 语句之后立即可用,并且在同一游标上执行另一个 INSERT 之前仍然可以访问。

多次插入操作

执行多次插入时,lastrowid 始终反映最近的插入操作。 此示例演示了此行为。

multiple_inserts.py
import sqlite3

with sqlite3.connect('example.db') as conn:
    cursor = conn.cursor()
    cursor.execute('''CREATE TABLE IF NOT EXISTS products
                      (id INTEGER PRIMARY KEY, name TEXT, price REAL)''')
    
    # First insert
    cursor.execute("INSERT INTO products (name, price) VALUES (?, ?)",
                  ('Laptop', 999.99))
    print("First insert ID:", cursor.lastrowid)
    
    # Second insert
    cursor.execute("INSERT INTO products (name, price) VALUES (?, ?)",
                  ('Phone', 699.99))
    print("Second insert ID:", cursor.lastrowid)

每个 INSERT 操作都会更新 lastrowid 值。 输出显示按顺序分配给每个新行的 ID。

即使插入在不同的事务中,或者插入之间发生其他操作,此行为也是一致的。

批量插入操作

当使用 executemany 进行批量插入时,lastrowid 仅返回批处理中最后插入行的 ID。

bulk_insert.py
import sqlite3

employees = [
    ('John Doe', 'Engineering'),
    ('Jane Smith', 'Marketing'),
    ('Bob Johnson', 'Sales')
]

with sqlite3.connect('company.db') as conn:
    cursor = conn.cursor()
    cursor.execute('''CREATE TABLE IF NOT EXISTS employees
                      (id INTEGER PRIMARY KEY, name TEXT, department TEXT)''')
    
    cursor.executemany("INSERT INTO employees (name, department) VALUES (?, ?)",
                      employees)
    
    print("Last inserted row ID from bulk operation:", cursor.lastrowid)

executemany 方法插入多行,但只有最后插入行的 ID 在 lastrowid 中可用。

要获取所有插入的 ID,您需要执行单独的 INSERT 语句,或者在每次插入后使用不同的方法,例如 SELECT last_insert_rowid

没有自动递增的表

当使用没有 INTEGER PRIMARY KEY 列的表时,lastrowid 的行为会有所不同。 此示例显示了差异。

no_autoincrement.py
import sqlite3

with sqlite3.connect('test.db') as conn:
    cursor = conn.cursor()
    
    # Table with explicit primary key
    cursor.execute('''CREATE TABLE IF NOT EXISTS table1
                      (pk TEXT PRIMARY KEY, data TEXT)''')
    cursor.execute("INSERT INTO table1 VALUES ('key1', 'value1')")
    print("Table with TEXT PK:", cursor.lastrowid)  # None
    
    # Table with composite primary key
    cursor.execute('''CREATE TABLE IF NOT EXISTS table2
                      (id INT, name TEXT, PRIMARY KEY (id, name))''')
    cursor.execute("INSERT INTO table2 VALUES (1, 'test')")
    print("Table with composite PK:", cursor.lastrowid)  # None
    
    # Table with INTEGER PRIMARY KEY
    cursor.execute('''CREATE TABLE IF NOT EXISTS table3
                      (id INTEGER PRIMARY KEY, data TEXT)''')
    cursor.execute("INSERT INTO table3 (data) VALUES ('works')")
    print("Table with INTEGER PK:", cursor.lastrowid)  # Actual ID

只有具有 INTEGER PRIMARY KEY 列(默认情况下自动递增)的表才会填充 lastrowid。 其他主键类型导致 None。

在设计需要跟踪插入行 ID 的数据库模式时,这种区别非常重要。

将 lastrowid 与事务一起使用

即使事务回滚,lastrowid 仍然有效,但 ID 可能会被重用。 此示例演示了该行为。

transactions.py
import sqlite3

with sqlite3.connect('transactions.db') as conn:
    cursor = conn.cursor()
    cursor.execute('''CREATE TABLE IF NOT EXISTS logs
                      (id INTEGER PRIMARY KEY, message TEXT)''')
    
    # Successful transaction
    cursor.execute("INSERT INTO logs (message) VALUES ('First message')")
    first_id = cursor.lastrowid
    conn.commit()
    print("First ID (committed):", first_id)
    
    # Rolled back transaction
    cursor.execute("INSERT INTO logs (message) VALUES ('Second message')")
    second_id = cursor.lastrowid
    conn.rollback()
    print("Second ID (rolled back):", second_id)
    
    # New insert after rollback
    cursor.execute("INSERT INTO logs (message) VALUES ('Third message')")
    third_id = cursor.lastrowid
    conn.commit()
    print("Third ID (committed):", third_id)

回滚后,下一个插入可能会重用回滚的 ID。 确切的行为取决于 SQLite 的内部序列计数器。

当事务可能回滚时,应用程序不应依赖于特定的 ID 序列。

将 lastrowid 与 SELECT 结合使用

您可以将 lastrowid 与 SELECT 语句结合使用,以检索完整的插入行。 当您需要的不仅仅是 ID 时,这很有用。

select_after_insert.py
import sqlite3

with sqlite3.connect('inventory.db') as conn:
    cursor = conn.cursor()
    cursor.execute('''CREATE TABLE IF NOT EXISTS items
                      (id INTEGER PRIMARY KEY, name TEXT, quantity INTEGER)''')
    
    # Insert new item
    cursor.execute("INSERT INTO items (name, quantity) VALUES (?, ?)",
                  ('Widget', 100))
    new_id = cursor.lastrowid
    
    # Retrieve the complete inserted row
    cursor.execute("SELECT * FROM items WHERE id = ?", (new_id,))
    inserted_row = cursor.fetchone()
    print("Inserted row:", inserted_row)

这种模式是有效的,因为它使用主键索引来检索刚刚插入的确切行。

当默认值或触发器在插入期间修改数据时,它特别有用。

lastrowid 的替代方案

SQLite 提供了 last_insert_rowid 函数作为 cursor.lastrowid 的替代方案。 此示例显示了两种方法。

alternative.py
import sqlite3

with sqlite3.connect('alternate.db') as conn:
    cursor = conn.cursor()
    cursor.execute('''CREATE TABLE IF NOT EXISTS notes
                      (id INTEGER PRIMARY KEY, content TEXT)''')
    
    # Using cursor.lastrowid
    cursor.execute("INSERT INTO notes (content) VALUES ('First note')")
    print("Using cursor.lastrowid:", cursor.lastrowid)
    
    # Using last_insert_rowid() function
    cursor.execute("INSERT INTO notes (content) VALUES ('Second note')")
    cursor.execute("SELECT last_insert_rowid()")
    print("Using last_insert_rowid():", cursor.fetchone()[0])

两种方法都返回相同的值,但 last_insert_rowid 是一个 SQL 函数,可以在更复杂的查询中使用。

对于简单的情况,lastrowid 属性通常更方便,因为它不需要额外的查询。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 到目前为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程