查看: 278|回复: 0

PyMySQL连接数据库实战:连接参数、事务提交与批量插入性能避坑

[复制链接]
发表于 3 小时前 | 显示全部楼层 |阅读模式
PyMySQL是纯Python实现的MySQL驱动,安装不需要编译C扩展,在Windows、Linux、macOS上表现一致,API也高度兼容MySQLdb,支持Python 3全系,并能配合MySQL 8.x使用;原文还提到MariaDB在驱动层面同样兼容。下面围绕原文的用户表示例,按安装、连接参数、增删改查、事务、连接池和常见报错重组关键代码与排查路径。

一、环境安装与表结构准备
先安装并确认版本,避免多Python环境装错:
  1. pip install pymysql
  2. import pymysql
  3. print(pymysql.__version__)
复制代码
在项目里可用requirements.txt锁版本,例如pymysql==1.1.0。写代码前先确认MySQL服务可用,并用最小权限账号连接。建库、建用户和演示表如下:
  1. CREATE DATABASE demo_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  2. CREATE USER 'demo_user'@'localhost' IDENTIFIED BY 'your_password';
  3. GRANT SELECT, INSERT, UPDATE, DELETE ON demo_db.* TO 'demo_user'@'localhost';
  4. FLUSH PRIVILEGES;
  5. USE demo_db;
  6. CREATE TABLE users (
  7.   id INT AUTO_INCREMENT PRIMARY KEY,
  8.   username VARCHAR(64) NOT NULL,
  9.   email VARCHAR(128),
  10.   age INT,
  11.   created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  12.   UNIQUE KEY uk_username (username)
  13. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
复制代码
字符集统一用utf8mb4,因为MySQL的utf8实际是utf8mb3,无法完整支持emoji和生僻字。权限不要直接GRANT ALL。

二、连接参数与资源管理
最小连接示例如下,注意host使用127.0.0.1而不是localhost,部分系统会把localhost解析到IPv6的::1,而MySQL默认监听IPv4,容易出现Can't connect to MySQL server on 'localhost'。
  1. import pymysql
  2. conn = pymysql.connect(
  3.   host='127.0.0.1',
  4.   port=3306,
  5.   user='demo_user',
  6.   password='your_password',
  7.   database='demo_db',
  8.   charset='utf8mb4'
  9. )
  10. try:
  11.   cursor = conn.cursor()
  12.   cursor.execute('SELECT VERSION()')
  13.   row = cursor.fetchone()
  14.   print('MySQL版本:', row[0])
  15. finally:
  16.   cursor.close()
  17.   conn.close()
复制代码
常用参数中,connect_timeout默认10秒,网络环境差时按需调整;max_allowed_packet默认67108864,插入大数据包时要调大;autocommit默认False,执行INSERT、UPDATE、DELETE后必须手动conn.commit(),否则看似执行成功但数据没有落库。该行为是事务保护机制,不是bug。PyMySQL连接和游标支持上下文管理,推荐用with自动释放:
  1. with pymysql.connect(
  2.   host='127.0.0.1',
  3.   user='demo_user',
  4.   password='your_password',
  5.   database='demo_db',
  6.   charset='utf8mb4'
  7. ) as conn:
  8.   with conn.cursor() as cursor:
  9.     cursor.execute('SELECT id, username, email FROM users LIMIT 5')
  10.     rows = cursor.fetchall()
  11.     for row in rows:
  12.       print(row)
复制代码

三、查询、分页与插入性能
游标提供fetchone()、fetchmany(size)、fetchall()。数据量小用fetchall没问题,几十万行全量加载可能撑爆内存,应使用fetchmany分块或服务端分页。分页查询示例:
  1. page_size = 100
  2. offset = 0
  3. while True:
  4.   cursor.execute(
  5.     'SELECT id, username, email FROM users ORDER BY id LIMIT %s OFFSET %s',
  6.     (page_size, offset)
  7.   )
  8.   rows = cursor.fetchall()
  9.   if not rows:
  10.     break
  11.   for row in rows:
  12.     print(row)
  13.   offset += page_size
复制代码
插入单条可用cursor.lastrowid拿自增ID。批量插入优先executemany,原文本地实测1000条数据,单条循环约1.5秒,executemany约0.2秒,差距约7到10倍;十万级以上可考虑LOAD DATA LOCAL INFILE。示例:
  1. sql = 'INSERT INTO users (username, email, age) VALUES (%s, %s, %s)'
  2. data = []
  3. for i in range(1000):
  4.   data.append((f'user_{i}', f'user_{i}@example.com', 20 + i % 30))
  5. cursor.executemany(sql, data)
  6. conn.commit()
  7. print('批量插入记录数:', cursor.rowcount)
复制代码
字段多时用DictCursor按字段名访问,便于JSON序列化和模板渲染;追求极致性能时默认元组游标略快,但万级以下差异可忽略。超大结果集用SSCursor或SSDictCursor,服务端保留游标、逐行拉取;同一连接上SSCursor是独占的,不能同时开第二个游标执行别的查询。

四、事务控制与自动提交选择
事务用于保证多条SQL要么全部成功、要么全部失败。PyMySQL中推荐try、commit、except、rollback结构:
  1. try:
  2.   conn.autocommit(False)
  3.   cursor.execute('UPDATE users SET age = age + 1 WHERE username = %s', ('user_1',))
  4.   cursor.execute('UPDATE users SET age = age - 1 WHERE username = %s', ('user_2',))
  5.   conn.commit()
  6.   print('事务提交成功')
  7. except Exception as e:
  8.   conn.rollback()
  9.   print('事务回滚:', e)
  10. finally:
  11.   conn.close()
复制代码
autocommit=True适合纯查询、非关键数据写入、日志和统计上报;涉及多表写入、订单支付库存等强一致业务、批量导入需整体回滚时必须关闭自动提交。单条INSERT在InnoDB中也会被包成独立事务,关闭自动提交批量写入可减少redo log同步次数。

五、SQL注入与连接池
动态拼接SQL危险,用户输入会被当成SQL语法执行。正确做法是参数化占位符:
  1. sql = 'SELECT * FROM users WHERE username = %s'
  2. cursor.execute(sql, (username,))
复制代码
PyMySQL会把username当纯数据处理,不参与SQL语法解析。并发高时每次新建连接容易报Too many connections,可用DBUtils的PooledDB:
  1. from dbutils.pooled_db import PooledDB
  2. import pymysql
  3. pool = PooledDB(
  4.   creator=pymysql,
  5.   maxconnections=6,
  6.   mincached=2,
  7.   maxcached=5,
  8.   blocking=True,
  9.   host='127.0.0.1',
  10.   user='demo_user',
  11.   password='your_password',
  12.   database='demo_db',
  13.   charset='utf8mb4'
  14. )
  15. conn = pool.connection()
  16. cursor = conn.cursor()
  17. cursor.execute('SELECT COUNT(*) FROM users')
  18. print(cursor.fetchone())
  19. conn.close()
复制代码
这里conn.close()是归还连接而非真正关闭;blocking=True表示池满时等待。原文压测中连接池比每次新建连接吞吐量约提升3倍。

六、常见报错与唯一键冲突
Can't connect通常查服务是否启动和host是否改127.0.0.1;Access denied查账号密码和GRANT权限;Unknown database查库名和database参数;Table doesn't exist查当前库和表名;Duplicate entry是唯一索引冲突;Incorrect string value多与字符集不支持emoji有关,表结构、连接参数和文件编码要统一utf8mb4;Lost connection和MySQL server has gone away要查max_allowed_packet、wait_timeout、空闲断开和重连保活。唯一键冲突可用一条SQL处理:
  1. sql = '''
  2. INSERT INTO users (username, email, age)
  3. VALUES (%s, %s, %s)
  4. ON DUPLICATE KEY UPDATE
  5. email = VALUES(email),
  6. agE = VALUES(age)
  7. '''
复制代码
排查时建议按顺序确认:MySQL服务是否正常、账号权限是否足够、database和表名是否正确、字符集是否统一、autocommit是否忘记提交、连接数/超时/包大小是否触顶。上述代码与数据均来自原文示例,可按实际环境替换账号、密码和表名。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-20 14:12 , Processed in 0.021676 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部