У меня есть такая модель Note (это извлекается из устаревшей базы данных MS SQL Server, поэтому большинство этих записей не были созданы Django):
class Note(models.Model):
id = models.AutoField(primary_key=True, db_column="note_id")
content = models.TextField(db_column="note_content", blank=True, null=True)
date_created = models.DateTimeField(db_column="date_created", auto_now_add=True)
date_modified = models.DateTimeField(db_column="date_modified", null=True, blank=True)
date_removed = models.DateTimeField(db_column="date_deleted", null=True, blank=True)
Запуск .get
для некоторых записей возвращал DoesNotExist
, даже если они существовали в базе данных.
Оказывается, это происходит, когда длина содержимого поля MS SQL Server TEXT
(например, CREATE TABLE Foo ( content TEXT null)
) превышает определенную величину; конкретно 19455 символов.
Вот как это выглядит в действии:
>>> note = Note.objects.get(pk=1)
>>> note.content = "x" * 19455
>>> note.save()
>>> note = Note.objects.get(pk=1)
>>> note.content = "x" * 19456
>>> note.save()
>>> note = Note.objects.get(pk=1)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/path/to/django/db/models/query.py", line 366, in get
% self.model._meta.object_name)
DoesNotExist: Note matching query does not exist.
Я использую FreeTDS, и размер текста установлен на 2147483647, что похоже является верхним пределом для версии MS SQL Server, которую я использую.
Согласно этому другому вопросу об усечении, вы должны добавить инструкцию SET TEXTSIZE n
, где n является значением в байтах, и это решит проблему усечения данных. Поэтому я задавался вопросом, происходит ли это в моем случае, и исправит ли это это.
Итак, я пошел дальше и написал код, который использует только курсор и команду SET TEXTSIZE
.
Во-первых, давайте посмотрим, что должно быть в записи:
print "Длина: %d; Последние 40 символов: %s" % (len(note.content), note.content[-40:]) Длина: 19456; Последние 40 символов: rVEF1cCJeRaTtcdkXMqqQUxEVLZapMGVGSxMfJ2T
А теперь проходим по кругу. Каждый раз мы увеличиваем настройку TEXTSIZE и отображаем запись, если она нравится. Мы также отображаем длину и последние 10 символов возвращаемого поля записи.
>>> for xx in xrange(19450, 19460):
... cursor = connection.cursor()
... try:
... qrys = 'SET TEXTSIZE %d SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1' % xx
... print qrys
... qry = cursor.execute(qrys)
... record = qry.fetchone()
... if record:
... record_id, record_content = record
... print record_id, len(record_content), record_content[-10:]
... else:
... print "No record found after TEXTSIZE set to %d" % xx
... break
... except Exception, inst:
... print "Error: %s (%s)" % (inst, type(inst))
... break
... finally:
... cursor.close()
...
SET TEXTSIZE 19450 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
1 19450 VLZapMGVGS
SET TEXTSIZE 19451 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
1 19451 LZapMGVGSx
SET TEXTSIZE 19452 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
1 19452 ZapMGVGSxM
SET TEXTSIZE 19453 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
1 19453 apMGVGSxMf
SET TEXTSIZE 19454 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
1 19454 pMGVGSxMfJ
SET TEXTSIZE 19455 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
1 19455 MGVGSxMfJ2
SET TEXTSIZE 19456 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
No record found after TEXTSIZE set to 19456
>>>
Поэтому, как только мы пытаемся получить запись с TEXTSIZE, установленным на число больше 19456, никакие записи не возвращаются. И вы заметите, что последние 10 символов строки совпадают со строкой выше за вычетом символов, которые были пропущены из-за того, что они слишком короткие. Например, для последней найденной записи последние 10 символов равны MGVGSxMfJ2
. В реальной записи отсутствует T
, потому что TEXTSIZE 19455 на единицу меньше, чем длина рассматриваемого поля.
Итак, теперь, конечно, мне интересно, что происходит??? Могу ли я предпринять какие-либо дальнейшие действия по устранению неполадок, чтобы определить, является ли это проблемой с django-pyodbc, pyodbc или FreeTDS? Возможно, это также может быть SQL Server, но запуск SET TEXTSIZE 19456 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
непосредственно в Server Management Studio работает правильно и возвращает правильное количество символов.
Также обратите внимание, что сохранение работает:
>>> note.content = (note.content * 10)[:65536] # 65536 is max length allowed for TEXT, apparently
>>> len(note.content)
65536
>>> note.save()
>>> cursor = connection.cursor()
>>> qry = cursor.execute( 'SELECT [Notes].[note_id], DATALENGTH([Notes].[note_content]) FROM [Notes] WHERE [Notes].[note_id] = 1')
>>> record = qry.fetchone()
>>> record
(1, 65536)
>>>
TEXT
илиNVARCHAR(MAX)
или что-то еще? Использовали ли вы SQL Profiler для проверки того, что SQL, отправляемый на сервер, действительно соответствует вашим ожиданиям? - person Pondlife   schedule 20.11.2012TEXT
. Я посмотрю на использование SQL Profiler, посмотрим, даст ли это мне больше информации. - person Jordan Reiter   schedule 20.11.2012TEXT
в полеNVARCHAR
. Это устраняет проблему на 100%, но, очевидно, далеко не идеально, поскольку это означает просмотр схемы всех моих таблиц и их изменение. - person Jordan Reiter   schedule 20.11.2012TEXT
устарел в любом случае. - person Pondlife   schedule 20.11.2012