python pymysql multiprocessing.dummy多线程 读写数据库报错

本文探讨了使用Python的multiprocessing.dummy模块下载视频时遇到的数据库连接问题,包括并发下数据库一致性问题和解决方案。作者提供了三种改进方法:加锁、每个线程独立的数据库连接和数据库链接池,以确保并发下载时数据的正确性和性能优化。
摘要由CSDN通过智能技术生成


一、例子

  • 需求
    使用多线程下载视频到本地,将视频的名字保存在数据库表中,数据库表中不能保存重复视频名字
  • demo.py
from multiprocessing.dummy import Pool
import traceback
import requests
import pymysql
import os
# 习惯函数名开头大写,变量名开头小写,还没适应Python写代码规范,见谅

# 数据库链接类
class KsMySql:
    def __init__(self):
        self.conn = pymysql.Connect(host='127.0.0.1', port=3306, user='root', password='1234567', db='pythonspider', charset='utf8mb4') # 普通链接
        self.cursor = self.conn.cursor()

    # 是否保存过了通过视频名称查找, 返回true为不存在,false为存在
    def IsSaveVideoByName(self, filename):
        try:
            self.cursor.execute('select * from ksvideoinfo where filename = "%s"' %(filename))
            result = self.cursor.fetchone()
            return result is not None
        except:
            print('IsSaveVideoByName 查询错误')
            traceback.print_exc()
            return True

    # 插入视频信息
    def SaveVideoInfo(self, filename):
        try:
            self.cursor.execute('insert into ksvideoinfo(filename) values("%s")'%(filename))
            self.conn.commit()
            print('SaveVideoInfo 插入数据成功')
        except Exception as e:
            self.conn.rollback()
            print('SaveVideoInfo 插入数据错误')
            print(e)
            traceback.print_exc()

    def __del__(self):
        self.cursor.close()
        self.conn.close()

# 全局变量
ksmysql = KsMySql()# 数据库类实例
infolist = []
dirName = 'E:/AllWorkSpace1/Pytharm/pythonProjectPaWeb/Testdemo'# 保存目录
if not os.path.exists(dirName):
    os.mkdir(dirName)

def Select():
    name = '杨洋迪丽热巴《烟火星辰》,用歌声致敬中国航天'
    # 需求3:数据库表中不能保存重复视频名字(这里只是模拟)
    isSave = ksmysql.IsSaveVideoByName(name)

    # 为了方便,默认为不存在,直接添加url到list中
    mp4url = 'https://video.pearvideo.com/mp4/short/20220206/cont-1751191-15823342-hd.mp4'
    infolist.append({'name': name, 'videoUrl': mp4url})

def SaveInfo(dic):
    name = dic['name']
    pathName = dirName + '/' + name + '.mp4'
    url = dic['videoUrl']
    try:
        # if not os.path.exists(pathName):
        mp4Data = requests.get(url=url).content # 从网络下载视频
        with open(pathName, 'wb') as f:# 需求1:视频保存在本地
            f.write(mp4Data)
            print(name, "下载完成")
        # else:
        #     print(name,'已存在,无需下载')
        # 需求2:视频的名字保存在数据库表中
        ksmysql.SaveVideoInfo(name)
    except Exception as e:
        print(name, '下载失败失败或者保存数据库失败')
        print(e)
        traceback.print_exc()

def Main():
    pool1 = Pool(20) # 线程池
    for cur in range(0, 100):
        infolist.clear()
        Select()
        pool1.map(SaveInfo, infolist) # 使用多线程下载
    pool1.close()
    pool1.join()

Main()

二、报错及原因

  • 常见错误
    1). Packet sequence number wrong
    2). Exception _mysql_exceptions.OperationalError: (2013, ‘Lost connection to MySQL server during query’)
    3). pymysql AttributeError: ‘NoneType‘ object has no attribute ‘settimeout‘
  • 原因
    如上demo.py,是因为各个线程共享同一个数据库链接而导致的错误

三、解决方法

1.在每个execute前加上互斥锁

如:

...同上
import threading
class KsMySql:
    def __init__(self):
        self.conn = pymysql.Connect(host='127.0.0.1', port=3306, user='root', password='tiger', db='pythonspider', charset='utf8mb4') # 普通链接
        self.cursor = self.conn.cursor()
		self.lock = threading.Lock()# 实例化
		
	# 是否保存过了通过视频名称查找, 返回true为不存在,false为存在
    def IsSaveVideoByName(self, filename):
        try:
        	self.lock.acquire() # 上锁
            self.cursor.execute('select * from ksvideoinfo where filename = "%s"' %(filename))
            result = self.cursor.fetchone()
            self.lock.release() # 解锁
            return result is not None
        except:
            print('IsSaveVideoByName 查询错误')
            traceback.print_exc()
            return True
...同上

但经过我个人测试发现,没有用,还是会报新错,这个方法理论上是没问题的,但是在multiprocessing.dummy多线程情况下却不行。仅代表我个人想法,也许自己能力不足,哪里写错了

2.在pool1.map(func, list)中参数的func函数中,实例化一个数据库对象

...
def SaveInfo(dic):
	ksmysql = KsMySql()# 数据库类实例
    name = dic['name']
    pathName = dirName + '/' + name + '.mp4'
    url = dic['videoUrl']
    try:
        # if not os.path.exists(pathName):
        mp4Data = requests.get(url=url).content # 从网络下载视频
        with open(pathName, 'wb') as f:# 需求1:视频保存在本地
            f.write(mp4Data)
            print(name, "下载完成")
        # else:
        #     print(name,'已存在,无需下载')
        # 需求2:视频的名字保存在数据库表中
        ksmysql.SaveVideoInfo(name)
    except Exception as e:
        print(name, '下载失败失败或者保存数据库失败')
        print(e)
        traceback.print_exc()
...

可以完美解决,因为这样每个线程都有自己的数据库链接对象。
优点:简单、方便
缺点:每调用SaveInfo函数一次就建立一个数据库链接,并函数结束时关闭链接,可能性能有损

3.在KsMySql数据库链接类中使用数据库链接池获取链接,将pool链接池为类对象

...
from dbutils.pooled_db import PooledDB

class KsMySql:
    pool = None
    def __init__(self):
        # self.conn = pymysql.Connect(host='127.0.0.1', port=3306, user='root', password='tiger', db='pythonspider', charset='utf8mb4') # 普通链接,每实例化一个对象就会新建一个链接
        self.conn = KsMySql.Getmysqlconn()# 从链接池中获取链接
        self.cursor = self.conn.cursor()

    # 静态方法
    @staticmethod
    def Getmysqlconn():
        if KsMySql.pool is None:
            mysqlInfo = {
                "host": '127.0.0.1',
                "user": 'root',
                "passwd": 'tiger',
                "db": 'pythonspider',
                "port": 3306,
                "charset": 'utf8mb4'
            }
            KsMySql.pool = PooledDB(creator=pymysql, mincached=1, maxcached=20, host=mysqlInfo['host'],
                              user=mysqlInfo['user'], passwd=mysqlInfo['passwd'], db=mysqlInfo['db'],
                              port=mysqlInfo['port'], charset=mysqlInfo['charset'], blocking=True)
            print(KsMySql.pool)
        # else:
            # print('新KsMySql实例,从数据库链接池获取链接')
        return KsMySql.pool.connection()
        ...
         def __del__(self):
	        # 链接不是真正的被关闭,而是放回链接池中
	        self.cursor.close()
	        self.conn.close()

def SaveInfo(dic):
	ksmysql = KsMySql()# 同样要写上实例化数据库类对象
	...
...

注意

KsMySql.pool = PooledDB(creator=pymysql, mincached=1, maxcached=20, host=mysqlInfo['host'],
                              user=mysqlInfo['user'], passwd=mysqlInfo['passwd'], db=mysqlInfo['db'],
                              port=mysqlInfo['port'], charset=mysqlInfo['charset'], blocking=True)
'''
blocking参数,代表当链接都被占用了,是否等待新的空闲链接
True :等待, 可能影响程序速度
False:不等待,(个人猜测。。好像是代表同用已占有的数据库链接对象,会重复一开始的报错),反正会报错,最好写成True
'''

可以完美解决,因为这样每个线程也都有自己的数据库链接对象。
优点:从链接池中获取自己的链接,优化点性能把
缺点:代码稍微复杂,坑多。。。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: pymysql.connect是Python中连接MySQL数据库的模块,其用法如下: 1. 导入pymysql模块 import pymysql 2. 建立数据库连接 db = pymysql.connect(host='localhost', port=3306, user='root', password='123456', database='test', charset='utf8') 其中,host为主机名,port为端口号,user为用户名,password为密码,database为要连接的数据库名,charset为字符集。 3. 创建游标对象 cursor = db.cursor() 4. 执行SQL语句 cursor.execute('SELECT * FROM students') 其中,students为要查询的表名。 5. 获取查询结果 result = cursor.fetchall() 6. 关闭游标和数据库连接 cursor.close() db.close() 以上就是pymysql.connect的用法。 ### 回答2: pymysql.connect()是Python中连接MySQL数据库的方法,它的用法如下: pymysql.connect(host, user, password, database, charset, cursorclass) 1. host:要连接的MySQL服务器的主机名或IP地址。 2. user:数据库的用户名。 3. password:数据库的密码。 4. database:要连接的数据库名。 5. charset:数据库使用的字符集,默认为'utf8'。 6. cursorclass:游标类,默认为pymysql.cursors.Cursor。 示例代码如下: ```python import pymysql # 连接数据库 connection = pymysql.connect(host='localhost', user='root', password='password', database='testdb', charset='utf8') # 获取游标 cursor = connection.cursor() # 执行数据库操作 sql = "SELECT * FROM users" cursor.execute(sql) # 获取查询结果 result = cursor.fetchall() # 关闭游标和连接 cursor.close() connection.close() ``` 通过pymysql.connect()方法,我们可以连接到MySQL数据库并执行各种数据库操作,如增删改查等。我们首先需要提供正确的数据库连接信息,包括主机名、用户名、密码等。然后,我们还可以指定要连接的数据库名、字符集及游标类等。 连接成功后,可以通过cursor的execute()方法执行SQL语句,并通过fetchall()方法获取结果。最后,记得关闭游标和连接以释放资源。 以上就是pymysql.connect()方法的用法及示例。 ### 回答3: 在Python中,pymysql.connect是一个用于连接MySQL数据库的模块。它是pymysql模块中的一个函数,用于建立与MySQL数据库的连接。 pymysql.connect的用法如下: 1. 导入pymysql模块:首先需要在代码中导入pymysql模块,以便使用其中的功能。 ```python import pymysql ``` 2. 建立连接:使用pymysql.connect函数建立与MySQL数据库的连接。函数的参数包括主机名(host)、用户名(user)、密码(password)、数据库名(db)等。 ```python conn = pymysql.connect(host='localhost', user='root', password='password', db='database_name') ``` 3. 创建游标:使用连接对象的cursor方法创建一个游标对象,用于执行SQL语句和获取查询结果。 ```python cursor = conn.cursor() ``` 4. 执行SQL语句:使用游标对象的execute方法执行SQL语句。 ```python sql = "SELECT * FROM table_name" cursor.execute(sql) ``` 5. 获取查询结果:使用游标对象的fetchall方法获取查询结果。fetchall方法会返回所有的查询结果,可以通过循环遍历来获取每一条结果。 ```python results = cursor.fetchall() for row in results: # 处理每一行的数据 ``` 6. 关闭游标和连接:使用游标对象的close方法关闭游标,连接对象的close方法关闭连接。 ```python cursor.close() conn.close() ``` 以上就是pymysql.connect的基本用法。通过这些步骤,我们可以使用Python与MySQL数据库进行交互,执行SQL语句并获取查询结果。当连接建立成功后,还可以使用连接对象的其他方法执行插入、更新、删除等操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘建杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值