Python多线程和数据库请求?

时间:2013-11-15 13:47:04

标签: python multithreading

我正在构建一个使用Multithreading.Pool一次调用多个项目的脚本,我的结果很奇怪。

作为首发,这是我的(简化)代码,但经过测试和工作(或失败:p):

#!/usr/bin/env python

import MySQLdb
import time
import multiprocessing
import sys
import logging

TIMEOUT = 2500 # ms, 2,5 seconds
MAX_TIMEOUT = 120000 # ms, 2 minutes

DB_PARAMS = {
    'user': 'root',
    'passwd': '******',
    'db': 'my_db'
}

class CheckState:
    def __call__(self, row):
        print "Calling for %s" % (row[0], )

        # Retrieving n-1 instance
        entry_id = row[0]
        print entry_id
        last_log = database.find_unique("SELECT status FROM entry_logs WHERE entry_id = %s ORDER BY occured DESC LIMIT 1;", (entry_id, ))
        print entry_id, last_log


class DatabaseBridge:
    def __init__(self, *args, **kwargs):
        self.cnx = MySQLdb.connect (**kwargs)
        self.cnx.autocommit(True)
        self.cursor = self.cnx.cursor()

    def query_all(self, query, *args):
        self.cursor.execute(query, *args)
        return self.cursor.fetchall()

    def find_unique(self, query, *args):
        rows = self.query_all(query, *args);
        if len(rows) == 1:
            return rows[0]

        return None

    def execute(self, query, params):
        self.cursor.execute(query, params)
        return self.cursor.rowcount

    def close(self):
        self.cursor.close()
        self.cnx.close()

database = DatabaseBridge(**DB_PARAMS)


def main():
    start_time = time.time()
    pool = multiprocessing.Pool(multiprocessing.cpu_count())

    try:
        logging.info("===================================")
        rows = database.query_all("SELECT id FROM entries WHERE is_disabled = 0 AND removed IS NULL AND IFNULL(address, '') != '';")

        if len(rows) > 0:
            pool_timeout = len(rows) * TIMEOUT
            if pool_timeout > MAX_TIMEOUT:
                pool_timeout = MAX_TIMEOUT

            result = pool.map_async(CheckState(), rows)

            pool.close()
            pool.join()

            logging.info("Running for %d seconds max", float(pool_timeout)/1000)
            result.get(timeout=float(pool_timeout)/1000) # Maximum Timeout allowed for security reasons !

        end_time = time.time() - start_time
        logging.info("Took approx %.2f seconds to run.", end_time)

        database.close()
        pool.terminate()

        return 0
    except Exception, err:
        end_time = time.time() - start_time

        print "An error occured with the script"
        print "Took approx %.2f seconds to run." % (end_time, )

        logging.error("Took approx %.2f seconds to run.", end_time)
        logging.exception("Script failed")

        pool.terminate()
        database.close()

        return 1

if __name__ == "__main__":
    sys.exit(main())

在我的数据库中,表条目中有10行,在表entry_logs中,我有n *个条目行。

entry_logs中的状态可以是OK,DOWN或INVALID。

在我的数据库中,只有entry_id 5和10有DOWN或INVALID,所有其他8个总是有状态OK

现在,当我运行我的脚本时,我得到了这个:

Calling for 1
1
Calling for 2
2
Calling for 3
3
Calling for 4
4
1 ('OK',)
Calling for 6
6
Calling for 7
7
Calling for 5
5
6 ('OK',)
Calling for 8
8
8 ('OK',)
Calling for 9
9
9 ('OK',)
Calling for 10
10
10 ('OK',)
5 ('OK',)
4 ('INVALID',)
3 ('OK',)
7 ('OK',)

这里的关键是:

5 ('OK',)
10 ('OK',)
4 ('INVALID',)

这是不可能的, 5和10应该返回INVALID,4应该返回OK

所以我猜我的线程正在弄乱DatabaseBridge类,并且返回的数据在线程之间交叉而不是在正确的线程中返回,但是,我该如何解决?

奖励点:有时(不是每次),我的脚本都会挂起,永远不会返回。尽管我在timeout方法上强制get(),但我必须杀死主要的进程和一个孩子(可能是被吊死的那个)。你知道为什么吗?

1 个答案:

答案 0 :(得分:0)

好的,经过长时间的大量搜索,我认为我找到了原因,但解决方案(或替代方案)必须实施。

事实上,这个问题得到了很好的解释here

  

MySQLdb用户指南,该模块支持1级。(“线程可以共享模块,但不能连接。”)。

简而言之:我不应该为查询使用相同的连接,因为它不能保证为正确的线程返回结果。而这正是我所经历的。

现在我有两个选择:

1 - 使用Mysql Pool  2 - 将代码重写为Pool,只需要花费很长时间来计算,而不是整个工作。

我想我会选择第二个,更简单;)