V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
wsds
V2EX  ›  Python

Python 多线程查询 mysql 报错,求解决办法

  •  
  •   wsds · 2018-04-25 19:36:11 +08:00 · 3738 次点击
    这是一个创建于 2410 天前的主题,其中的信息可能已经有所发展或是发生改变。
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # @Date    : 2018-04-25 18:04:06
    # @Author  : Your Name ([email protected])
    # @Link    : http://example.org
    # @Version : $Id$
    
    import threading,time,MySQLdb
    
    class TestMysql():
    
    	def __init__(self, ip, port, username, password, database):
    		self.db = MySQLdb.connect(host=ip, port=port, user=username, passwd=password, db=database, charset="utf8")
    		self.cursor = self.db.cursor()
    
    	def select1(self,tablename):
    		sql = "SELECT (gender & 15) AS gender1, f.image_data,f.json FROM intellif_face." + tablename + " f limit 100000;"
    		self.cursor.execute(sql)
    		data = self.cursor.fetchone()
    
    		print(data)
    
    	def select2(self,tablename):
    		sql = "SELECT (race & 15) AS race1,f.image_data,f.json FROM intellif_face." + tablename + " f limit 100000;"
    		self.cursor.execute(sql)
    		data = self.cursor.fetchone()
    
    		print(data)
    
    
    
    
    if __name__ == '__main__':
    	test = TestMysql("192.168.11.128", 3306, "root", 'introcks1234', "intellif_face")
    	tablelist = ['t_face_24','t_face_25','t_face_26']  # 可以增加表名
    	thread = []
    	for tablename in tablelist:
    		print(tablename)
    		t1 = threading.Thread(target = test.select1, args = (tablename,))
    		t2 = threading.Thread(target = test.select2, args = (tablename,))
    		thread.append(t1)
    		thread.append(t2)
    	for t in thread:
                    t.setDaemon(True)
    		t.start()
    	t.join()
    
    
    

    ============================output=========================================

    Exception in thread Thread-1:
    Traceback (most recent call last):
      File "C:\Python36\lib\threading.py", line 916, in _bootstrap_inner
        self.run()
      File "C:\Python36\lib\threading.py", line 864, in run
        self._target(*self._args, **self._kwargs)
      File "F:\code\pyProject\thread\thread_mysql_test.py", line 25, in select2
        self.cursor.execute(sql)
      File "C:\Python36\lib\site-packages\MySQLdb\cursors.py", line 250, in execute
        self.errorhandler(self, exc, value)
      File "C:\Python36\lib\site-packages\MySQLdb\connections.py", line 50, in defaulterrorhandler
        raise errorvalue
      File "C:\Python36\lib\site-packages\MySQLdb\cursors.py", line 247, in execute
        res = self._query(query)
      File "C:\Python36\lib\site-packages\MySQLdb\cursors.py", line 411, in _query
        rowcount = self._do_query(q)
      File "C:\Python36\lib\site-packages\MySQLdb\cursors.py", line 374, in _do_query
        db.query(q)
      File "C:\Python36\lib\site-packages\MySQLdb\connections.py", line 277, in query
        _mysql.connection.query(self, query)
    _mysql_exceptions.OperationalError: (2013, 'Lost connection to MySQL server during query')
    
    .......
    
    [Finished in 1.9s with exit code 3221226356]
    
    
    18 条回复    2018-04-27 20:27:02 +08:00
    ericls
        1
    ericls  
       2018-04-25 20:32:25 +08:00 via iPhone
    执行前检查是否需要重新连接 如果需要就重新连接
    tkmiles
        2
    tkmiles  
       2018-04-25 20:37:04 +08:00
    我貌似记得 mysql 会杀死空闲连接的
    wsds
        3
    wsds  
    OP
       2018-04-25 20:39:28 +08:00
    @ericls 怎么检查
    testsec
        4
    testsec  
       2018-04-25 21:45:36 +08:00 via iPhone
    用连接池
    Aliencn
        5
    Aliencn  
       2018-04-25 21:50:44 +08:00
    数据库承受不住这么大的查询吧,把 sql 语句的 limit 改成 1 试试。
    lesteryu
        6
    lesteryu  
       2018-04-25 21:57:34 +08:00
    MySQLdb 的 connections 和 cursors 都不 thread safe,不同的 threads 不能用同一个 connection 或者 cursor。
    MerleLiuKun
        7
    MerleLiuKun  
       2018-04-26 08:49:13 +08:00 via Android
    check connection 可以用 ping(). 感觉可以用 MySQLdb 提供的 pool.
    wsds
        8
    wsds  
    OP
       2018-04-26 09:01:33 +08:00
    @Aliencn 一样的不行
    wsds
        9
    wsds  
    OP
       2018-04-26 09:04:17 +08:00
    @lesteryu 5 个方法各连一次 MySQl ?
    xpresslink
        10
    xpresslink  
       2018-04-26 09:16:11 +08:00
    要每个线程单独用一个 连接,把 test = TestMysql("192.168.11.128", 3306, "root", 'introcks1234', "intellif_face")
    放在 for 里面就可以了

    if __name__ == '__main__':

    □□□□tablelist = ['t_face_24','t_face_25','t_face_26'] # 可以增加表名
    □□□□thread = []
    □□□□for tablename in tablelist:
    □□□□□□□□test = TestMysql("192.168.11.128", 3306, "root", 'introcks1234', "intellif_face")
    □□□□□□□□print(tablename)
    □□□□□□□□t1 = threading.Thread(target = test.select1, args = (tablename,))
    □□□□□□□□t2 = threading.Thread(target = test.select2, args = (tablename,))
    □□□□□□□□thread.append(t1)
    □□□□□□□□thread.append(t2)
    □□□□for t in thread:
    t.setDaemon(True)
    t.start()
    □□□□t.join()
    Hopetree
        11
    Hopetree  
       2018-04-26 09:24:59 +08:00
    借楼问一下,往 MySQL 里面插入多个数据的做法,有两种做法①开启一个连接,然后循环插入数据,最后断开连接②循环(开启连接,插入,断开)这个步骤,也就是每次插入都重新练级,我一直有点疑惑这两种方式,从性能和安全等给方面考虑,应该是采取哪一种?我用 Python 的时候喜欢用第一种,里面使用 try 语句来插入
    wsds
        12
    wsds  
    OP
       2018-04-26 09:32:09 +08:00
    @xpresslink 那就是要每个方法中各设各的连接咯,偷懒放到 init 里边,看来还是得独立出来
    xpresslink
        13
    xpresslink  
       2018-04-26 09:52:34 +08:00
    @wsds
    这个只是临时方案,连接太多数据库也受不了,
    而且每个线程创建了连接要关闭。不然时间长了爆内存了。

    实际上你应该弄个连接池,每个线程到连接池取一个打开的连接,超过连接数就等待。

    连接最后在退出时候统一关闭。
    wsds
        14
    wsds  
    OP
       2018-04-26 10:08:54 +08:00
    @xpresslink 连接池怎么搞?
    long88
        15
    long88  
       2018-04-26 15:56:18 +08:00
    @wsds 可以用 DBUtils
    wsds
        16
    wsds  
    OP
       2018-04-26 16:17:24 +08:00
    @long88 我查一下这个
    Minys
        17
    Minys  
       2018-04-27 00:11:10 +08:00 via iPhone
    @xpresslink 自己写的线程池操作也可能有意想不到的危险,因为 Python 的 GIL,多线程的时候其实是通过 CPU 不断切换执行,让用户看起来像多线程。这就会出现 thread1 和 thread2 在对同一个对象操作的时候出现问题。

    比如两个 thread1 和 thread2 都要执行对 A.a+=1 的操作,但是这个看似基本的操作其实分 3 步:
    1. tmp=getattr(A, 'a')
    2. tmp =tmp+1
    3. setattr(A, 'a', tmp)

    如果刚好在这中间切换线程,就会导致奇怪的 bug,根本无法 debug,感觉还是使用 multiprocessing 里面的 Pool 来直接多进程操作比较好。多线程的话还是得用 threading 库确保不会冲突,必要的时候还是得上锁。
    sujin190
        18
    sujin190  
       2018-04-27 20:27:02 +08:00
    首先 MySQLdb 并不是线程安全的,而且 mysql 协议是 request respond 模式的,并不能在上一个查询未完成之前发送下一个查询请求,这种基础问题随便一查都知道的吧,多线程不加锁挂才是正常的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2777 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 09:35 · PVG 17:35 · LAX 01:35 · JFK 04:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.