为啥 Django 不在 SQLite3 中重置序列?

Posted

技术标签:

【中文标题】为啥 Django 不在 SQLite3 中重置序列?【英文标题】:Why Doesn't Django Reset Sequences in SQLite3?为什么 Django 不在 SQLite3 中重置序列? 【发布时间】:2014-07-28 17:19:06 【问题描述】:

为什么 Django 允许您重置 postgres 和其他 DBMS 上的序列 (AutoID) 字段,而不是 SQLite3?

查看django/db/backends/sqlite3/base.pysql_flush方法的源码,有注释说:

注意:不需要重置自动递增索引(参见其他 sql_flush() 实现)。此时只需返回 SQL

我有一些测试在其中加载依赖于绝对主键 ID 的夹具文件。因为 Django 没有为 SQLite 重置 auto id 字段,所以这些设备不能正确加载。

在sqlite中重置自动id列似乎有些微不足道:How can I reset a autoincrement sequence number in sqlite

【问题讨论】:

听起来你的测试坏了,那么。什么都不应该依赖于绝对 ID。 @DanielRoseman 是的,每个人都知道这不是最佳实践。事实上,很容易质疑在测试中使用加载夹具。这就是存在像 FactoryGirl 这样的东西的原因。但是,在这种情况下,这是一个完美的解决方案。 Django 文档指定在测试用例docs.djangoproject.com/en/1.5/topics/testing/overview/… 中使用重置序列。即使他们提到这是一个坏主意。但是,我的问题是:即使这是一个坏主意,为什么他们不支持 sqlite? 我不知道 sqlite 是否像 Oracle 那样支持序列休息。例如,我发现当我清空相关表时,sqlite 自动整数主键会重置。我知道在 Oracle 下,Auto ID 序列作为一个完全独立的实体存在,可以独立重置。 【参考方案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)一起使用。但是,请参阅 Django documentation 以了解有关 reset_sequences 的警告。一方面,它使测试变慢。

【讨论】:

【参考方案2】:

也许这个 sn-p 会有所帮助:

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))

【讨论】:

Django 3.0 不再有django.utils.six;见***.com/a/59420098/5139284。我们或许可以用from io import StringIO 替换该行。 确保你没有将reset_sequences定义为TestCase的类方法,否则你会得到“assert not self.reset_sequences, 'reset_sequences cannot be used on TestCase instances' AssertionError: reset_sequences cannot用于 TestCase 实例” 对我来说,这总是打印“未找到序列”。

以上是关于为啥 Django 不在 SQLite3 中重置序列?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 django sqlite3 数据库在一台机器上与另一台机器上的工作方式不同?

为啥 Django 不在我的模板上显示我的动态背景图像

为啥 Django 不在 Varnish 代理后面生成 CSRF 或会话 Cookie?

Django Reset PW 电子邮件不在 AWS 上发送

重置 SQLite3/MySQL 中的行数计数

sqlite3 性能测试 - 如何快速重置/清除缓存