django удаленная строка существует? - PullRequest
4 голосов
/ 10 сентября 2010

Я сталкиваюсь с очень специфической проблемой: даже когда я удаляю некоторые строки, я могу просто вернуть их снова в той же транзакции. Я запускаю это под apache и mod_wsgi, база данных mysql.

Edit: Я создал пример приложения для его тестирования, чтобы быть уверенным, что ни один из моих кодов не является виновником.

Я создал testapp со следующим кодом

models.py

import uuid
from django.db import models

class TestTable(models.Model):
    id = models.CharField(max_length=36, primary_key=True)
    name = models.CharField(max_length=50)

    @classmethod
    def get_row(cls, name):
        return TestTable(id=str(uuid.uuid4()), name=name)

    def __unicode__(self):
        return u"%s[%s]"%(self.name, self.id)

views.py

import traceback
import time
from django.db import transaction
from django.http import HttpResponse

from testapp.models import TestTable

@transaction.commit_manually
def test_view(request):
    time.sleep(1)
    out = []
    try:
        # delete 3 rows
        for row in TestTable.objects.all()[:3]:
            ID=row.id
            out.append("deleting %s"%row)
            row.delete()
            # check fi really deleted
            try:
                TestTable.objects.get(id=ID)
                out.append("row not deleted?")
            except TestTable.DoesNotExist,e:
                out.append("row deleted.")

        # create 5 rows
        for i in range(5):
            row = TestTable.get_row("row %s"%i)
            row.save()

    except Exception,e:
        out.append("Error:%s"%traceback.format_exc())
        transaction.rollback()
    else:
        transaction.commit()

    return HttpResponse('\n'.join(out), 'text/text')

urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('testapp.views', (r'^test_bug$', 'test_view')

TestScript

import urllib2
from multiprocessing import Process

def get_data():
    r = urllib2.urlopen("http://localhost:81/timeapp/test/test_bug")
    print "---------"
    print r.read()

if __name__ == "__main__":
    for i in range(2):
        p = Process(target=get_data)
        p.start()

Выход:

$ python test.py 
---------
deleting row 1[3ad3a82e-830f-4540-8148-88479175ed5e]
row deleted.
deleting row 0[544462d1-8588-4a8c-a809-16a060054479]
row deleted.
deleting row 3[55d422f3-6c39-4c26-943a-1b4db498bf25]
row deleted.
---------
deleting row 1[3ad3a82e-830f-4540-8148-88479175ed5e]
row not deleted?
deleting row 0[544462d1-8588-4a8c-a809-16a060054479]
row not deleted?
deleting row 3[55d422f3-6c39-4c26-943a-1b4db498bf25]
row not deleted?

Так что мой вопрос в том, почему удаленная строка снова может быть получена с помощью TestTable.objects.get, также даже если я сплю больше во время второго вызова, чтобы первый вызов мог принять код, я все равно получаю удаленные строки во втором вызове.

Ответы [ 4 ]

4 голосов
/ 22 сентября 2010

Ваша проблема очаровала меня, поэтому я потратил немало времени на ее изучение, и единственный вывод, к которому я могу прийти, - это то, что это истинная ошибка в Python-MySQL или MySQL.Вот что я попробовал:

Я должен отметить, что я немного изменил код, чтобы вместо:

try:
    TestTable.objects.get(id=ID)
    out.append("row not deleted?")
except TestTable.DoesNotExist,e:
    out.append("row deleted.")

у меня было:

c = TestTable.objects.filter(id=ID).count()
if c:
    out.append("row not deleted?")
else:
    out.append("row deleted.")

Это сделало это немногопроще в отладке и никак не повлияло на проявление проблемы.

Во-первых, кеширование Django здесь не виновато.Запросы для получения количества были выполнены, как можно видеть в журналах MySQL (1 и 2 представляют два отдельных одновременных соединения, которые были сделаны):

1 Query SET NAMES utf8
2 Query SET NAMES utf8
2 Query set autocommit=0
1 Query set autocommit=0
1 Query SELECT `testapp_testtable`.`id`, `testapp_testtable`.`name` FROM `testapp_testtable` LIMIT 3
2 Query SELECT `testapp_testtable`.`id`, `testapp_testtable`.`name` FROM `testapp_testtable` LIMIT 3
2 Query DELETE FROM `testapp_testtable` WHERE `id` IN ('32f027ff-c798-410b-8621-c2d47e2cfa7c')
1 Query DELETE FROM `testapp_testtable` WHERE `id` IN ('32f027ff-c798-410b-8621-c2d47e2cfa7c')
2 Query SELECT COUNT(*) FROM `testapp_testtable` WHERE `testapp_testtable`.`id` = '32f027ff-c798-410b-8621-c2d47e2cfa7c'
2 Query DELETE FROM `testapp_testtable` WHERE `id` IN ('3f693297-9993-4162-98c4-a9ca68232c75')
2 Query SELECT COUNT(*) FROM `testapp_testtable` WHERE `testapp_testtable`.`id` = '3f693297-9993-4162-98c4-a9ca68232c75'
2 Query DELETE FROM `testapp_testtable` WHERE `id` IN ('96f9a1f7-c818-4528-858f-4e85a93de5c3')
2 Query SELECT COUNT(*) FROM `testapp_testtable` WHERE `testapp_testtable`.`id` = '96f9a1f7-c818-4528-858f-4e85a93de5c3'
2 Query SELECT (1) AS `a` FROM `testapp_testtable` WHERE `testapp_testtable`.`id` = '035c90ba-82a6-4bdc-afe1-318382563017'  LIMIT 1
2 Query INSERT INTO `testapp_testtable` (`id`, `name`) VALUES ('035c90ba-82a6-4bdc-afe1-318382563017', 'row 0')
2 Query SELECT (1) AS `a` FROM `testapp_testtable` WHERE `testapp_testtable`.`id` = '15393978-4200-4b98-98e6-73636c39dd1c'  LIMIT 1
2 Query INSERT INTO `testapp_testtable` (`id`, `name`) VALUES ('15393978-4200-4b98-98e6-73636c39dd1c', 'row 1')
2 Query SELECT (1) AS `a` FROM `testapp_testtable` WHERE `testapp_testtable`.`id` = '22459ba2-18d5-4175-ac6b-2377ba63ecc7'  LIMIT 1
2 Query INSERT INTO `testapp_testtable` (`id`, `name`) VALUES ('22459ba2-18d5-4175-ac6b-2377ba63ecc7', 'row 2')
2 Query commit
2 Quit  
1 Query SELECT COUNT(*) FROM `testapp_testtable` WHERE `testapp_testtable`.`id` = '32f027ff-c798-410b-8621-c2d47e2cfa7c'
1 Query DELETE FROM `testapp_testtable` WHERE `id` IN ('3f693297-9993-4162-98c4-a9ca68232c75')
1 Query SELECT COUNT(*) FROM `testapp_testtable` WHERE `testapp_testtable`.`id` = '3f693297-9993-4162-98c4-a9ca68232c75'
1 Query DELETE FROM `testapp_testtable` WHERE `id` IN ('96f9a1f7-c818-4528-858f-4e85a93de5c3')
1 Query SELECT COUNT(*) FROM `testapp_testtable` WHERE `testapp_testtable`.`id` = '96f9a1f7-c818-4528-858f-4e85a93de5c3'
1 Query SELECT (1) AS `a` FROM `testapp_testtable` WHERE `testapp_testtable`.`id` = '6dc6e901-bebe-4f3b-98d1-c8c4a90d06df'  LIMIT 1
1 Query INSERT INTO `testapp_testtable` (`id`, `name`) VALUES ('6dc6e901-bebe-4f3b-98d1-c8c4a90d06df', 'row 0')
1 Query SELECT (1) AS `a` FROM `testapp_testtable` WHERE `testapp_testtable`.`id` = 'c335ccad-31c6-4ddd-bccd-578435cd6e7b'  LIMIT 1
1 Query INSERT INTO `testapp_testtable` (`id`, `name`) VALUES ('c335ccad-31c6-4ddd-bccd-578435cd6e7b', 'row 1')
1 Query SELECT (1) AS `a` FROM `testapp_testtable` WHERE `testapp_testtable`.`id` = '2c507629-a87e-48ec-b80d-2f758cd16c44'  LIMIT 1
1 Query INSERT INTO `testapp_testtable` (`id`, `name`) VALUES ('2c507629-a87e-48ec-b80d-2f758cd16c44', 'row 2')
1 Query commit
1 Quit  

И, конечно, любые последующие попыткиполучение отсчета после закрытия сеанса показывает, что строка была фактически удалена.Кроме того, регистрация полученных результатов SQL в django.db.models.sql.query показывает, что операторы SELECT COUNT, следующие непосредственно за операторами DELETE во второй половине вышеприведенного журнала, фактически возвращали 1 вместо ожидаемого 0.У меня нет объяснения этому.

Насколько я могу судить, есть только два варианта, чтобы получить желаемую функциональность.Я проверил, что они оба работают:

  • В вашей конфигурации Apache установите MaxClients и ThreadsPerChild на 1 (не очень практичный вариант).
  • Используйте PostgreSQL(Я бы порекомендовал это всем, кто использует MySQL в любом случае).
1 голос
/ 23 сентября 2010

Мне кажется, у вас есть вариант этого билета , о котором сообщается на djangoproject.com.

0 голосов
/ 19 сентября 2010

Возможно, вы имеете дело с поиском кэшированных объектов.Кроме того, если вы имеете дело с кэшированными объектами, они, вероятно, обнаруживаются только при вашей настройке apache, поскольку запросы обрабатываются параллельно двумя разными процессами.Попробуйте уменьшить количество рабочих процессов apache до 1 и посмотрите, не отличается ли поведение от того, что вы запускаете на сервере dev (./manage.py runserver).

Вы также можете попробовать добавить несколько временных меток и дампSQL используется.Установите DEBUG = True в settings.py, и тогда вы сможете просмотреть свои необработанные запросы sql .

# views.py
from django.db import connection

def test_view(request):
    connection.queries = []
    start_time = time.time()
    out = []
    out.append("%09.6f" % (time.time() % 100))  # something like 13.45678
    time.sleep(1)
    [...]
        # delete 3 rows
            [...]
            out.append("deleting %s"%row)
            out.append("%09.6f" % (time.time() % 100))
            [...]
            out.append("%d queries after the last delete" %d len(connection.queries))
        # create 5 rows
    [...]
    out.append("%09.6f total time spent" % (time.time() - start_time))
    out.append("%d queries TOTAL" %d len(connection.queries))
    # dump the actual queries if you are still digging.
    for q in connection.queries:
        out.append("%s\n----" % q)
0 голосов
/ 10 сентября 2010

Я сомневаюсь, что твоя проблема в том, что ты думаешь. Обратите внимание, что во второй раз вы не получите напечатанное исключение.

Проблема в том, что вы перехватываете все исключения, а не только то, которое вы обрабатываете, TimeCardDetail.DoesNotExist. Это маскирует реальную проблему, когда происходит что-то неожиданное. Замените универсальное «кроме исключения» этим конкретным исключением и посмотрите, что произойдет.

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