2010-09-10 43 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上。 – 2010-09-17 08:53:54

+0

@Aram Dulyan,感谢您检查了这一点,我希望你在apache上尝试过,而不是django devserver,我使用mysql和innodb,并且我始终如一地获取这个“行未删除?”。 – 2010-09-17 11:29:01

+0

既然你正在一个事务中工作,那么删除只会在你实际提交后才会发生? – 2010-09-20 21:12:18

回答

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 

和当然,任何后续尝试在会话关闭后计数显示该行实际上已被删除。此外,在django.db.models.sql.query中记录收到的SQL结果显示紧接在DELETE语句之后的SELECT COUNT语句事实上在上述日志的后半部分中返回1,而不是预期的0。我对此没有任何解释。

据我所知,只有两个选项可用于获得您想要的功能。我已经验证他们两人的工作:

  • 在Apache的conf,设置MaxClientsThreadsPerChild为1(不是一个非常实用的选择)。
  • 使用PostgreSQL(我会推荐给任何使用MySQL的人)。
+0

之后获得相同的行,并且仅在事务中发生,可能是mysql事务的一些错误,你在哪里看到查询返回的结果,在你的日志中我只看到查询 – 2010-09-22 16:13:14

+0

为了得到结果,我在'django.db.models.sql.query.Query中添加了一行。get_aggregation()'在'result = query.get_compiler(using).execute_sql(SINGLE)'这一行之后,它会将'result'变量记录到一个文件中。该文件最终看起来像'000111'。 – 2010-09-22 23:30:08

0

我怀疑你的问题是你认为的问题。请注意,第二次没有打印异常。

问题是,您正在捕获所有异常,而不是您处理的'TimeCardDetail.DoesNotExist'异常。这在发生意外事件时掩盖了真正的问题。用特定的例外替换catch-all'Exception',然后看看会发生什么。

+0

在我的真实代码中,我没有捕获异常,无论如何它打印“删除的行存在?这意味着在创建已删除的行时不会出现异常,只是为了确保我尝试更改为TimeCardDetail.DoesNotExist,但行为仍然相同,如何在r.delete – 2010-09-10 07:53:23

0

您可能正在处理对缓存对象的查找。另外,如果你正在处理缓存对象,它们可能只会显示你的apache设置,因为请求是由两个不同的进程并行处理的。尝试将apache工作进程的数量减少到1,并查看该行为是否与在dev服务器中运行时相同(./manage.py runserver)。

您还可以尝试添加一些时间戳和正在使用的SQL转储。在settings.py中设置DEBUG = True,然后你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

它看起来好像你有一个在djangoproject.com报道的this ticket变体。

+0

是的看起来相似,但它说固定,我仍然面临问题 – 2010-09-24 09:00:10