为什么Django不重置SQLite3中的序列?

5 投票
2 回答
2361 浏览
提问于 2025-04-18 08:58

为什么Django允许你在Postgres和其他数据库管理系统上重置序列(自动ID)字段,但在SQLite3上却不行呢?

在查看Django的源代码时,特别是django/db/backends/sqlite3/base.py中的sql_flush方法时,有一句注释提到:

注意:没有重置自增索引的要求(与其他sql_flush()实现不同)。此时只需返回SQL。

我有一些测试需要加载依赖于绝对主键ID的固定数据文件。因为Django不重置SQLite的自动ID字段,所以这些固定数据无法正确加载。

看起来在SQLite中重置自动ID列其实是比较简单的:如何重置SQLite中的自增序列号

2 个回答

0

也许这个代码片段会对你有帮助:

import os

from django.core.management import call_command
from django.db import connection
from django.utils.six import StringIO


def reset_sequences(app_name):
    os.environ['DJANGO_COLORS'] = 'nocolor'
    buf = StringIO()
    call_command('sqlsequencereset', app_name, stdout=buf)
    buf.seek(0)

    sql = "".join(buf.readlines())

    with connection.cursor() as cursor:
        cursor.execute(sql)

    print("Sequences for app '{}' reset".format(app_name))
1

你可以通过下面的方式来修改 sql_flush,以重置 SQLite 的序列:

from django.db.backends.sqlite3.operations import DatabaseOperations
from django.db import connection


def _monkey_patch_sqlite_sql_flush_with_sequence_reset():
    original_sql_flush = DatabaseOperations.sql_flush

    def sql_flush_with_sequence_reset(self, style, tables, sequences, allow_cascade=False):
        sql_statement_list = original_sql_flush(self, style, tables, sequences, allow_cascade)
        if tables:
            # DELETE FROM sqlite_sequence WHERE name IN ($tables)
            sql = '%s %s %s %s %s %s (%s);' % (
                style.SQL_KEYWORD('DELETE'),
                style.SQL_KEYWORD('FROM'),
                style.SQL_TABLE(self.quote_name('sqlite_sequence')),
                style.SQL_KEYWORD('WHERE'),
                style.SQL_FIELD(self.quote_name('name')),
                style.SQL_KEYWORD('IN'),
                ', '.join(style.SQL_FIELD(f"'{table}'") for table in tables)
            )
            sql_statement_list.append(sql)
        return sql_statement_list

    DatabaseOperations.sql_flush = sql_flush_with_sequence_reset

TransactionTestCase 中,你可以这样使用它:

from django.test import TransactionTestCase


class TransactionTestCaseWithSQLiteSequenceReset(TransactionTestCase):

    reset_sequences = True

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        if connection.vendor == 'sqlite':
            _monkey_patch_sqlite_sql_flush_with_sequence_reset()

这样做可以确保那些依赖于固定主键的测试在 SQLite 和其他数据库(比如 PostgreSQL)中都能正常工作。不过,关于 reset_sequences 的一些注意事项,可以查看 Django 的文档。其中一个问题是,它会让测试变得比较慢。

撰写回答