Строки, используемые в запросе, всегда отправляются с синтаксисом NVARCHAR, даже если базовый столбец не является Unicode - PullRequest
1 голос
/ 11 марта 2019

Я заметил странное поведение в SQL, сгенерированном для запросов к строковым полям в MS SQL.

Версия сервера: SQL Server 2014 12.0.5000.0

Сортировка: SQL_Latin1_General_CP1_CI_AS

Версия Python: 3.7

В нашей базе данных есть поля NVARCHAR (в основном новые) и VARCHAR (в основном старые). Мы используем SQLAlchemy для подключения нашего приложения Python к базе данных, и хотя мы указываем, что столбец имеет тип String (в отличие от Unicode), исполняемый SQL всегда выходит с синтаксисом NVARCHAR (например, , N'foo').

Это в конечном итоге создает некоторые очевидные проблемы, поскольку простой поиск индекса в многомиллионной таблице строк превращается в гигантскую операцию перекодирования строк.

Обходной путь, который я обнаружил, заключается в передаче байтовых строк (а-ля s.encode("utf-8")) вместо str с, но это невероятно подвержено ошибкам и хакерски. Я ожидал, что SQLAlchemy будет обрабатывать это автоматически, поскольку я сказал, что запрашиваю столбец String, а не столбец Unicode.

Если это должно происходить автоматически, то может быть потому, что он не знает сопоставление базы данных? Если это так, как бы я настроить это?

Наконец, в качестве другого ориентира мы используем pymssql. Из предыдущего опыта до использования SQLAlchemy я знаю, что pymssql делает то же самое (предполагается, что строки юникода NVARCHAR, а строки байтов - нет). Код здесь . Насколько я могу судить, SQLAlchemy просто передает это по линии. Такое поведение меня немного удивляет, поскольку SQLAlchemy знает типы столбцов и тип соединения / драйвера, с которым он работает.

Я не боюсь испачкать руки, поэтому, если кто-нибудь узнает, где это может быть исправлено, я был бы рад внести свой вклад. Мое текущее исследование, кажется, указывает на то, что что-то связано с диалектами и / или компиляцией запросов / операторов.

Я загрузил минимальный пример проекта в GitHub .

РЕДАКТИРОВАТЬ 2019-03-18 : Обновлено с новой информацией, основанной на расследовании.

РЕДАКТИРОВАТЬ 2019-03-23 ​​: добавлено репозиторий GitHub с минимальным примером.

1 Ответ

0 голосов
/ 23 марта 2019

Мне удалось воспроизвести проблему.Ваш MCVE был очень полезен.

Интересно было видеть, что для вашего примера ORM SQL Profiler не показал никаких доказательств того, что SQLAlchemy извлекал метаданные столбца перед выполнением запроса SELECT к таблице.Очевидно, он полагает, что знает достаточно о столбцах для построения рабочего запроса, хотя (как выясняется) он не обязательно самый эффективный.

Я знал, что SQL-язык выражений SQLAlchemy получит таблицуметаданных, поэтому я попробовал аналогичный SELECT, используя

metadata = MetaData()
my_table = Table('test', metadata, autoload=True, autoload_with=engine)
stmt = select([my_table.c.id, my_table.c.key])\
    .select_from(my_table)\
    .where(my_table.c.key == value)
cnxn = engine.connect()
items = cnxn.execute(stmt).fetchall()

, и хотя SQLAlchemy действительно извлек метаданные, используя

SELECT [INFORMATION_SCHEMA].[columns].[table_schema],
       [INFORMATION_SCHEMA].[columns].[table_name],
       [INFORMATION_SCHEMA].[columns].[column_name],
       [INFORMATION_SCHEMA].[columns].[is_nullable],
       [INFORMATION_SCHEMA].[columns].[data_type],
       [INFORMATION_SCHEMA].[columns].[ordinal_position],
       [INFORMATION_SCHEMA].[columns].[character_maximum_length],
       [INFORMATION_SCHEMA].[columns].[numeric_precision],
       [INFORMATION_SCHEMA].[columns].[numeric_scale],
       [INFORMATION_SCHEMA].[columns].[column_default],
       [INFORMATION_SCHEMA].[columns].[collation_name]
FROM   [INFORMATION_SCHEMA].[columns]
WHERE  [INFORMATION_SCHEMA].[columns].[table_name] = Cast(
       N'test' AS NVARCHAR(max))
       AND [INFORMATION_SCHEMA].[columns].[table_schema] = Cast(
           N'dbo' AS NVARCHAR(max))
ORDER  BY [INFORMATION_SCHEMA].[columns].[ordinal_position]

, часть выходных данных которых равна

TABLE_SCHEMA  TABLE_NAME  COLUMN_NAME  IS_NULLABLE  DATA_TYPE  ORDINAL_POSITION  CHARACTER_MAXIMUM_LENGTH
------------  ----------  -----------  -----------  ---------  ----------------  ------------------------
dbo           test        id           NO           int        1                 NULL
dbo           test        key          NO           varchar    2                 50

в результирующем запросе SELECT по-прежнему использовался nvarchar литерал

SELECT test.id, test.[key] 
FROM test 
WHERE test.[key] = N'record123456'

Наконец, я проделал те же тесты, используя pyodbc вместо pymssql, и результаты были практически одинаковыми.Мне было любопытно, если бы диалект SQLAlchemy для pyodbc мог использовать преимущества setinputsizes для указания типов параметров (т. Е. pyodbc.SQL_VARCHAR вместо pyodbc.SQL_WVARCHAR), но, по-видимому, это не так.

ТакЯ бы сказал, что на данный момент лучше всего продолжать кодировать строковые значения в байтах, которые соответствуют набору символов столбца varchar, который вы запрашиваете (не utf-8).Конечно, вы также можете погрузиться в исходный код для диалекта (ов) SQLAlchemy и отправить PR, чтобы улучшить SQLAlchemy.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...