2010-09-10 3 views
3

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

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

Я создал 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, также, даже если я больше спать во втором вызове, чтобы первый вызов мог совершить код, я все равно получаю удаленные строки во втором вызове.

+0

Я запустил ваш код и не смог воспроизвести ошибку. Я попробовал это с MySQL (с использованием механизма InnoDB) и PostgreSQL, и строки всегда удаляются. Есть вероятность, что у вас может быть ошибка в библиотечной версии. Используемые мной версии: 'Django == 1.2.3' и' MySQL-python == 1.2.3' с 'Python 2.6' и' MySQL 14.14 (5.5.2-m2) '. Вы также можете попробовать проверить, не возникает ли проблема с PostgreSQL. –

+0

@Aram Dulyan, спасибо за проверку этого, надеюсь, вы попробовали его на apache, а не django devserver, я использую mysql с innodb, и я получаю это последовательно «строка не удалена»? –

+0

Поскольку вы работаете внутри транзакции, не удалялось бы только после того, как вы на самом деле совершили? –

ответ

3

Ваша проблема очаровал меня, так что я провел совсем немного времени, исследуя его, и единственный вывод, который я могу придумать, что это добросовестная ошибка в любом питона-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 в любом случае).
+0

hmm интересно, и это происходит только с транзакциями , может быть некоторая ошибка с транзакциями mysql, где вы видите результат, возвращаемый запросом, в вашем журнале я вижу только запросы –

+0

Чтобы получить результат, я добавил строку в 'django.db.models.sql.query.Query.get_aggregation() 'после строки' result = query.get_compiler (using) .execute_sql (SINGLE) ', которая будет записывать переменную' result' в файл. Файл оказался похожим на '000111'. –

0

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

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

+0

В моем реальном коде я не поймаю исключение, так или иначе он печатает «удаленная строка существует?» что означает, что исключение не возникает при получении удаленной строки. Просто, чтобы убедиться, что я попытался перейти на TimeCardDetail.DoesNotExist, но все же поведение такое же, как получилось, я могу получить такую ​​же строку после того, как r.delete –

0

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

Вы также можете попробовать добавить некоторые временные метки и свалку используемого sql. Установите DEBUG = True в settings.py, а затем вы can look at your raw sql queries.

# 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) 
1

Это выглядит так, как будто у вас есть вариант this ticket, указанный на djangoproject.com.

+0

Да выглядит похоже, но он говорит, что исправлено, и я все еще сталкиваюсь с проблемой –

Смежные вопросы