当前位置:首页 > 学海无涯 > 正文内容

Python 链接数据库与基础增删改查(CRUD)操作详解

清羽天2周前 (11-27)学海无涯14


在 Python 开发中,数据库交互是后端开发、数据分析、自动化脚本等场景的核心能力 —— 无论是存储用户数据、处理业务逻辑,还是批量分析数据,都需要 Python 与数据库建立连接并执行操作。本文以 MySQL 数据库(Python 生态最常用的关系型数据库)为例,从环境准备、数据库连接、核心 CRUD 实现到安全优化,一步步带大家掌握 Python 操作数据库的基础技能,适合新手快速入门。

一、环境准备

在编码前,需完成 “环境配置 + 数据库 + 依赖” 的准备工作,确保操作顺畅。

1.1 核心环境与工具

  • Python 版本:推荐 Python 3.8+(本文使用 Python 3.10,兼容性更强)。

  • MySQL 数据库:推荐 MySQL 8.0 或 MariaDB 10.5+(需提前启动服务)。

  • 开发工具:PyCharm、VS Code(配 Python 插件)或 Jupyter Notebook。

  • 数据库管理工具:Navicat、SQLyog 或 PHPMyAdmin,用于执行 SQL 语句和管理数据。

  • 核心依赖pymysql(Python 连接 MySQL 的第三方库,轻量且常用)。

1.2 依赖安装

打开终端 / 命令行,执行以下命令安装 pymysql
bash
运行
pip install pymysql
安装完成后,可在 Python 交互式环境中验证:
python
运行
import pymysql  # 无报错则安装成功

1.3 测试数据库与表创建

打开 MySQL 客户端(如 Navicat、命令行),执行以下 SQL 语句,创建测试用的数据库 python_db 和用户表 users
sql
-- 1. 创建数据库(若不存在),指定 UTF-8 编码CREATE DATABASE IF NOT EXISTS python_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;-- 2. 使用数据库USE python_db;-- 3. 创建用户表(存储用户ID、姓名、年龄、邮箱、注册时间)CREATE TABLE IF NOT EXISTS users (
    id INT PRIMARY KEY AUTO_INCREMENT, -- 主键自增
    name VARCHAR(50) NOT NULL,         -- 姓名,非空
    age TINYINT UNSIGNED,              -- 年龄(0-255,无符号)
    email VARCHAR(100) UNIQUE NOT NULL,-- 邮箱,唯一且非空
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP -- 注册时间,默认当前时间) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

二、Python 链接数据库的核心:pymysql 用法

pymysql 是 Python 操作 MySQL 的主流库,兼容 MySQL 5.5+ 版本,支持 SQL 执行、事务、预处理语句等核心功能。链接数据库的核心步骤的是:建立连接 → 创建游标 → 执行 SQL → 处理结果 → 关闭资源

2.1 核心连接参数

连接 MySQL 需指定以下 5 个核心参数:
  • host:数据库地址(本地为 localhost,远程为服务器 IP)。

  • port:MySQL 端口号(默认 3306)。

  • user:MySQL 用户名(如 root)。

  • password:MySQL 密码(你的登录密码)。

  • database:要连接的数据库名(本文为 python_db)。

  • charset:字符编码(必须为 utf8mb4,支持中文和 emoji)。

2.2 封装数据库工具类(推荐)

直接在业务代码中写连接逻辑会导致冗余,且难以维护。因此,我们先封装一个 数据库工具类,统一处理连接创建、SQL 执行、资源关闭:
python
运行
import pymysqlfrom pymysql.err import OperationalError, ProgrammingError, IntegrityErrorclass MySQLDB:
    """MySQL 数据库工具类:封装连接、CRUD 操作、资源关闭"""
    def __init__(self, host='localhost', port=3306, user='root', password='123456', database='python_db', charset='utf8mb4'):
        # 初始化连接参数
        self.host = host
        self.port = port
        self.user = user
        self.password = password
        self.database = database
        self.charset = charset
        self.conn = None  # 数据库连接对象
        self.cursor = None  # 游标对象(执行 SQL 用)

    def connect(self):
        """建立数据库连接"""
        try:
            self.conn = pymysql.connect(
                host=self.host,
                port=self.port,
                user=self.user,
                password=self.password,
                database=self.database,
                charset=self.charset,
                cursorclass=pymysql.cursors.DictCursor  # 游标返回字典格式(键为字段名)
            )
            self.cursor = self.conn.cursor()
            print("数据库连接成功!")
        except OperationalError as e:
            print(f"数据库连接失败:{e}")
            raise  # 抛出异常,由调用者处理

    def close(self):
        """关闭游标和连接(避免资源泄漏)"""
        try:
            if self.cursor:
                self.cursor.close()
            if self.conn and self.conn.open:
                self.conn.close()
                print("数据库连接已关闭!")
        except Exception as e:
            print(f"关闭资源失败:{e}")

    def execute_sql(self, sql, params=None):
        """
        执行 SQL 语句(insert/update/delete)
        :param sql: SQL 语句(使用 %s 作为占位符)
        :param params: SQL 参数(元组/列表,如 (name, age))
        :return: 受影响的行数
        """
        try:
            self.connect()  # 建立连接
            affected_rows = self.cursor.execute(sql, params)  # 执行 SQL(params 避免 SQL 注入)
            self.conn.commit()  # 提交事务(增删改必须提交)
            print(f"SQL 执行成功,受影响行数:{affected_rows}")
            return affected_rows        except (ProgrammingError, IntegrityError) as e:
            self.conn.rollback()  # 执行失败,回滚事务
            print(f"SQL 执行失败:{e}")
            raise
        finally:
            self.close()  # 无论成功失败,都关闭资源

    def query_sql(self, sql, params=None, fetch_all=True):
        """
        执行查询 SQL(select)
        :param sql: 查询 SQL 语句
        :param params: SQL 参数
        :param fetch_all: 是否查询所有结果(True 返回全部,False 返回单条)
        :return: 查询结果(字典列表/单个字典)
        """
        try:
            self.connect()
            self.cursor.execute(sql, params)
            if fetch_all:
                result = self.cursor.fetchall()  # 获取所有结果
            else:
                result = self.cursor.fetchone()  # 获取单条结果
            print(f"查询成功,共 {len(result) if fetch_all else 1} 条数据" if result else "查询无结果")
            return result        except ProgrammingError as e:
            print(f"查询失败:{e}")
            raise
        finally:
            self.close()
关键说明:
  1. cursorclass=pymysql.cursors.DictCursor:设置游标返回格式为字典(如 {'id': 1, 'name': '张三'}),比默认元组更直观。

  2. 事务处理:execute_sql 中执行 commit()(提交事务)和 rollback()(回滚事务),避免数据不一致。

  3. 占位符:使用 %s 作为 SQL 占位符(注意:不是 Python 的格式化字符串 %),通过 params 传递参数,防止 SQL 注入。

  4. 资源关闭:finally 块中强制关闭游标和连接,避免资源泄漏。

三、基础增删改查(CRUD)操作实现

基于上面封装的 MySQLDB 工具类,我们可以简洁地实现 users 表的增删改查操作,无需重复编写连接和关闭逻辑。

3.1 新增操作(Create)

功能:向 users 表插入一条新用户数据。
python
运行
if __name__ == "__main__":
    # 初始化数据库工具类(替换为你的 MySQL 账号密码)
    db = MySQLDB(user='root', password='123456')

    # 1. 新增用户
    try:
        sql = "INSERT INTO users (name, age, email) VALUES (%s, %s, %s)"
        params = ("张三", 24, "zhangsan@example.com")  # 对应 SQL 中的 %s 占位符
        affected_rows = db.execute_sql(sql, params)
        print(f"新增用户成功,受影响行数:{affected_rows}")
    except Exception as e:
        print(f"新增失败:{e}")
注意:params 必须是元组或列表,即使只有一个参数,也需写成 (param,)(末尾加逗号),避免语法错误。

3.2 查询操作(Read)

查询分为 “查询单条数据” 和 “查询多条数据”,通过 fetch_all 参数控制。

3.2.1 查询单条数据(按 ID 查询)

python
运行
if __name__ == "__main__":
    db = MySQLDB(user='root', password='123456')

    # 2. 按 ID 查询单条用户
    try:
        sql = "SELECT id, name, age, email, create_time FROM users WHERE id = %s"
        params = (1,)  # 查询 ID=1 的用户
        user = db.query_sql(sql, params, fetch_all=False)  # fetch_all=False 返回单条
        if user:
            print("查询到的用户信息:")
            print(f"ID:{user['id']}")
            print(f"姓名:{user['name']}")
            print(f"年龄:{user['age']}")
            print(f"邮箱:{user['email']}")
            print(f"注册时间:{user['create_time']}")
        else:
            print("未查询到该用户")
    except Exception as e:
        print(f"查询失败:{e}")

3.2.2 查询多条数据(查询所有用户)

python
运行
if __name__ == "__main__":
    db = MySQLDB(user='root', password='123456')

    # 3. 查询所有用户(按注册时间倒序)
    try:
        sql = "SELECT id, name, age, email FROM users ORDER BY create_time DESC"
        users = db.query_sql(sql, fetch_all=True)  # fetch_all=True 返回全部
        if users:
            print("所有用户列表:")
            for idx, user in enumerate(users, 1):
                print(f"序号 {idx}:ID={user['id']},姓名={user['name']},邮箱={user['email']}")
        else:
            print("暂无用户数据")
    except Exception as e:
        print(f"查询失败:{e}")

3.3 修改操作(Update)

功能:根据用户 ID 修改姓名、年龄或邮箱。
python
运行
if __name__ == "__main__":
    db = MySQLDB(user='root', password='123456')

    # 4. 修改用户信息(修改 ID=1 的用户)
    try:
        sql = "UPDATE users SET name = %s, age = %s WHERE id = %s"
        params = ("张三_更新", 25, 1)  # 新姓名、新年龄、目标 ID
        affected_rows = db.execute_sql(sql, params)
        if affected_rows > 0:
            print("用户信息修改成功!")
        else:
            print("修改失败:未找到用户或数据未变更")
    except Exception as e:
        print(f"修改失败:{e}")

3.4 删除操作(Delete)

功能:根据用户 ID 删除指定用户数据。
python
运行
if __name__ == "__main__":
    db = MySQLDB(user='root', password='123456')

    # 5. 删除用户(删除 ID=1 的用户)
    try:
        sql = "DELETE FROM users WHERE id = %s"
        params = (1,)
        affected_rows = db.execute_sql(sql, params)
        if affected_rows > 0:
            print(f"删除成功!已删除 ID=1 的用户")
        else:
            print("删除失败:未找到该用户")
    except Exception as e:
        print(f"删除失败:{e}")

四、关键注意事项与安全优化

4.1 防 SQL 注入(重中之重)

SQL 注入是最常见的安全漏洞之一,攻击者通过构造恶意参数篡改 SQL 语句,窃取或删除数据。Python 中预防 SQL 注入的核心方案:
  1. 使用参数绑定:严禁直接拼接 SQL 字符串(如下错误示范),必须通过 params 参数传递值,pymysql 会自动转义特殊字符:

    python
    运行
    # 错误示范:直接拼接参数,存在 SQL 注入风险name = "张三' OR 1=1 --"sql = f"SELECT * FROM users WHERE name = '{name}'"  # 恶意参数会导致查询所有用户
  2. 过滤用户输入:对用户提交的参数(如表单、接口请求)进行合法性校验(如年龄必须是数字、邮箱格式正确),减少恶意参数传入。

4.2 常见问题排查

  1. 连接失败
    • 检查 MySQL 服务是否启动(可通过 net start mysql 启动)。

    • 确认 hostportuserpassword 正确(尤其是 MySQL 8.0+ 需注意密码认证方式)。

    • 若报 “Access denied”,可能是用户无权限访问 python_db 数据库,需授权:

      sql
      GRANT ALL PRIVILEGES ON python_db.* TO 'root'@'localhost' IDENTIFIED BY '123456';FLUSH PRIVILEGES;
  2. 中文乱码
    • 数据库、表、字段的编码必须为 utf8mb4(已在创建时指定)。

    • MySQLDB 初始化时已设置 charset='utf8mb4',确保连接编码一致。

    • 若查询结果中文乱码,检查 Python 脚本文件编码是否为 UTF-8(PyCharm 默认是)。

  3. 事务提交问题
    • 增删改操作必须执行 conn.commit(),否则数据不会写入数据库(工具类已封装)。

    • 执行失败时需 rollback() 回滚事务,避免数据不一致(工具类已处理)。

4.3 生产环境优化建议

  1. 配置文件分离:将数据库连接参数(hostuserpassword)放在独立配置文件(如 config.py)中,避免硬编码:
    python
    运行
    # config.pyDB_CONFIG = {
        'host': 'localhost',
        'port': 3306,
        'user': 'prod_user',  # 生产环境使用低权限用户
        'password': 'StrongPassword123!',  # 复杂密码
        'database': 'python_db',
        'charset': 'utf8mb4'}
    使用时导入:
    python
    运行
    from config import DB_CONFIG
    db = MySQLDB(**DB_CONFIG)  # 解包字典参数
  2. 错误处理优化:生产环境中避免直接打印异常信息(泄露敏感数据),应使用日志模块(如 logging)记录错误,向用户返回友好提示:
    python
    运行
    import logging# 配置日志logging.basicConfig(
        filename='db_error.log',
        level=logging.ERROR,
        format='%(asctime)s - %(levelname)s - %(message)s')try:
        db.execute_sql(sql, params)except Exception as e:
        logging.error(f"SQL 执行失败:{e}")  # 记录日志
        print("系统繁忙,请稍后再试~")  # 友好提示
  3. 使用数据库连接池:高并发场景下,频繁创建 / 关闭数据库连接会消耗大量资源。推荐使用 DBUtils 库的连接池,复用连接提升性能:
    bash
    运行
    pip install DBUtils  # 安装连接池库
    改造工具类(核心代码):
    python
    运行
    from DBUtils.PooledDB import PooledDBclass MySQLPoolDB:
        def __init__(self, **config):
            self.pool = PooledDB(
                creator=pymysql,  # 数据库驱动
                maxconnections=10,  # 最大连接数
                **config        )
    
        def connect(self):
            self.conn = self.pool.connection()  # 从连接池获取连接
            self.cursor = self.conn.cursor(pymysql.cursors.DictCursor)
  4. 限制数据库用户权限:生产环境中,Python 连接数据库的用户仅授予 SELECTINSERTUPDATEDELETE 必要权限,禁止 DROPALTER 等高危权限,降低风险。

五、扩展:其他数据库支持

Python 除了操作 MySQL,还支持其他主流数据库,核心库如下:
  • PostgreSQL:使用 psycopg2 库(用法与 pymysql 类似)。

  • SQLite:Python 内置 sqlite3 库(无需额外安装,适合轻量级应用)。

  • Oracle:使用 cx_Oracle 库。

  • MongoDB(非关系型):使用 pymongo 库。

核心思路一致:建立连接 → 执行 SQL / 命令 → 处理结果 → 关闭资源,只是库的 API 略有差异。

六、总结

本文详细讲解了 Python 基于 pymysql 操作 MySQL 数据库的核心流程,重点掌握:
  1. 环境准备:安装 pymysql 依赖,创建测试数据库和表。

  2. 工具类封装:统一管理连接、SQL 执行、资源关闭,减少冗余代码。

  3. CRUD 实现:使用参数绑定(%s 占位符)实现增删改查,避免 SQL 注入。

  4. 安全与优化:分离配置文件、记录日志、使用连接池,适配生产环境。

Python 操作数据库是后端开发、数据分析的基础技能,掌握本文内容后,可应对小型项目(如个人博客、数据爬虫存储、管理系统)的需求。建议大家多动手实践,尝试结合 Flask/Django 框架、接口开发等场景扩展功能,加深理解。后续可学习 ORM 框架(如 SQLAlchemy、Django ORM),进一步简化数据库操作代码。


分享给朋友:

“Python 链接数据库与基础增删改查(CRUD)操作详解” 的相关文章

Java 实现在线视频播放完整方案:从后端服务到前端播放

在 Web 开发中,在线视频播放是常见需求(如教育平台、视频网站、企业培训系统等)。Java 作为成熟的后端技术,能提供稳定的视频资源管理、权限控制、流式传输能力;配合前端播放器组件,可实现流畅的跨浏览器视频播放体验。本文将从技术选型、后端实现、前端集成、功能优化四个维度,手把手教你完成 Java...

Java 链接数据库与基础增删改查操作详解

在 Java 开发中,数据库交互是绝大多数应用的核心功能之一。无论是用户信息存储、业务数据统计还是日志记录,都需要通过 Java 程序与数据库建立连接并执行数据操作。本文将以 MySQL 数据库(最常用的关系型数据库之一)为例,从环境准备、数据库连接、基础增删改查(CRUD)操作到代码优化...

Unity 场景转换功能实现全指南:从基础到进阶

场景转换是几乎所有 Unity 项目都必备的核心功能,无论是简单的场景切换还是带有加载动画的复杂过渡,都直接影响着玩家的体验。本文将从基础原理出发,逐步讲解如何在 Unity 中实现各种场景转换效果,帮助开发者打造流畅自然的场景过渡体验。一、场景转换的基本原理在 Unity 中,场景转换本质上是卸载...

Unity 开发实战:实现银行存取款功能系统

在许多游戏中,银行系统都是重要的经济组成部分,它能帮助玩家管理虚拟资产、实现安全存储。本文将详细介绍如何在 Unity 中设计并实现一个完整的银行存取款功能,包括数据结构设计、UI 交互逻辑和安全验证机制。一、银行系统核心需求分析一个基础的银行系统应包含以下核心功能:账户余额查询存款功能(将背包货币...

Unity 开发实战:实现逼真的作物生长系统

作物生长系统是农场类、生存类游戏的核心玩法之一,一个设计精良的作物生长系统能极大提升游戏的沉浸感。本文将详细介绍如何在 Unity 中构建一个完整的作物生长系统,包括生长周期、环境影响、交互逻辑和可视化表现。一、作物生长系统核心需求分析一个真实的作物生长系统应包含以下核心要素:多阶段生长周期(种子→...

Unity 开发规划:体力值与资源系统设计方案

在许多游戏中,体力值(Stamina/HP)系统是控制玩家节奏、平衡游戏进度的核心机制,尤其在手游和休闲游戏中应用广泛。本文将详细介绍如何规划和设计一个灵活可扩展的体力值系统,以及相关联的资源恢复、消耗和奖励机制,帮助你在 Unity 项目中构建既平衡又有趣的资源管理体系。一、体力值系统核心需求分析...