Я использую, python 3.7, django 2.2 и django -ms sql -обэкэнда с устаревшей базой данных Microsoft SQL Server 2012. Я сгенерировал модель, используя inspectdb. Модель имеет поле с именем «ord_no», которое является типом данных CHAR (8) в базе данных. Django правильно создает его как CharField (max_length = 8). Примечание. Для этой таблицы имеется индекс в столбце ord_no. Вот упрощенное определение таблицы:
CREATE TABLE [dbo].[imordbld_sql](
[ord_no] [char](8) NOT NULL,
[item_no] [char](30) NOT NULL,
[qty] [decimal](13, 4) NULL,
[qty_per] [decimal](15, 6) NULL,
[ID] [numeric](9, 0) IDENTITY(1,1) NOT NULL
) ON [PRIMARY]
GO
Вот мой models.py с упрощенной версией рассматриваемой модели с полями, автоматически сгенерированными с помощью inspectdb. Я также создал собственный менеджер моделей, который преобразует Параметр ord_no в поле CHAR (8) для демонстрационных целей.
models.py
from django.db import models
class OrderBuildManager(models.Manager):
"""
Django automatically use nvarchar(16) instead of char(8) when using the ORM to filter by work order.
This method casts the ord_no parameter to a char(8) so SQL can use an index on the ord_no
column more efficiently (index seek vs index scan)
"""
def get_order(self, ord_no):
qs = super(OrderBuildManager, self).get_queryset().extra(
where=('ord_no = CAST(%s as CHAR(8))',),
params=(ord_no,))
return qs
class OrderBuild(models.Model):
ord_no = models.CharField(max_length=8)
item_no = models.CharField(max_length=30)
qty = models.DecimalField(max_digits=13, decimal_places=4, blank=True, null=True)
qty_per = models.DecimalField(max_digits=15, decimal_places=6, blank=True, null=True)
id = models.AutoField(db_column='ID', unique=True, primary_key=True)
objects = OrderBuildManager()
class Meta:
managed = False
db_table = 'imordbld_sql'
Вот мой views.py, где я использую два разных метода для потяните то же ord_no
views.py
from django.shortcuts import render
from .models import OrderBuild
def order_build_view(request, *args, **kwargs):
if request.method == 'GET':
order = kwargs.get('order')
order_using_default_filter = OrderBuild.objects.filter(ord_no=order)
order_using_extra_cast = OrderBuild.objects.get_order(order)
context = {
'order_1': order_using_default_filter,
'order_2': order_using_extra_cast
}
return render(request, 'chartest/order.html', context)
Шаблон очень прост c и просто отображает два объекта OrderBuild на странице.
Вот что показывает django -debug-toolbar для двух запросов.
Как видите, первый запрос, использующий фильтр Django по умолчанию, значительно медленнее, чем второй. Я вижу эти результаты последовательно, и также не имеет значения, в каком порядке выполняются запросы. Также обратите внимание, что в этих примерах используется тестовая база данных, которая намного меньше / менее активна, чем наша производственная база данных. Я вижу, что первый запрос иногда занимает до 5000 мс, а второй - только 50-100 мс.
Используя профилировщик событий SQL SSMS, я обнаружил фактическое выполнение запросов:
Первый запрос с использованием значения по умолчанию Django filter
declare @p1 int set @p1=NULL exec sp_prepexec @p1 output,N'@P1 nvarchar(16)',N'SELECT [imordbld_sql].[ord_no], [imordbld_sql].[item_no], [imordbld_sql].[qty], [imordbld_sql].[qty_per], [imordbld_sql].[ID] FROM [imordbld_sql] WHERE [imordbld_sql].[ord_no] = @P1',N' 6807319' select @p1
Второй запрос с CAST to CHAR (8)
declare @p1 int set @p1=NULL exec sp_prepexec @p1 output,N'@P1 nvarchar(16)',N'SELECT [imordbld_sql].[ord_no], [imordbld_sql].[item_no], [imordbld_sql].[qty], [imordbld_sql].[qty_per], [imordbld_sql].[ID] FROM [imordbld_sql] WHERE (ord_no = CAST(@P1 as CHAR(8)))',N' 6807319' select @p1
Как видно из обоих запросов, параметр @ P1 объявлен как тип nvarchar (16), который несовместим с типом данных CHAR (8), которым должен быть ord_no. Когда SQL Сервер выполняет первый запрос, он использует сканирование индекса для получения результатов. Во втором запросе, поскольку я привел его к правильному типу данных, SQL может использовать поиск по индексу, который намного быстрее сканирования.
Мой вопрос: возможно ли заставить Django использовать тип данных CHAR (n) для CharFields в фильтре. Я не хочу создавать методы для всех моих моделей, которые используют тип данных CHAR (n) SQL - особенно, поскольку мне приходится использовать метод queryset.extra (), который устарел Django. Я также не уверен, является ли это проблемой Django или django -ms sql -обязанием, поэтому любая / вся помощь приветствуется.
Что я пробовал: I увидел этот вопрос , который несколько связан, поэтому я реализовал ответ, создав собственный тип поля. Я создал fields.py, который содержит пользовательский тип поля.
fields.py
from django.db.models import Field
class SQLCharField(Field):
def __init__(self, length, *args, **kwargs):
self.max_length = length
super().__init__(*args, **kwargs)
def db_type(self, connection):
return 'CHAR(%s)' % self.max_length
Затем я заменил CharField в своей модели на созданный мной SQLCharField :
models.py
from django.db import models
from .fields import SQLCharField
...
class OrderBuild(models.Model):
ord_no = SQLCharField(length=8)
item_no = SQLCharField(length=30)
qty = models.DecimalField(max_digits=13, decimal_places=4, blank=True, null=True)
qty_per = models.DecimalField(max_digits=15, decimal_places=6, blank=True, null=True)
id = models.AutoField(db_column='ID', unique=True, primary_key=True)
objects = OrderBuildManager()
class Meta:
managed = False
db_table = 'imordbld_sql'
К сожалению, это не изменило способ выполнения запросов (параметр по-прежнему объявляется как nvarchar (16)) И если только я неправильно понимаю использование пользовательских типов данных, эта функциональность предназначена в первую очередь для создания миграций, не связанных с моей проблемой.
РЕДАКТИРОВАТЬ
Я сейчас использую метод raw () в наборе запросов, который позволяет мне написать запрос точно так, как необходимо для правильного использования индексов. К сожалению, я должен сделать это для всех моих моделей, но по крайней мере он не использует extra ()
class OrderBuildManager(models.Manager):
def get_order(self, ord_no):
return super(OrderBuildManager, self).get_queryset().raw(
'SELECT ord_no, item_no, qty, qty_per, id FROM imordbld_sql WHERE ord_no
= %s', [ord_no])