本文介绍 Django 的迁移系统如何通过mssql-django后端处理SQL Server,并记录已知的边缘情况。
创建和应用迁移
Django 的迁移工作流的工作方式与其他数据库SQL Server相同:
根据模型变更生成迁移:
python manage.py makemigrations myapp查看
<app>/migrations/中生成的迁移文件。将迁移应用到数据库:
python manage.py migrate myapp检查迁移状态:
python manage.py showmigrations myapp
初始项目设置
使用SQL Server设置新的 Django 项目时,请运行迁移以创建 Django 的内置表(身份验证、会话、管理员):
python manage.py migrate
此命令创建列出的 INSTALLED_APPS应用所需的所有表。
迁移中的自定义 SQL
使用 migrations.RunSQL 在迁移过程中执行原始 SQL 语句。 此方法可用于创建存储过程、触发器或其他特定于SQL Server的对象:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("myapp", "0001_initial"),
]
operations = [
migrations.RunSQL(
sql="CREATE INDEX IX_myapp_product_name ON myapp_product (name);",
reverse_sql="DROP INDEX IX_myapp_product_name ON myapp_product;",
),
]
已知迁移边缘案例
以下迁移操作在以 SQL Server 为目标时需要采用变通方法。
AutoField 修改
不支持在迁移期间将模型字段从 AutoField 更改为其他值,或从其他值更改为 AutoField。 SQL Server不允许从现有列添加或删除IDENTITY属性。
解决方法:使用所需的字段类型创建新模型。 将数据从旧表迁移到新表,然后删除旧表。
重命名带有外键约束的字段或模型
重命名具有外键约束的字段或模型可能会失败。 SQL Server需要在重命名操作期间删除和重新创建 FK 约束。
解决方法:用于 migrations.SeparateDatabaseAndState 删除 FK 约束、重命名列并重新创建约束,同时告知 Django 更新其模型状态。 以下示例将 product 模型上的 Order 外键重命名为 item:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("myapp", "0002_previous"),
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=[
migrations.RunSQL(
sql="ALTER TABLE myapp_order DROP CONSTRAINT FK_order_product;",
reverse_sql="ALTER TABLE myapp_order ADD CONSTRAINT FK_order_product FOREIGN KEY (product_id) REFERENCES myapp_product(id);",
),
migrations.RunSQL(
sql="EXECUTE sp_rename 'myapp_order.product_id', 'item_id', 'COLUMN';",
reverse_sql="EXECUTE sp_rename 'myapp_order.item_id', 'product_id', 'COLUMN';",
),
migrations.RunSQL(
sql="ALTER TABLE myapp_order ADD CONSTRAINT FK_order_item FOREIGN KEY (item_id) REFERENCES myapp_product(id);",
reverse_sql="ALTER TABLE myapp_order DROP CONSTRAINT FK_order_item;",
),
],
state_operations=[
migrations.RenameField(
model_name="order",
old_name="product",
new_name="item",
),
],
),
]
运行此 T-SQL 代码之前,请在数据库中查找实际约束名称。 Django 生成包含短哈希的约束名称,因此架构中的名称与此处所示的占位符不匹配。
Squash 迁移
当迁移文件积累到很多时,你可以将它们合并为更少的文件:
python manage.py squashmigrations myapp 0001 0010
Tip
始终要针对全新的数据库测试压缩后的迁移,以确保其生成正确的数据库架构。
生成的列(计算列)
mssql-django 后端支持 Django 的 GeneratedField(Django 5.0 及更高版本),它对应于 SQL Server 的计算列。
存储的 (PERSISTED) 生成的列
存储的生成列在物理上写入磁盘,并在源列更改时更新:
from django.db import models
from django.db.models import F
class Product(models.Model):
price = models.DecimalField(max_digits=10, decimal_places=2)
tax_rate = models.DecimalField(max_digits=5, decimal_places=4)
total_price = models.GeneratedField(
expression=F("price") * (1 + F("tax_rate")),
output_field=models.DecimalField(max_digits=10, decimal_places=2),
db_persist=True,
)
这将生成: total_price AS ([price] * (1 + [tax_rate])) PERSISTED.
虚拟生成的列
虚拟生成的列在查询时计算,不使用存储:
from django.db import models
from django.db.models import F, Value
from django.db.models.functions import Concat
class Employee(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
full_name = models.GeneratedField(
expression=Concat(F("first_name"), Value(" "), F("last_name")),
output_field=models.CharField(max_length=101),
db_persist=False,
)
Note
SQL Server限制非持久化计算列的索引。 如果需要为生成的列编制索引,请使用 db_persist=True 。
表和列注释
后端 mssql-django 支持 Django 的功能(Django db_comment 4.2 及更高版本)。 注释作为扩展属性存储在 MS_Description SQL Server 对象上。
表注释
class AuditLog(models.Model):
action = models.CharField(max_length=50)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
db_table_comment = "Tracks user actions for compliance auditing."
列注释
class Measurement(models.Model):
value = models.FloatField(db_comment="Sensor reading in Celsius")
recorded_at = models.DateTimeField(db_comment="UTC timestamp from the data logger")
注释在 SQL Server Management Studio 的列/表属性中可见,也可通过 sys.extended_properties 查看。
复合主键
Django 5.2 引入了 CompositePrimaryKey.
mssql-django 后端对复合主键提供了部分支持,但某些 Django 测试用例仍被排除在外。 在将复合键迁移和查询投入生产环境之前,请先结合你的应用程序对其进行验证。
-
inspectdb无法正确生成复合主键。 检查后手动定义它们。 - 不支持元组查找。 后端将复合键比较分解为单个列条件。
- 针对子查询的元组比较需要 Django 5.2.4 及以上版本。
- 某些迁移操作仍存在已知的排除项。 有关当前状态 ,请参阅 mssql-django 中的限制和不支持的功能 。
from django.db import models
from django.db.models import CompositePrimaryKey
class OrderItem(models.Model):
pk = CompositePrimaryKey("order_id", "product_id")
order = models.ForeignKey("Order", on_delete=models.CASCADE)
product = models.ForeignKey("Product", on_delete=models.CASCADE)
quantity = models.IntegerField()
IDENTITY_INSERT处理
当您将显式值插入到一个 AutoField 中时(例如,使用特定 ID 从备份中还原数据),后端会自动将该插入操作封装在 SET IDENTITY_INSERT ON / SET IDENTITY_INSERT OFF 中。 无需手动 SQL。
# The backend handles IDENTITY_INSERT automatically
Product.objects.create(id=42, name="Restored Widget", price=9.99)
Note
SQL Server 只允许每个会话一次有一个表具有 IDENTITY_INSERT ON。 如果在单个 atomic() 块中向多个表插入显式 ID,后端会按每条语句分别处理该切换。 但是,在同一个表上也使用 IDENTITY_INSERT 的并发会话可能会发生冲突。