异步协程中基于 aiomysql 的异步数据库操作

本文介绍如何在 异步协程 中使用异步数据库操作,结合 aiomysql 库和异步连接池实现轻量级的异步数据库封装,适用于 asyncio 协程和异步编程。

1. 项目背景

在 Python 的异步编程中,aiomysql 是一款非常流行的异步 MySQL 数据库驱动。通过结合 asyncio 库和 aiomysql,可以高效地处理并发数据库操作。

在大规模并发系统中,数据库操作的效率往往成为瓶颈。为了优化数据库性能和实现高并发,本教程将通过以下内容进行演示:

  • 使用异步连接池
  • 通过异步协程执行数据库操作
  • 处理基本的数据库增删改查(CRUD)操作

2. 安装依赖

首先,确保你已安装必要的库。使用以下命令来安装:

bash 复制代码
pip install aiomysql

3. 基于 aiomysql 封装异步连接池

我们将封装一个异步数据库连接池,供后续操作调用。以下是 AsyncConnection 类的实现,它使用 aiomysql 创建连接池,并提供查询、插入、更新等操作。

代码实现

python 复制代码
import asyncio
import aiomysql
import logging
import traceback

class AsyncConnection:
    """基于 aiomysql 的异步数据库连接封装"""

    def __init__(self, host, database, user=None, password=None,
                 port=3306, connect_timeout=10, charset="utf8mb4",
                 sql_mode="TRADITIONAL"):
        self.host = host
        self.database = database
        self.user = user
        self.password = password
        self.port = port
        self.connect_timeout = connect_timeout
        self.charset = charset
        self.sql_mode = sql_mode
        self._pool = None

    async def connect(self):
        """建立连接池"""
        self._pool = await aiomysql.create_pool(
            host=self.host,
            port=self.port,
            user=self.user,
            password=self.password,
            db=self.database,
            charset=self.charset,
            autocommit=True,
            connect_timeout=self.connect_timeout,
            sql_mode=self.sql_mode,
            cursorclass=aiomysql.DictCursor
        )

    async def close(self):
        """关闭连接池"""
        if self._pool:
            self._pool.close()
            await self._pool.wait_closed()

    async def query(self, sql, *args):
        """执行查询,返回多行"""
        async with self._pool.acquire() as conn:
            async with conn.cursor() as cur:
                await cur.execute(sql, args)
                result = await cur.fetchall()
                return result

    async def get(self, sql, *args):
        """执行查询,返回一行"""
        async with self._pool.acquire() as conn:
            async with conn.cursor() as cur:
                await cur.execute(sql, args)
                return await cur.fetchone()

    async def execute(self, sql, *args):
        """执行写操作,返回最后插入的ID"""
        async with self._pool.acquire() as conn:
            async with conn.cursor() as cur:
                try:
                    await cur.execute(sql, args)
                    return cur.lastrowid
                except aiomysql.MySQLError as e:
                    if e.args[0] == 1062:
                        logging.warning("忽略重复插入: %s", e)
                    else:
                        logging.error("SQL执行失败: %s\nSQL: %s", e, sql)
                        raise

    ## =============== 高级封装 ===================

    async def table_has(self, table_name, field, value):
        """检查表中是否有对应字段值"""
        sql = f"SELECT {field} FROM {table_name} WHERE {field}=%s LIMIT 1"
        return await self.get(sql, value)

    async def table_insert(self, table_name, item: dict):
        """插入一条数据"""
        fields = list(item.keys())
        values = list(item.values())
        placeholders = ','.join(['%s'] * len(fields))
        field_list = ','.join(fields)
        sql = f"INSERT INTO {table_name} ({field_list}) VALUES ({placeholders})"
        try:
            return await self.execute(sql, *values)
        except aiomysql.MySQLError as e:
            if e.args[0] == 1062:
                logging.warning("重复插入被跳过")
            else:
                logging.error("插入数据出错: %s\n数据: %s", e, item)
                raise

    async def table_update(self, table_name, updates: dict, field_where: str, value_where):
        """更新一条记录"""
        set_clause = ', '.join([f"{k}=%s" for k in updates.keys()])
        values = list(updates.values())
        values.append(value_where)
        sql = f"UPDATE {table_name} SET {set_clause} WHERE {field_where}=%s"
        await self.execute(sql, *values)

4. 多协程并发执行数据库操作

接下来,我们使用 asyncio.gather 来并发执行多个异步数据库操作,每个协程模拟一个用户执行数据库增删改查(CRUD)操作。

完整的多协程测试代码

python 复制代码
import asyncio
import logging
from ezpymysql_async import AsyncConnection

# 日志配置
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")

# 初始化连接
db = AsyncConnection(
    host='localhost',
    database='test_db',
    user='root',
    password='5201314',
    port=3306
)


async def run_user_task(user_id):
    name = f"User-{user_id}"
    age = 20 + user_id

    try:
        # 1. 插入数据
        await db.table_insert("test_table", {"name": name, "age": age})
        logging.info(f"[{name}] 插入成功")

        # 2. 查询所有
        all_data = await db.query("SELECT * FROM test_table")
        logging.info(f"[{name}] 当前所有数据行数: {len(all_data)}")

        # 3. 查询单条
        one = await db.get("SELECT * FROM test_table WHERE name=%s", name)
        logging.info(f"[{name}] 查询单条: {one}")

        # 4. 更新
        await db.table_update("test_table", {"age": age + 10}, "name", name)
        logging.info(f"[{name}] 年龄更新完成")

        # 5. 再查询
        updated = await db.get("SELECT * FROM test_table WHERE name=%s", name)
        logging.info(f"[{name}] 更新后: {updated}")

        # 6. 检查存在
        exists = await db.table_has("test_table", "name", name)
        logging.info(f"[{name}] 是否存在: {exists is not None}")

        # 7. 删除
        await db.execute("DELETE FROM test_table WHERE name=%s", name)
        logging.info(f"[{name}] 删除成功")

        # 8. 再插入并测试 lastrowid
        last_id = await db.insert("INSERT INTO test_table (name, age) VALUES (%s, %s)", f"{name}_new", age)
        logging.info(f"[{name}] 插入新数据成功, ID: {last_id}")

    except Exception as e:
        logging.error(f"[{name}] 出现异常: {e}")


async def main():
    # 建立连接池
    await db.connect()

    # 创建多个协程任务
    tasks = [run_user_task(i) for i in range(100)]

    # 并发执行任务
    await asyncio.gather(*tasks)

    # 关闭连接池
    await db.close()

    logging.info("所有协程任务已完成")


if __name__ == "__main__":
    asyncio.run(main())

5. 结果与总结

通过上述的异步数据库封装和多协程测试,我们能够:

  • 高效地进行数据库操作,避免了阻塞。
  • 实现了多线程并发数据库操作,提升了数据库的访问性能。
  • 灵活地使用 Python 的 asyncio 协程处理大量并发请求。

输出示例


通过本文,你可以将这种异步数据库操作封装到任何异步应用中,无论是 Web 开发还是爬虫等领域,都能有效提高数据库操作的性能。


如果你对本篇教程有任何疑问或建议,欢迎留言讨论!

相关推荐
kali-Myon21 分钟前
某校园门禁系统高危 SQL 注入漏洞挖掘复盘
数据库·sql·安全·web安全
程序员在囧途2 小时前
likeadmin-api API 中转站怎么做统一报价?从 /pricing、/user/balance 到 task_id 回执的落地方法
运维·服务器·数据库·likeadmin-api·api中转站·token计费
Alan_6912 小时前
聊聊 Swagger/Postman/Apifox 的真实选型与 Python 项目实战
python·测试工具·postman
龙石数据2 小时前
MySQL 全量同步到 Hive 怎么做?三步配置教程
数据库·hive·mysql·数据治理·数据中台
智慧物业老杨3 小时前
物业绿化数智化权责管控方案:基于工单平台的双层权限追溯模块落地实践
python
程序员在囧途3 小时前
likeadmin-api API 算力超市怎么做供应商切换?统一鉴权、task_id 和 callback_url 才能稳交付
java·服务器·数据库·开放api·likeadmin-api·api算力超市
数据工匠老o3 小时前
连接池配置实战——从“连不上“到“连太多“的完整排查指南
数据库
Metaphor6923 小时前
使用 Python 在 Word 文档中添加批注
python·word
FreakStudio4 小时前
WIZnet 开源方案分享:W55MH32 开发板 + 小智 AI实现以太网版本的小智机器人
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
会周易的程序员4 小时前
使用 pybind11 封装 C++ 日志库 microLog 为 Python 模块
java·c++·python·物联网·架构·日志·aiot