本文提供了将 Django 应用程序从 PostgreSQL、MySQL 或 SQLite 迁移到使用 mssql-django 后端的 SQL Server 的指导。
Overview
Django 的 ORM 提取了大多数数据库差异,但某些行为和 SQL 方言在后端之间有所不同。 本指南介绍迁移到SQL Server时遇到的主要差异。
步骤 1:安装 mssql-django
mssql-django安装包及其依赖项:
pip install mssql-django
确保已安装用于SQL Server的 Microsoft ODBC 驱动程序。 有关特定于平台的说明,请参阅 安装 mssql-django 。
步骤 2:更新 DATABASE 配置
将 settings.py 中现有的数据库配置替换为:
# Example: From PostgreSQL
# DATABASES = {
# "default": {
# "ENGINE": "django.db.backends.postgresql",
# "NAME": "mydb",
# },
# }
# To SQL Server
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"USER": "<your-username>",
"PASSWORD": "<your-password>",
"HOST": "<your-server>",
"PORT": "1433",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
},
},
}
步骤 3:创建新的迁移
从SQL Server的干净迁移历史记录开始:
# Remove existing migration files (keep __init__.py)
# Then regenerate
python manage.py makemigrations
python manage.py migrate
Important
使用数据迁移或单独的 ETL 过程传输数据。 不要尝试针对SQL Server运行 PostgreSQL 或 MySQL 迁移文件。
与 PostgreSQL 的主要区别
| 功能 / 特点 | PostgreSQL | SQL Server (mssql-django) |
|---|---|---|
| 自动递增 | SERIAL / BIGSERIAL |
IDENTITY(1,1) |
| 布尔类型 | 原生 boolean |
bit (0 或 1) |
| 文本字段 |
text (无限制) |
nvarchar(max) |
| JSON 支持 | 原生 jsonb |
具有 JSON 函数的 nvarchar(max)(SQL Server 2016+) |
| 数组字段 | ArrayField |
不支持。 使用相关的表或 JSON。 |
| HStore 字段 | HStoreField |
不支持。 改用 JSONField。 |
| 范围字段 |
IntegerRangeField、BigIntegerRangeField、DateRangeField、DateTimeRangeField |
不支持。 使用两个单独的字段。 |
| 全文搜索 |
SearchVector、SearchRank |
将原始 SQL 与SQL Server全文搜索配合使用。 |
DISTINCT ON |
支持 | 不支持。 使用 GROUP BY 或子查询。 |
DateTimeField 带时区 |
timestamp with time zone |
datetimeoffset(当为 USE_TZ=True 时)或 datetime2 |
待替换的 PostgreSQL 特有功能
如果您的代码使用了来自 django.contrib.postgres 的 PostgreSQL 特有功能,请将其替换掉:
# PostgreSQL ArrayField - replace with JSONField or related table
# Before
from django.contrib.postgres.fields import ArrayField
tags = ArrayField(models.CharField(max_length=50))
# After (using JSONField)
tags = models.JSONField(default=list)
# PostgreSQL HStoreField - replace with JSONField
# Before
from django.contrib.postgres.fields import HStoreField
metadata = HStoreField()
# After
metadata = models.JSONField(default=dict)
与 MySQL 的主要区别
| 功能 / 特点 | MySQL | SQL Server (mssql-django) |
|---|---|---|
| 自动递增 | AUTO_INCREMENT |
IDENTITY(1,1) |
| 布尔类型 | tinyint(1) |
bit |
| 文本字段 | longtext |
nvarchar(max) |
| JSON 支持 | 原生 JSON (5.7 及更高版本) |
带有 JSON 函数的 nvarchar(max) |
| Collation | 可按列配置 | 实例或数据库级别(使用 COLLATE 选项替代) |
DateTimeField |
datetime(6) | datetimeoffset 或 datetime2 |
与 SQLite 的主要区别
| 功能 / 特点 | SQLite | SQL Server (mssql-django) |
|---|---|---|
| 类型强制执行 | 灵活的键入 | 严格类型强制执行 |
| 并发写入 | 有限 | 完全并发支持 |
| 最大连接数 | 有效 1 个编写器 | 支持大量并发连接的连接池 |
DateTimeField |
存储为文本 | datetimeoffset 或 datetime2 |
排序规则差异
排序规则控制 SQL Server 如何比较和排序文本。 这是从 PostgreSQL 或 MySQL 迁移时最常见的意外行为源之一。
事例敏感性
SQL Server的默认排序规则 (SQL_Latin1_General_CP1_CI_AS) 不区分大小写。 PostgreSQL 默认区分大小写。
这种行为意味着,迁移后,先前区分 "Smith" 和 "smith" 的查询会将它们视为相等:
# On PostgreSQL: returns only exact case matches
# On SQL Server (default collation): returns both "Smith" and "smith"
User.objects.filter(last_name="Smith")
如果应用程序依赖于区分大小写的比较,有两个选项:
将数据库或列排序规则更改为区分大小写的变体:
-- Database-level (affects all new columns) ALTER DATABASE [<your-database>] COLLATE Latin1_General_CS_AS; -- Column-level (for specific columns) ALTER TABLE [<your-table>] ALTER COLUMN [<column-name>] NVARCHAR (150) COLLATE Latin1_General_CS_AS;在原始 SQL 中对特定查询使用 Django 的
__exact查找功能,并覆盖排序规则
区分重音
SQL Server 的默认排序规则区分重音符(AS),这与 PostgreSQL 的行为一致。 像 é 和 e 这样的字符会被视为不同的字符。 如果需要进行不区分重音符号的比较,请使用以 _AI 结尾的排序规则。
在 mssql-django 中配置排序规则
替代数据库配置中文本字段查找的默认排序规则:
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
"collation": "Latin1_General_CS_AS", # Case-sensitive
},
},
}
Note
mssql-django 中的 collation 选项控制 LIKE 中使用的排序规则,以及 Django 的 ORM 查找生成的比较操作中使用的排序规则。 它不会更改数据库中现有列的排序规则。 若要更改存储的列排序规则,请使用 ALTER TABLE / ALTER COLUMN 语句。 有关详细信息,请参阅SQL Server排序规则文档。
步骤 4:更新自定义 SQL
如果代码包含原始 SQL,请更新它以获取SQL Server语法:
# PostgreSQL syntax
# cursor.execute("SELECT * FROM products LIMIT 10 OFFSET 20")
# SQL Server syntax
cursor.execute("SELECT * FROM products ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY")
常见的 SQL 语法差异:
| 运算 | PostgreSQL/MySQL | SQL Server |
|---|---|---|
| 限制结果 | LIMIT 10 |
TOP 10 或 OFFSET ... FETCH NEXT ... |
| 字符串拼接 |
\|\| (PG) / CONCAT() |
+ 或 CONCAT() |
| 布尔文本 | TRUE / FALSE |
1 / 0 |
| 当前时间戳 | NOW() |
用于时区感知值的 GETDATE()、SYSDATETIME() 或 SYSDATETIMEOFFSET() |
| 如果不存在 | CREATE TABLE IF NOT EXISTS |
检查 sys.objects 或使用 IF NOT EXISTS |
事务隔离差异
PostgreSQL 的 READ COMMITTED 隔离级别使用 MVCC(多版本并发控制)机制。 读取器永远不会阻塞写入器,写入器也永远不会阻塞读取器。
SQL Server 默认的READ COMMITTED采用锁定机制,这意味着读取查询在等待写入事务完成时可能会被阻塞。 如果应用程序在迁移后遇到阻止增加,请考虑在数据库上启用 READ COMMITTED SNAPSHOT :
ALTER DATABASE [<your-database>]
SET READ_COMMITTED_SNAPSHOT ON;
这会将 SQL Server 的 READ COMMITTED 更改为使用行版本控制(类似于 PostgreSQL 的 MVCC),而不是锁机制。 读者无需等待活动编写器即可看到行的最后一个提交版本。
Note
READ COMMITTED SNAPSHOT 需要额外的 tempdb 空间来存储行版本。 在生产环境中启用之前,在实际负载下进行测试。 有关详细信息,请参阅 mssql-django 中的事务管理。
步骤 5:迁移数据
数据迁移策略取决于数据集大小:
小型数据集(<500 MB)
使用 Django 的 dumpdata/loaddata:
# On the source database
python manage.py dumpdata --natural-foreign --natural-primary -o data.json
# Switch settings.py to SQL Server, then:
python manage.py migrate
python manage.py loaddata data.json
大型数据集(>500 MB)
对于大型迁移,请使用专用工具避免内存耗尽和超时问题。 Django 的 ORM 不是适合这种规模的大容量加载工具。 在数据迁移过程中绕过它,之后让 Django 管理数据库模式和应用程序逻辑。
| 工具 | 最适用于 |
|---|---|
| SQL Server 导入和导出向导 | 使用 GUI 进行本地到本地迁移 |
| Azure 数据工厂 | 任何源到 Azure SQL,包括混合场景 |
| Azure 数据库迁移服务 | 具备内置验证和回滚的大规模迁移 |
| 使用 Apache Arrow 进行 mssql-python 大容量复制 | 要求在 SQL Server、Azure SQL 数据库和 Fabric 中的 SQL 数据库之间实现最大吞吐量的自定义 Python 管道 |
迁移后验证
迁移后,验证自动递增列的标识种子一致性:
-- Check identity seed and current value for all tables
SELECT
TABLE_NAME,
IDENT_SEED(TABLE_SCHEMA + '.' + TABLE_NAME) AS IdentitySeed,
IDENT_INCR(TABLE_SCHEMA + '.' + TABLE_NAME) AS IdentityIncrement,
IDENT_CURRENT(TABLE_SCHEMA + '.' + TABLE_NAME) AS CurrentIdentity
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND OBJECTPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + TABLE_NAME), 'TableHasIdentity') = 1
ORDER BY TABLE_NAME;
如果 CurrentIdentity 超过 IdentitySeed + record_count,则重新分配:
DBCC CHECKIDENT ('your_table', RESEED, new_seed);