PyMySQL完整教程

PyMySQL完整教程

第1章: 环境配置与基础连接

1.1 安装与导入

python 复制代码
# 安装命令: pip install pymysql==1.1.2
import pymysql
import sys
import logging

# 配置日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

1.2 创建测试数据库

sql 复制代码
-- 创建测试数据库
CREATE DATABASE IF NOT EXISTS pymysql_tutorial
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

-- 使用数据库
USE pymysql_tutorial;

-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
    username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
    email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
    password_hash VARCHAR(255) NOT NULL COMMENT '密码哈希',
    age TINYINT UNSIGNED COMMENT '年龄',
    salary DECIMAL(10, 2) DEFAULT 0.00 COMMENT '薪水',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    is_active BOOLEAN DEFAULT TRUE COMMENT '是否激活',
    INDEX idx_username (username),
    INDEX idx_email (email),
    INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';

-- 创建订单表
CREATE TABLE IF NOT EXISTS orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',
    user_id INT NOT NULL COMMENT '用户ID',
    order_no VARCHAR(50) NOT NULL UNIQUE COMMENT '订单编号',
    total_amount DECIMAL(10, 2) NOT NULL COMMENT '总金额',
    status ENUM('pending', 'paid', 'shipped', 'completed', 'cancelled') 
        DEFAULT 'pending' COMMENT '订单状态',
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间',
    notes TEXT COMMENT '备注',
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    INDEX idx_user_id (user_id),
    INDEX idx_order_date (order_date),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单表';

-- 创建产品表
CREATE TABLE IF NOT EXISTS products (
    product_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '产品ID',
    product_name VARCHAR(100) NOT NULL COMMENT '产品名称',
    category VARCHAR(50) NOT NULL COMMENT '产品分类',
    price DECIMAL(10, 2) NOT NULL COMMENT '单价',
    stock INT NOT NULL DEFAULT 0 COMMENT '库存',
    description TEXT COMMENT '产品描述',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    INDEX idx_category (category),
    INDEX idx_price (price)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品表';

-- 插入测试数据
INSERT INTO users (username, email, password_hash, age, salary, is_active) VALUES
('张三', 'zhangsan@example.com', 'hash1', 25, 5000.00, TRUE),
('李四', 'lisi@example.com', 'hash2', 30, 8000.00, TRUE),
('王五', 'wangwu@example.com', 'hash3', 28, 6500.00, FALSE),
('赵六', 'zhaoliu@example.com', 'hash4', 35, 12000.00, TRUE);

INSERT INTO products (product_name, category, price, stock, description) VALUES
('MacBook Pro', '电脑', 12999.00, 50, '苹果笔记本电脑'),
('iPhone 15', '手机', 6999.00, 100, '苹果智能手机'),
('iPad Air', '平板', 4999.00, 30, '苹果平板电脑'),
('小米电视', '家电', 2999.00, 80, '智能电视');

INSERT INTO orders (user_id, order_no, total_amount, status) VALUES
(1, 'ORD20260001', 12999.00, 'completed'),
(1, 'ORD20260002', 6999.00, 'shipped'),
(2, 'ORD20260003', 4999.00, 'paid'),
(3, 'ORD20260004', 2999.00, 'pending');

第2章: 基础数据库连接

python 复制代码
"""
开发思路:
1. 通过连接参数建立与MySQL数据库的连接
2. 使用try-except-finally确保连接正确关闭
3. 通过游标执行SQL语句
4. 处理查询结果
"""

import pymysql
from pymysql.constants import CLIENT

def basic_connection_example():
    """基础连接示例"""
    
    # 数据库连接参数
    db_config = {
        'host': 'localhost',           # 数据库主机地址
        'port': 3306,                  # 数据库端口,MySQL默认3306
        'user': 'root',                # 数据库用户名
        'password': 'your_password',   # 数据库密码
        'database': 'pymysql_tutorial',# 数据库名
        'charset': 'utf8mb4',          # 字符编码
        'cursorclass': pymysql.cursors.DictCursor,  # 返回字典格式的游标
        'client_flag': CLIENT.MULTI_STATEMENTS,     # 允许多语句执行
    }
    
    # 初始化连接和游标变量
    connection = None
    cursor = None
    
    try:
        # 1. 建立数据库连接
        # 使用pymysql.connect()方法创建连接对象
        connection = pymysql.connect(**db_config)
        
        # 2. 创建游标对象
        # 游标用于执行SQL语句和获取结果
        cursor = connection.cursor()
        
        # 3. 执行查询语句
        # 查询数据库版本
        cursor.execute("SELECT VERSION() AS version")
        
        # 4. 获取查询结果
        # fetchone()获取一条记录
        result = cursor.fetchone()
        
        # 5. 输出结果
        print(f"MySQL数据库版本: {result['version']}")
        
        # 6. 查询当前数据库
        cursor.execute("SELECT DATABASE() AS current_db")
        db_info = cursor.fetchone()
        print(f"当前数据库: {db_info['current_db']}")
        
        # 7. 查询表信息
        cursor.execute("SHOW TABLES")
        tables = cursor.fetchall()  # fetchall()获取所有记录
        print("\n数据库中的表:")
        for table in tables:
            print(f"  - {list(table.values())[0]}")
            
    except pymysql.MySQLError as e:
        # 捕获MySQL错误
        print(f"数据库错误: {e}")
        
    finally:
        # 8. 关闭资源
        # 确保游标和连接被正确关闭
        if cursor:
            cursor.close()
        if connection:
            connection.close()
        print("\n数据库连接已关闭")

# 运行示例
if __name__ == "__main__":
    basic_connection_example()

第3章: 数据查询操作

python 复制代码
"""
开发思路:
1. 使用不同的查询方法获取数据
2. 处理查询参数防止SQL注入
3. 使用不同的fetch方法获取结果
4. 实现分页查询
"""

def query_operations_example():
    """数据查询操作示例"""
    
    connection = None
    cursor = None
    
    try:
        # 建立连接
        connection = pymysql.connect(
            host='localhost',
            user='root',
            password='your_password',
            database='pymysql_tutorial',
            charset='utf8mb4',
            cursorclass=pymysql.cursors.DictCursor
        )
        
        cursor = connection.cursor()
        
        # 1. 查询所有用户
        print("=== 所有用户 ===")
        cursor.execute("SELECT * FROM users")
        users = cursor.fetchall()  # 获取所有记录
        
        for user in users:
            print(f"ID: {user['id']}, 用户名: {user['username']}, "
                  f"年龄: {user['age']}, 薪水: {user['salary']}")
        
        # 2. 查询单个用户
        print("\n=== 查询单个用户 ===")
        # 使用参数化查询防止SQL注入
        user_id = 1
        cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
        single_user = cursor.fetchone()  # 获取一条记录
        
        if single_user:
            print(f"用户信息: {single_user['username']}, 邮箱: {single_user['email']}")
        
        # 3. 条件查询
        print("\n=== 条件查询 ===")
        min_salary = 6000
        cursor.execute(
            "SELECT username, salary FROM users WHERE salary >= %s AND is_active = %s",
            (min_salary, True)
        )
        
        active_users = cursor.fetchall()
        print(f"薪水>=6000的活跃用户:")
        for user in active_users:
            print(f"  {user['username']}: ¥{user['salary']}")
        
        # 4. 聚合查询
        print("\n=== 聚合查询 ===")
        cursor.execute("""
            SELECT 
                COUNT(*) as total_users,
                AVG(salary) as avg_salary,
                MAX(salary) as max_salary,
                MIN(salary) as min_salary
            FROM users
            WHERE is_active = TRUE
        """)
        stats = cursor.fetchone()
        print(f"活跃用户数: {stats['total_users']}")
        print(f"平均薪水: {stats['avg_salary']:.2f}")
        print(f"最高薪水: {stats['max_salary']}")
        print(f"最低薪水: {stats['min_salary']}")
        
        # 5. 分组查询
        print("\n=== 分组查询 ===")
        cursor.execute("""
            SELECT 
                CASE 
                    WHEN age < 25 THEN '25岁以下'
                    WHEN age BETWEEN 25 AND 30 THEN '25-30岁'
                    WHEN age > 30 THEN '30岁以上'
                    ELSE '未知'
                END as age_group,
                COUNT(*) as user_count,
                AVG(salary) as avg_salary
            FROM users
            WHERE age IS NOT NULL
            GROUP BY age_group
            ORDER BY user_count DESC
        """)
        
        age_groups = cursor.fetchall()
        for group in age_groups:
            print(f"{group['age_group']}: {group['user_count']}人, "
                  f"平均薪水: ¥{group['avg_salary']:.2f}")
        
        # 6. 分页查询
        print("\n=== 分页查询 ===")
        page = 1
        page_size = 2
        
        # 查询总数
        cursor.execute("SELECT COUNT(*) as total FROM users")
        total = cursor.fetchone()['total']
        print(f"总记录数: {total}")
        
        # 计算总页数
        total_pages = (total + page_size - 1) // page_size
        print(f"总页数: {total_pages}")
        
        # 分页查询
        offset = (page - 1) * page_size
        cursor.execute(
            "SELECT * FROM users ORDER BY id LIMIT %s OFFSET %s",
            (page_size, offset)
        )
        
        page_users = cursor.fetchall()
        print(f"\n第{page}页数据:")
        for user in page_users:
            print(f"  ID: {user['id']}, 用户名: {user['username']}")
        
        # 7. 连接查询
        print("\n=== 连接查询 ===")
        cursor.execute("""
            SELECT 
                u.username,
                u.email,
                o.order_no,
                o.total_amount,
                o.status,
                o.order_date
            FROM users u
            JOIN orders o ON u.id = o.user_id
            ORDER BY o.order_date DESC
        """)
        
        orders_with_users = cursor.fetchall()
        for order in orders_with_users:
            print(f"用户: {order['username']}, 订单: {order['order_no']}, "
                  f"金额: ¥{order['total_amount']}, 状态: {order['status']}")
        
        # 8. 子查询
        print("\n=== 子查询 ===")
        cursor.execute("""
            SELECT 
                username,
                salary
            FROM users
            WHERE salary > (
                SELECT AVG(salary) 
                FROM users 
                WHERE is_active = TRUE
            )
            ORDER BY salary DESC
        """)
        
        above_avg_users = cursor.fetchall()
        print("薪水高于平均值的用户:")
        for user in above_avg_users:
            print(f"  {user['username']}: ¥{user['salary']}")
            
    except pymysql.MySQLError as e:
        print(f"查询错误: {e}")
        
    finally:
        if cursor:
            cursor.close()
        if connection:
            connection.close()

# 运行示例
if __name__ == "__main__":
    query_operations_example()

第4章: 数据插入操作

python 复制代码
"""
开发思路:
1. 使用单条插入和多条插入
2. 处理自增主键的获取
3. 使用事务确保数据一致性
4. 处理插入异常
"""

def insert_operations_example():
    """数据插入操作示例"""
    
    connection = None
    cursor = None
    
    try:
        # 建立连接
        connection = pymysql.connect(
            host='localhost',
            user='root',
            password='your_password',
            database='pymysql_tutorial',
            charset='utf8mb4',
            cursorclass=pymysql.cursors.DictCursor
        )
        
        cursor = connection.cursor()
        
        print("=== 数据插入操作 ===")
        
        # 1. 单条数据插入
        print("\n1. 单条数据插入")
        insert_single_sql = """
        INSERT INTO users (username, email, password_hash, age, salary, is_active)
        VALUES (%s, %s, %s, %s, %s, %s)
        """
        
        # 插入数据
        new_user = ('孙七', 'sunqi@example.com', 'hash5', 27, 7500.00, True)
        cursor.execute(insert_single_sql, new_user)
        
        # 获取自增主键
        new_user_id = cursor.lastrowid
        print(f"插入成功!新用户ID: {new_user_id}")
        
        # 提交事务
        connection.commit()
        print("事务已提交")
        
        # 2. 插入并验证
        print("\n2. 验证插入的数据")
        cursor.execute("SELECT * FROM users WHERE id = %s", (new_user_id,))
        inserted_user = cursor.fetchone()
        print(f"新用户: {inserted_user['username']}, 邮箱: {inserted_user['email']}")
        
        # 3. 批量插入多条数据
        print("\n3. 批量插入多条数据")
        insert_multi_sql = """
        INSERT INTO products (product_name, category, price, stock, description)
        VALUES (%s, %s, %s, %s, %s)
        """
        
        # 批量数据
        products_data = [
            ('华为MateBook', '电脑', 8999.00, 40, '华为笔记本电脑'),
            ('三星Galaxy', '手机', 5999.00, 60, '三星智能手机'),
            ('戴尔显示器', '显示器', 1999.00, 30, '电竞显示器')
        ]
        
        # 执行批量插入
        cursor.executemany(insert_multi_sql, products_data)
        
        # 获取影响的行数
        affected_rows = cursor.rowcount
        print(f"批量插入成功!影响行数: {affected_rows}")
        
        connection.commit()
        
        # 4. 验证批量插入
        cursor.execute("""
            SELECT product_name, category, price, stock 
            FROM products 
            WHERE category IN ('电脑', '手机', '显示器')
            ORDER BY created_at DESC 
            LIMIT 3
        """)
        
        new_products = cursor.fetchall()
        print("新插入的产品:")
        for product in new_products:
            print(f"  {product['product_name']}: ¥{product['price']}, "
                  f"库存: {product['stock']}")
        
        # 5. 插入时忽略重复
        print("\n5. 插入时忽略重复(使用IGNORE)")
        insert_ignore_sql = """
        INSERT IGNORE INTO users (username, email, password_hash, age, salary)
        VALUES (%s, %s, %s, %s, %s)
        """
        
        # 尝试插入重复数据
        duplicate_user = ('张三', 'zhangsan_new@example.com', 'hash6', 26, 5500.00)
        try:
            cursor.execute(insert_ignore_sql, duplicate_user)
            connection.commit()
            if cursor.rowcount > 0:
                print("插入成功")
            else:
                print("忽略重复数据(username已存在)")
        except pymysql.IntegrityError as e:
            print(f"完整性错误: {e}")
        
        # 6. 插入或更新(ON DUPLICATE KEY UPDATE)
        print("\n6. 插入或更新")
        upsert_sql = """
        INSERT INTO users (username, email, password_hash, age, salary)
        VALUES (%s, %s, %s, %s, %s)
        ON DUPLICATE KEY UPDATE
            email = VALUES(email),
            age = VALUES(age),
            salary = VALUES(salary),
            updated_at = CURRENT_TIMESTAMP
        """
        
        # 如果username存在则更新,否则插入
        upsert_data = ('李四', 'lisi_updated@example.com', 'hash7', 31, 8500.00)
        cursor.execute(upsert_sql, upsert_data)
        connection.commit()
        
        if cursor.rowcount == 1:
            print("插入新记录")
        elif cursor.rowcount == 2:
            print("更新现有记录")
        
        # 7. 插入后返回完整记录
        print("\n7. 插入后返回完整记录")
        insert_return_sql = """
        INSERT INTO users (username, email, password_hash, age, salary, is_active)
        VALUES (%s, %s, %s, %s, %s, %s)
        """
        
        return_user = ('周八', 'zhouba@example.com', 'hash8', 29, 6800.00, True)
        cursor.execute(insert_return_sql, return_user)
        new_id = cursor.lastrowid
        
        # 查询刚插入的记录
        cursor.execute("SELECT * FROM users WHERE id = %s", (new_id,))
        full_record = cursor.fetchone()
        print(f"完整记录: {full_record}")
        
        connection.commit()
        
        # 8. 带事务的批量插入
        print("\n8. 带事务的批量插入示例")
        orders_data = [
            (new_user_id, 'ORD20260005', 8999.00, 'pending'),
            (new_user_id, 'ORD20260006', 1999.00, 'pending')
        ]
        
        try:
            # 开始事务
            connection.begin()
            
            insert_order_sql = """
            INSERT INTO orders (user_id, order_no, total_amount, status)
            VALUES (%s, %s, %s, %s)
            """
            
            cursor.executemany(insert_order_sql, orders_data)
            
            # 验证插入
            cursor.execute("SELECT COUNT(*) as count FROM orders WHERE user_id = %s", 
                          (new_user_id,))
            order_count = cursor.fetchone()['count']
            
            if order_count == len(orders_data):
                connection.commit()
                print(f"订单插入成功!用户 {new_user_id} 有 {order_count} 个订单")
            else:
                connection.rollback()
                print("订单数量不匹配,事务回滚")
                
        except Exception as e:
            connection.rollback()
            print(f"事务失败,已回滚: {e}")
        
    except pymysql.MySQLError as e:
        print(f"插入操作错误: {e}")
        if connection:
            connection.rollback()
            
    finally:
        if cursor:
            cursor.close()
        if connection:
            connection.close()
        print("\n连接已关闭")

# 运行示例
if __name__ == "__main__":
    insert_operations_example()

第5章: 数据更新与删除操作

python 复制代码
"""
开发思路:
1. 单条和多条数据更新
2. 使用事务确保更新操作的原子性
3. 安全的删除操作
4. 软删除的实现
"""

def update_delete_operations_example():
    """数据更新与删除操作示例"""
    
    connection = None
    cursor = None
    
    try:
        # 建立连接
        connection = pymysql.connect(
            host='localhost',
            user='root',
            password='your_password',
            database='pymysql_tutorial',
            charset='utf8mb4',
            cursorclass=pymysql.cursors.DictCursor
        )
        
        cursor = connection.cursor()
        
        print("=== 数据更新与删除操作 ===")
        
        # 1. 单条数据更新
        print("\n1. 单条数据更新")
        
        # 更新前的数据
        user_id = 2
        cursor.execute("SELECT username, salary FROM users WHERE id = %s", (user_id,))
        before_update = cursor.fetchone()
        print(f"更新前 - 用户名: {before_update['username']}, 薪水: {before_update['salary']}")
        
        # 执行更新
        update_sql = """
        UPDATE users 
        SET salary = salary * 1.1,  -- 涨薪10%
            updated_at = CURRENT_TIMESTAMP
        WHERE id = %s
        """
        
        cursor.execute(update_sql, (user_id,))
        affected_rows = cursor.rowcount
        connection.commit()
        
        if affected_rows > 0:
            print(f"更新成功!影响行数: {affected_rows}")
            
            # 验证更新
            cursor.execute("SELECT username, salary FROM users WHERE id = %s", (user_id,))
            after_update = cursor.fetchone()
            print(f"更新后 - 用户名: {after_update['username']}, 薪水: {after_update['salary']}")
        
        # 2. 批量更新
        print("\n2. 批量更新")
        
        # 批量更新多个用户的薪水
        update_batch_sql = """
        UPDATE users 
        SET salary = salary + 500,
            updated_at = CURRENT_TIMESTAMP
        WHERE is_active = TRUE 
          AND salary < 7000
        """
        
        cursor.execute(update_batch_sql)
        batch_affected = cursor.rowcount
        connection.commit()
        
        print(f"批量更新成功!影响 {batch_affected} 条记录")
        
        # 查看更新结果
        cursor.execute("""
            SELECT username, salary 
            FROM users 
            WHERE is_active = TRUE 
            ORDER BY salary DESC
        """)
        
        updated_users = cursor.fetchall()
        print("活跃用户薪水排名:")
        for i, user in enumerate(updated_users, 1):
            print(f"  {i}. {user['username']}: ¥{user['salary']}")
        
        # 3. 带条件的复杂更新
        print("\n3. 带条件的复杂更新")
        
        # 根据年龄范围调整薪水
        update_complex_sql = """
        UPDATE users 
        SET salary = CASE 
            WHEN age < 25 THEN salary * 1.15  -- 25岁以下涨15%
            WHEN age BETWEEN 25 AND 30 THEN salary * 1.10  -- 25-30岁涨10%
            ELSE salary * 1.05  -- 其他涨5%
        END,
        updated_at = CURRENT_TIMESTAMP
        WHERE is_active = TRUE
        """
        
        cursor.execute(update_complex_sql)
        complex_affected = cursor.rowcount
        connection.commit()
        
        print(f"复杂更新成功!影响 {complex_affected} 条记录")
        
        # 4. 更新关联表
        print("\n4. 更新关联表")
        
        # 先查询需要更新的订单
        cursor.execute("""
            SELECT o.order_id, o.total_amount, u.username
            FROM orders o
            JOIN users u ON o.user_id = u.id
            WHERE o.status = 'pending'
        """)
        
        pending_orders = cursor.fetchall()
        print(f"待处理订单: {len(pending_orders)} 个")
        
        # 更新订单状态
        if pending_orders:
            update_orders_sql = """
            UPDATE orders
            SET status = 'paid',
                notes = CONCAT(IFNULL(notes, ''), ' 已支付')
            WHERE order_id = %s
            """
            
            order_ids = [order['order_id'] for order in pending_orders]
            cursor.executemany(update_orders_sql, [(oid,) for oid in order_ids])
            connection.commit()
            
            print(f"已更新 {cursor.rowcount} 个订单为已支付状态")
        
        # 5. 安全的删除操作(先查询后删除)
        print("\n5. 安全的删除操作")
        
        # 先查询要删除的数据
        cursor.execute("""
            SELECT id, username, email 
            FROM users 
            WHERE is_active = FALSE
        """)
        
        inactive_users = cursor.fetchall()
        print(f"非活跃用户(待删除): {len(inactive_users)} 个")
        
        if inactive_users:
            for user in inactive_users:
                print(f"  ID: {user['id']}, 用户名: {user['username']}")
            
            # 确认后删除
            confirm = input("\n确认删除以上用户?(y/n): ").lower()
            
            if confirm == 'y':
                delete_sql = "DELETE FROM users WHERE id = %s"
                user_ids = [user['id'] for user in inactive_users]
                cursor.executemany(delete_sql, [(uid,) for uid in user_ids])
                connection.commit()
                
                print(f"已删除 {cursor.rowcount} 个非活跃用户")
            else:
                print("取消删除操作")
        
        # 6. 软删除实现
        print("\n6. 软删除实现示例")
        
        # 添加软删除字段(如果不存在)
        try:
            cursor.execute("""
                ALTER TABLE users 
                ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP NULL DEFAULT NULL
            """)
            connection.commit()
            print("已添加软删除字段")
        except:
            pass
        
        # 模拟软删除
        soft_delete_user_id = 3
        soft_delete_sql = """
        UPDATE users 
        SET is_active = FALSE,
            deleted_at = CURRENT_TIMESTAMP
        WHERE id = %s
        """
        
        cursor.execute(soft_delete_sql, (soft_delete_user_id,))
        connection.commit()
        
        if cursor.rowcount > 0:
            print(f"用户 {soft_delete_user_id} 已被软删除")
            
            # 查询时排除已删除的数据
            cursor.execute("""
                SELECT COUNT(*) as active_count 
                FROM users 
                WHERE is_active = TRUE 
                  AND deleted_at IS NULL
            """)
            active_count = cursor.fetchone()['active_count']
            print(f"当前活跃用户数: {active_count}")
        
        # 7. 使用事务的更新操作
        print("\n7. 使用事务的更新操作")
        
        try:
            # 开始事务
            connection.begin()
            
            # 更新用户薪水
            cursor.execute("""
                UPDATE users 
                SET salary = salary + 1000
                WHERE id = 1
            """)
            
            # 记录薪水变更日志(模拟)
            cursor.execute("""
                INSERT INTO orders (user_id, order_no, total_amount, status, notes)
                VALUES (1, CONCAT('SALARY_', DATE_FORMAT(NOW(), '%Y%m%d%H%i%s')), 
                        1000, 'completed', '薪水调整')
            """)
            
            # 验证更新
            cursor.execute("SELECT salary FROM users WHERE id = 1")
            new_salary = cursor.fetchone()['salary']
            
            if new_salary > 0:  # 简单的验证逻辑
                connection.commit()
                print(f"事务提交成功!用户1的新薪水: ¥{new_salary}")
            else:
                connection.rollback()
                print("薪水异常,事务回滚")
                
        except Exception as e:
            connection.rollback()
            print(f"事务执行失败,已回滚: {e}")
        
        # 8. 清空表数据(谨慎使用)
        print("\n8. 清空表数据示例(注释掉实际执行)")
        
        # 注意:TRUNCATE和DELETE的区别
        # TRUNCATE是DDL操作,更快但不能回滚
        # DELETE是DML操作,可以带条件,可以回滚
        
        """
        # 清空临时表示例
        truncate_sql = "TRUNCATE TABLE temp_table"
        delete_sql = "DELETE FROM temp_table WHERE condition"
        
        # 删除表
        drop_sql = "DROP TABLE IF EXISTS temp_table"
        """
        
    except pymysql.MySQLError as e:
        print(f"更新删除操作错误: {e}")
        if connection:
            connection.rollback()
            
    finally:
        if cursor:
            cursor.close()
        if connection:
            connection.close()
        print("\n连接已关闭")

# 运行示例
if __name__ == "__main__":
    update_delete_operations_example()

第6章: 事务处理

python 复制代码
"""
开发思路:
1. 手动事务管理
2. 保存点的使用
3. 事务隔离级别
4. 错误处理和回滚
"""

def transaction_example():
    """事务处理示例"""
    
    connection = None
    cursor = None
    
    try:
        # 建立连接,启用自动提交以便演示
        connection = pymysql.connect(
            host='localhost',
            user='root',
            password='your_password',
            database='pymysql_tutorial',
            charset='utf8mb4',
            cursorclass=pymysql.cursors.DictCursor,
            autocommit=False  # 关闭自动提交,手动控制事务
        )
        
        cursor = connection.cursor()
        
        print("=== 事务处理示例 ===")
        
        # 1. 基本事务示例
        print("\n1. 基本事务示例")
        
        try:
            # 开始事务
            connection.begin()
            print("事务开始")
            
            # 执行多个操作
            # 操作1: 插入新用户
            insert_user_sql = """
            INSERT INTO users (username, email, password_hash, age, salary, is_active)
            VALUES (%s, %s, %s, %s, %s, %s)
            """
            
            user_data = ('事务用户1', 'trans1@example.com', 'hash_t1', 30, 10000.00, True)
            cursor.execute(insert_user_sql, user_data)
            user_id = cursor.lastrowid
            print(f"插入用户成功,ID: {user_id}")
            
            # 操作2: 为用户创建订单
            insert_order_sql = """
            INSERT INTO orders (user_id, order_no, total_amount, status)
            VALUES (%s, %s, %s, %s)
            """
            
            order_data = [
                (user_id, f"TRANS{user_id}_001", 5000.00, 'pending'),
                (user_id, f"TRANS{user_id}_002", 3000.00, 'pending')
            ]
            
            cursor.executemany(insert_order_sql, order_data)
            print(f"插入 {cursor.rowcount} 个订单")
            
            # 验证数据一致性
            cursor.execute("SELECT COUNT(*) as order_count FROM orders WHERE user_id = %s", 
                          (user_id,))
            order_count = cursor.fetchone()['order_count']
            
            if order_count == 2:
                # 提交事务
                connection.commit()
                print("事务提交成功!")
            else:
                # 回滚事务
                connection.rollback()
                print(f"订单数量不匹配(期望2,实际{order_count}),事务回滚")
                
        except Exception as e:
            connection.rollback()
            print(f"事务执行失败,已回滚: {e}")
        
        # 2. 保存点示例
        print("\n2. 保存点示例")
        
        try:
            connection.begin()
            print("新事务开始")
            
            # 操作1: 更新用户薪水
            cursor.execute("UPDATE users SET salary = salary + 1000 WHERE id = 1")
            print("操作1: 更新用户1薪水完成")
            
            # 设置保存点
            cursor.execute("SAVEPOINT sp1")
            print("设置保存点 sp1")
            
            # 操作2: 插入订单
            cursor.execute("""
                INSERT INTO orders (user_id, order_no, total_amount, status)
                VALUES (1, 'SAVEPOINT_001', 2000.00, 'pending')
            """)
            print("操作2: 插入订单完成")
            
            # 模拟错误发生
            error_occurred = True  # 改为False测试正常流程
            
            if error_occurred:
                # 回滚到保存点
                cursor.execute("ROLLBACK TO SAVEPOINT sp1")
                print("发生错误,回滚到保存点 sp1")
                
                # 继续其他操作
                cursor.execute("""
                    INSERT INTO orders (user_id, order_no, total_amount, status)
                    VALUES (2, 'SAVEPOINT_002', 1500.00, 'pending')
                """)
                print("操作3: 插入用户2订单完成")
            
            # 提交事务
            connection.commit()
            print("事务提交成功")
            
        except Exception as e:
            connection.rollback()
            print(f"保存点事务失败: {e}")
        
        # 3. 嵌套事务模拟
        print("\n3. 嵌套事务模拟")
        
        def nested_transaction():
            """模拟嵌套事务的函数"""
            try:
                connection.begin()
                print("  → 嵌套事务开始")
                
                cursor.execute("UPDATE users SET salary = salary - 500 WHERE id = 2")
                print("  → 更新用户2薪水完成")
                
                connection.commit()
                print("  → 嵌套事务提交")
                return True
                
            except Exception as e:
                connection.rollback()
                print(f"  → 嵌套事务回滚: {e}")
                return False
        
        try:
            connection.begin()
            print("外层事务开始")
            
            # 外层操作
            cursor.execute("UPDATE users SET salary = salary + 1000 WHERE id = 1")
            print("外层操作: 更新用户1薪水完成")
            
            # 执行嵌套事务
            if nested_transaction():
                # 嵌套事务成功,继续外层操作
                cursor.execute("""
                    INSERT INTO orders (user_id, order_no, total_amount, status)
                    VALUES (1, 'NESTED_001', 3000.00, 'completed')
                """)
                print("外层操作: 插入订单完成")
                
                connection.commit()
                print("外层事务提交")
            else:
                connection.rollback()
                print("嵌套事务失败,外层事务回滚")
                
        except Exception as e:
            connection.rollback()
            print(f"嵌套事务示例失败: {e}")
        
        # 4. 事务隔离级别
        print("\n4. 事务隔离级别")
        
        # 查看当前隔离级别
        cursor.execute("SELECT @@transaction_isolation")
        isolation_level = cursor.fetchone()['@@transaction_isolation']
        print(f"当前事务隔离级别: {isolation_level}")
        
        # 设置不同的隔离级别
        isolation_levels = [
            'READ UNCOMMITTED',    # 读未提交
            'READ COMMITTED',      # 读已提交
            'REPEATABLE READ',     # 可重复读(MySQL默认)
            'SERIALIZABLE'         # 串行化
        ]
        
        for level in isolation_levels:
            try:
                cursor.execute(f"SET SESSION TRANSACTION ISOLATION LEVEL {level}")
                print(f"已设置隔离级别为: {level}")
                
                # 测试该隔离级别
                connection.begin()
                
                cursor.execute("SELECT COUNT(*) as cnt FROM users")
                count = cursor.fetchone()['cnt']
                print(f"  当前用户数: {count}")
                
                connection.commit()
                
            except Exception as e:
                print(f"  设置隔离级别 {level} 失败: {e}")
                if connection:
                    connection.rollback()
        
        # 恢复默认隔离级别
        cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ")
        
        # 5. 并发事务示例
        print("\n5. 并发事务示例")
        
        def transaction_worker(worker_id, delay=1):
            """模拟并发事务的工作线程"""
            import time
            
            worker_conn = pymysql.connect(
                host='localhost',
                user='root',
                password='your_password',
                database='pymysql_tutorial',
                charset='utf8mb4',
                cursorclass=pymysql.cursors.DictCursor,
                autocommit=False
            )
            
            worker_cursor = worker_conn.cursor()
            
            try:
                print(f"\n工作线程 {worker_id} 开始")
                
                # 设置隔离级别为 READ COMMITTED
                worker_cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED")
                worker_conn.begin()
                
                # 读取数据
                worker_cursor.execute("SELECT salary FROM users WHERE id = 1 FOR UPDATE")
                result = worker_cursor.fetchone()
                current_salary = result['salary']
                print(f"线程 {worker_id}: 读取到薪水 {current_salary}")
                
                # 模拟处理时间
                time.sleep(delay)
                
                # 更新数据
                new_salary = current_salary + worker_id * 100
                worker_cursor.execute(
                    "UPDATE users SET salary = %s WHERE id = 1",
                    (new_salary,)
                )
                print(f"线程 {worker_id}: 更新薪水为 {new_salary}")
                
                worker_conn.commit()
                print(f"线程 {worker_id}: 提交成功")
                
                return True
                
            except Exception as e:
                worker_conn.rollback()
                print(f"线程 {worker_id}: 失败 - {e}")
                return False
                
            finally:
                worker_cursor.close()
                worker_conn.close()
        
        # 在实际应用中,这里会使用多线程执行
        # 为了示例清晰,我们顺序执行
        print("注意:实际并发事务需要多线程环境,以下是模拟")
        transaction_worker(1, 0.5)
        transaction_worker(2, 0.3)
        
        # 6. 错误处理和回滚策略
        print("\n6. 错误处理和回滚策略")
        
        try:
            connection.begin()
            
            # 正常操作
            cursor.execute("UPDATE users SET salary = salary + 500 WHERE id = 1")
            print("操作1完成")
            
            # 模拟一个会失败的操作
            try:
                # 尝试插入重复的唯一键
                cursor.execute("""
                    INSERT INTO users (username, email, password_hash, age, salary)
                    VALUES ('张三', 'duplicate@example.com', 'hash', 25, 5000)
                """)
            except pymysql.IntegrityError as e:
                print(f"捕获到完整性错误: {e.args[1]}")
                # 继续执行其他操作
                cursor.execute("UPDATE users SET salary = salary + 200 WHERE id = 2")
                print("错误处理后继续执行")
            
            # 另一个可能失败的操作
            try:
                cursor.execute("SELECT * FROM non_existent_table")
            except pymysql.ProgrammingError as e:
                print(f"捕获到编程错误: {e.args[1]}")
                # 记录错误但继续
            
            # 最终提交
            connection.commit()
            print("事务提交成功(部分操作失败但被处理)")
            
        except Exception as e:
            connection.rollback()
            print(f"事务失败,已回滚: {e}")
        
        # 7. 长事务监控
        print("\n7. 长事务监控")
        
        # 查询当前运行的事务
        cursor.execute("""
            SELECT 
                trx_id,
                trx_state,
                trx_started,
                TIMESTAMPDIFF(SECOND, trx_started, NOW()) as duration_seconds,
                trx_mysql_thread_id
            FROM information_schema.INNODB_TRX
            ORDER BY trx_started
        """)
        
        current_transactions = cursor.fetchall()
        
        if current_transactions:
            print("当前运行的事务:")
            for trx in current_transactions:
                print(f"  事务ID: {trx['trx_id']}, "
                      f"状态: {trx['trx_state']}, "
                      f"运行时间: {trx['duration_seconds']}秒")
        else:
            print("当前没有运行的事务")
            
    except pymysql.MySQLError as e:
        print(f"事务处理错误: {e}")
        if connection:
            connection.rollback()
            
    finally:
        if cursor:
            cursor.close()
        if connection:
            # 确保连接关闭前提交或回滚
            try:
                connection.rollback()
            except:
                pass
            connection.close()
        print("\n连接已关闭")

# 运行示例
if __name__ == "__main__":
    transaction_example()

第7章: 高级特性

python 复制代码
"""
开发思路:
1. 存储过程调用
2. 批量操作优化
3. 连接池管理
4. ORM风格封装
"""

def advanced_features_example():
    """高级特性示例"""
    
    # 1. 连接池管理
    print("=== 高级特性示例 ===")
    print("\n1. 连接池管理")
    
    from pymysql import pools
    import threading
    import time
    
    # 创建连接池
    pool = pools.Pool(
        creator=pymysql,  # 使用的数据库模块
        maxconnections=5,  # 连接池最大连接数
        mincached=2,       # 初始化时创建的连接数
        maxcached=5,       # 连接池最大空闲连接数
        blocking=True,     # 连接池满时是否阻塞等待
        ping=0,           # 检查连接是否可用的ping频率
        host='localhost',
        user='root',
        password='your_password',
        database='pymysql_tutorial',
        charset='utf8mb4',
        cursorclass=pymysql.cursors.DictCursor
    )
    
    def pool_worker(worker_id):
        """使用连接池的工作线程"""
        connection = None
        try:
            # 从连接池获取连接
            connection = pool.connection()
            cursor = connection.cursor()
            
            # 执行查询
            cursor.execute("SELECT CONNECTION_ID() as conn_id, USER() as user, DATABASE() as db")
            result = cursor.fetchone()
            
            print(f"线程 {worker_id}: 连接ID={result['conn_id']}, "
                  f"数据库={result['db']}, 用户={result['user']}")
            
            # 模拟工作
            time.sleep(0.1)
            
            cursor.close()
            
        except Exception as e:
            print(f"线程 {worker_id} 错误: {e}")
        finally:
            # 连接归还给连接池
            if connection:
                connection.close()
    
    # 创建多个线程测试连接池
    threads = []
    for i in range(10):
        t = threading.Thread(target=pool_worker, args=(i+1,))
        threads.append(t)
        t.start()
    
    # 等待所有线程完成
    for t in threads:
        t.join()
    
    print("连接池测试完成")
    
    # 2. 存储过程调用
    print("\n2. 存储过程调用")
    
    connection = None
    cursor = None
    
    try:
        connection = pymysql.connect(
            host='localhost',
            user='root',
            password='your_password',
            database='pymysql_tutorial',
            charset='utf8mb4',
            cursorclass=pymysql.cursors.DictCursor
        )
        
        cursor = connection.cursor()
        
        # 创建存储过程
        create_proc_sql = """
        DROP PROCEDURE IF EXISTS get_user_statistics;
        CREATE PROCEDURE get_user_statistics(
            IN min_age INT,
            IN min_salary DECIMAL(10,2),
            OUT user_count INT,
            OUT avg_salary DECIMAL(10,2)
        )
        BEGIN
            -- 统计符合条件的用户数
            SELECT COUNT(*) INTO user_count
            FROM users
            WHERE (age IS NULL OR age >= min_age)
              AND salary >= min_salary
              AND is_active = TRUE;
            
            -- 计算平均薪水
            SELECT IFNULL(AVG(salary), 0) INTO avg_salary
            FROM users
            WHERE (age IS NULL OR age >= min_age)
              AND salary >= min_salary
              AND is_active = TRUE;
        END
        """
        
        # 执行多条语句
        for result in cursor.execute(create_proc_sql, multi=True):
            pass
        
        connection.commit()
        print("存储过程创建成功")
        
        # 调用存储过程
        print("\n调用存储过程:")
        
        # 准备调用参数
        min_age = 25
        min_salary = 5000
        
        # 调用存储过程
        cursor.callproc('get_user_statistics', [min_age, min_salary, 0, 0.0])
        
        # 获取输出参数
        result = cursor.fetchall()
        print(f"查询结果: {result}")
        
        # 另一种方式:使用SELECT获取输出
        cursor.execute("SELECT @_get_user_statistics_2 as user_count, "
                      "@_get_user_statistics_3 as avg_salary")
        stats = cursor.fetchone()
        print(f"年龄>={min_age}且薪水>={min_salary}的活跃用户:")
        print(f"  用户数: {stats['user_count']}")
        print(f"  平均薪水: ¥{stats['avg_salary']:.2f}")
        
        # 3. 批量操作优化
        print("\n3. 批量操作优化")
        
        # 生成测试数据
        test_data = []
        for i in range(1000):
            test_data.append((
                f'test_user_{i}',
                f'test{i}@example.com',
                f'hash_{i}',
                20 + (i % 30),
                5000 + (i % 5000),
                True
            ))
        
        # 方法1: 逐条插入(慢)
        print("方法1: 逐条插入")
        start_time = time.time()
        
        insert_single_sql = """
        INSERT INTO users (username, email, password_hash, age, salary, is_active)
        VALUES (%s, %s, %s, %s, %s, %s)
        """
        
        # 注释掉实际执行,只做演示
        # for data in test_data[:10]:  # 只插入10条做测试
        #     cursor.execute(insert_single_sql, data)
        
        single_time = time.time() - start_time
        print(f"  理论时间: {single_time * 100:.2f}秒 (插入1000条)")
        
        # 方法2: executemany批量插入
        print("\n方法2: executemany批量插入")
        start_time = time.time()
        
        # 注释掉实际执行
        # cursor.executemany(insert_single_sql, test_data)
        
        batch_time = time.time() - start_time
        print(f"  实际时间: {batch_time:.2f}秒")
        print(f"  性能提升: {single_time * 100 / max(batch_time, 0.001):.1f}倍")
        
        connection.commit()
        
        # 方法3: 使用LOAD DATA INFILE(最快)
        print("\n方法3: LOAD DATA INFILE (最快)")
        
        # 先创建临时CSV文件
        import csv
        csv_file = 'temp_users.csv'
        
        with open(csv_file, 'w', newline='', encoding='utf-8') as f:
            writer = csv.writer(f)
            # 写入表头(可选)
            writer.writerow(['username', 'email', 'password_hash', 'age', 'salary', 'is_active'])
            for data in test_data[:100]:  # 只测试100条
                writer.writerow(data)
        
        # 使用LOAD DATA INFILE
        load_sql = f"""
        LOAD DATA LOCAL INFILE '{csv_file}'
        INTO TABLE users
        FIELDS TERMINATED BY ','
        ENCLOSED BY '"'
        LINES TERMINATED BY '\\n'
        IGNORE 1 ROWS
        (username, email, password_hash, age, salary, is_active)
        """
        
        try:
            cursor.execute(load_sql)
            print(f"  LOAD DATA INFILE 插入 {cursor.rowcount} 条记录")
            connection.commit()
        except Exception as e:
            print(f"  LOAD DATA INFILE 错误: {e}")
            connection.rollback()
        
        # 清理临时文件
        import os
        if os.path.exists(csv_file):
            os.remove(csv_file)
        
        # 4. 服务器端游标(用于大数据集)
        print("\n4. 服务器端游标(大数据集处理)")
        
        # 使用SSCursor处理大量数据
        with connection.cursor(pymysql.cursors.SSCursor) as ss_cursor:
            ss_cursor.execute("SELECT * FROM users WHERE is_active = TRUE")
            
            batch_size = 100
            total_count = 0
            
            while True:
                # 分批获取
                rows = ss_cursor.fetchmany(batch_size)
                if not rows:
                    break
                
                total_count += len(rows)
                print(f"  已处理 {total_count} 条记录", end='\r')
            
            print(f"\n  总共处理 {total_count} 条活跃用户记录")
        
        # 5. 预处理语句
        print("\n5. 预处理语句")
        
        # 创建预处理语句
        prep_stmt = """
        SELECT 
            username,
            email,
            salary
        FROM users
        WHERE age BETWEEN %s AND %s
          AND salary >= %s
          AND is_active = %s
        ORDER BY salary DESC
        LIMIT %s
        """
        
        # 准备参数
        params = (25, 35, 6000, True, 5)
        
        # 多次执行相同的预处理语句
        print("执行预处理查询:")
        cursor.execute(prep_stmt, params)
        
        results = cursor.fetchall()
        for i, row in enumerate(results, 1):
            print(f"  {i}. {row['username']}: ¥{row['salary']}, {row['email']}")
        
        # 6. 数据库元数据查询
        print("\n6. 数据库元数据")
        
        # 获取表信息
        cursor.execute("""
            SELECT 
                TABLE_NAME,
                TABLE_ROWS,
                DATA_LENGTH,
                INDEX_LENGTH,
                CREATE_TIME,
                UPDATE_TIME
            FROM information_schema.TABLES
            WHERE TABLE_SCHEMA = DATABASE()
            ORDER BY TABLE_NAME
        """)
        
        tables_info = cursor.fetchall()
        print("数据库表信息:")
        for table in tables_info:
            size_mb = ((table['DATA_LENGTH'] or 0) + (table['INDEX_LENGTH'] or 0)) / 1024 / 1024
            print(f"  表: {table['TABLE_NAME']}")
            print(f"    行数: {table['TABLE_ROWS']:,}")
            print(f"    大小: {size_mb:.2f} MB")
            print(f"    创建时间: {table['CREATE_TIME']}")
        
        # 获取列信息
        cursor.execute("""
            SELECT 
                COLUMN_NAME,
                DATA_TYPE,
                IS_NULLABLE,
                COLUMN_DEFAULT,
                COLUMN_COMMENT
            FROM information_schema.COLUMNS
            WHERE TABLE_SCHEMA = DATABASE()
              AND TABLE_NAME = 'users'
            ORDER BY ORDINAL_POSITION
        """)
        
        columns_info = cursor.fetchall()
        print("\nusers表结构:")
        for col in columns_info:
            print(f"  {col['COLUMN_NAME']}: {col['DATA_TYPE']} "
                  f"({'可空' if col['IS_NULLABLE'] == 'YES' else '非空'})")
            if col['COLUMN_COMMENT']:
                print(f"    注释: {col['COLUMN_COMMENT']}")
        
        # 7. ORM风格封装示例
        print("\n7. ORM风格简单封装示例")
        
        class Database:
            """简单的数据库封装类"""
            
            def __init__(self, **config):
                self.config = config
                self.connection = None
            
            def __enter__(self):
                self.connect()
                return self
            
            def __exit__(self, exc_type, exc_val, exc_tb):
                self.close()
            
            def connect(self):
                """建立连接"""
                self.connection = pymysql.connect(**self.config)
                return self.connection
            
            def close(self):
                """关闭连接"""
                if self.connection:
                    self.connection.close()
                    self.connection = None
            
            def execute(self, sql, params=None):
                """执行SQL语句"""
                with self.connection.cursor(pymysql.cursors.DictCursor) as cursor:
                    cursor.execute(sql, params or ())
                    return cursor
            
            def fetch_one(self, sql, params=None):
                """获取单条记录"""
                with self.connection.cursor(pymysql.cursors.DictCursor) as cursor:
                    cursor.execute(sql, params or ())
                    return cursor.fetchone()
            
            def fetch_all(self, sql, params=None):
                """获取所有记录"""
                with self.connection.cursor(pymysql.cursors.DictCursor) as cursor:
                    cursor.execute(sql, params or ())
                    return cursor.fetchall()
            
            def insert(self, table, data):
                """插入数据"""
                if not data:
                    return 0
                
                keys = ', '.join(data.keys())
                placeholders = ', '.join(['%s'] * len(data))
                sql = f"INSERT INTO {table} ({keys}) VALUES ({placeholders})"
                
                with self.connection.cursor() as cursor:
                    cursor.execute(sql, tuple(data.values()))
                    self.connection.commit()
                    return cursor.lastrowid
            
            def update(self, table, data, where):
                """更新数据"""
                if not data:
                    return 0
                
                set_clause = ', '.join([f"{k} = %s" for k in data.keys()])
                sql = f"UPDATE {table} SET {set_clause} WHERE {where}"
                
                with self.connection.cursor() as cursor:
                    cursor.execute(sql, tuple(data.values()))
                    self.connection.commit()
                    return cursor.rowcount
        
        # 使用ORM风格封装
        print("使用ORM风格查询:")
        
        db_config = {
            'host': 'localhost',
            'user': 'root',
            'password': 'your_password',
            'database': 'pymysql_tutorial',
            'charset': 'utf8mb4'
        }
        
        with Database(**db_config) as db:
            # 查询示例
            result = db.fetch_one("SELECT COUNT(*) as count FROM users")
            print(f"用户总数: {result['count']}")
            
            # 插入示例
            new_user = {
                'username': 'orm_user',
                'email': 'orm@example.com',
                'password_hash': 'orm_hash',
                'age': 28,
                'salary': 8888.88,
                'is_active': True
            }
            
            # user_id = db.insert('users', new_user)
            # print(f"插入用户ID: {user_id}")
            
            # 更新示例
            update_data = {'salary': 9999.99}
            # affected = db.update('users', update_data, 'username = "orm_user"')
            # print(f"更新影响行数: {affected}")
            
    except pymysql.MySQLError as e:
        print(f"高级特性错误: {e}")
        
    finally:
        if cursor:
            cursor.close()
        if connection:
            connection.close()
        print("\n连接已关闭")

# 运行示例
if __name__ == "__main__":
    advanced_features_example()

第8章: 错误处理与调试

python 复制代码
"""
开发思路:
1. 异常分类处理
2. 连接重试机制
3. 查询超时设置
4. 日志记录和监控
"""

import time
import logging
from functools import wraps
from pymysql.constants import ER

def error_handling_example():
    """错误处理与调试示例"""
    
    # 配置详细日志
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler('mysql_operations.log'),
            logging.StreamHandler()
        ]
    )
    
    logger = logging.getLogger(__name__)
    
    print("=== 错误处理与调试 ===")
    
    # 1. 异常分类处理
    print("\n1. 异常分类处理")
    
    def handle_database_operation():
        """数据库操作异常处理示例"""
        connection = None
        cursor = None
        
        try:
            connection = pymysql.connect(
                host='localhost',
                user='root',
                password='your_password',
                database='pymysql_tutorial',
                charset='utf8mb4',
                cursorclass=pymysql.cursors.DictCursor,
                connect_timeout=5,  # 连接超时
                read_timeout=30,    # 读取超时
                write_timeout=30    # 写入超时
            )
            
            cursor = connection.cursor()
            
            # 各种可能出错的操作
            operations = [
                # 正常操作
                ("SELECT 1 + 1 AS result", None, "简单计算"),
                
                # 表不存在错误
                ("SELECT * FROM non_existent_table", None, "查询不存在的表"),
                
                # 语法错误
                ("SELEC * FROM users", None, "SQL语法错误"),
                
                # 重复键错误
                ("INSERT INTO users (username, email, password_hash) VALUES ('test', 'test@test.com', 'hash')", 
                 None, "插入重复数据"),
                
                # 数据类型错误
                ("INSERT INTO users (username, email, password_hash, age) VALUES ('test2', 'test2@test.com', 'hash', 'not_a_number')", 
                 None, "数据类型错误"),
                
                # 权限错误(需要错误的权限配置)
                # ("DROP DATABASE mysql", None, "权限不足"),
            ]
            
            for sql, params, description in operations:
                try:
                    print(f"\n尝试: {description}")
                    print(f"SQL: {sql}")
                    
                    cursor.execute(sql, params)
                    
                    if cursor.description:  # 是查询语句
                        result = cursor.fetchall()
                        print(f"结果: {result}")
                    else:  # 是更新语句
                        print(f"影响行数: {cursor.rowcount}")
                    
                    connection.commit()
                    
                except pymysql.ProgrammingError as e:
                    print(f"编程错误 (代码 {e.args[0]}): {e.args[1]}")
                    connection.rollback()
                    
                except pymysql.IntegrityError as e:
                    print(f"完整性错误 (代码 {e.args[0]}): {e.args[1]}")
                    connection.rollback()
                    
                except pymysql.DataError as e:
                    print(f"数据错误 (代码 {e.args[0]}): {e.args[1]}")
                    connection.rollback()
                    
                except pymysql.OperationalError as e:
                    print(f"操作错误 (代码 {e.args[0]}): {e.args[1]}")
                    connection.rollback()
                    
                except pymysql.InternalError as e:
                    print(f"内部错误 (代码 {e.args[0]}): {e.args[1]}")
                    connection.rollback()
                    
                except pymysql.NotSupportedError as e:
                    print(f"不支持错误 (代码 {e.args[0]}): {e.args[1]}")
                    connection.rollback()
                    
                except Exception as e:
                    print(f"未知错误: {type(e).__name__}: {e}")
                    connection.rollback()
        
        except pymysql.Error as e:
            print(f"连接错误: {e}")
            
        finally:
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    handle_database_operation()
    
    # 2. 连接重试装饰器
    print("\n2. 带重试的数据库操作")
    
    def retry_on_failure(max_retries=3, delay=1, backoff=2):
        """重试装饰器"""
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                retries = 0
                last_exception = None
                
                while retries < max_retries:
                    try:
                        return func(*args, **kwargs)
                        
                    except (pymysql.OperationalError, pymysql.InterfaceError) as e:
                        retries += 1
                        last_exception = e
                        
                        if retries == max_retries:
                            break
                            
                        wait_time = delay * (backoff ** (retries - 1))
                        print(f"操作失败,{wait_time}秒后重试 ({retries}/{max_retries}): {e}")
                        time.sleep(wait_time)
                        
                    except Exception as e:
                        # 非连接错误,直接抛出
                        raise
                
                # 重试次数用尽
                raise last_exception
                
            return wrapper
        return decorator
    
    class RobustDatabase:
        """健壮的数据库连接类"""
        
        def __init__(self, config, max_retries=3):
            self.config = config
            self.max_retries = max_retries
            self.connection = None
        
        @retry_on_failure(max_retries=3)
        def connect(self):
            """带重试的连接"""
            print("尝试连接数据库...")
            self.connection = pymysql.connect(**self.config)
            print("数据库连接成功")
            return self.connection
        
        def execute_with_retry(self, sql, params=None):
            """带重试的执行"""
            
            @retry_on_failure(max_retries=self.max_retries)
            def _execute():
                with self.connection.cursor(pymysql.cursors.DictCursor) as cursor:
                    cursor.execute(sql, params or ())
                    if cursor.description:
                        return cursor.fetchall()
                    else:
                        return cursor.rowcount
            
            return _execute()
        
        def close(self):
            if self.connection:
                self.connection.close()
    
    # 测试重试机制
    print("\n测试重试机制:")
    try:
        db_config = {
            'host': 'localhost',
            'user': 'root',
            'password': 'your_password',
            'database': 'pymysql_tutorial',
            'charset': 'utf8mb4',
            connect_timeout=3
        }
        
        robust_db = RobustDatabase(db_config, max_retries=2)
        robust_db.connect()
        
        # 执行查询
        result = robust_db.execute_with_retry("SELECT COUNT(*) as count FROM users")
        print(f"用户数: {result[0]['count']}")
        
        robust_db.close()
        
    except Exception as e:
        print(f"重试测试失败: {e}")
    
    # 3. 查询超时控制
    print("\n3. 查询超时控制")
    
    def execute_with_timeout(sql, params=None, timeout=5):
        """带超时的查询执行"""
        import signal
        
        class TimeoutError(Exception):
            pass
        
        def timeout_handler(signum, frame):
            raise TimeoutError("查询超时")
        
        connection = None
        cursor = None
        
        try:
            # 设置超时信号
            signal.signal(signal.SIGALRM, timeout_handler)
            signal.alarm(timeout)  # 设置超时时间
            
            connection = pymysql.connect(
                host='localhost',
                user='root',
                password='your_password',
                database='pymysql_tutorial',
                charset='utf8mb4',
                cursorclass=pymysql.cursors.DictCursor
            )
            
            cursor = connection.cursor()
            cursor.execute(sql, params or ())
            
            if cursor.description:
                result = cursor.fetchall()
            else:
                result = cursor.rowcount
            
            connection.commit()
            
            # 取消超时
            signal.alarm(0)
            
            return result
            
        except TimeoutError as e:
            print(f"查询超时: {timeout}秒")
            if connection:
                connection.rollback()
            raise
            
        except Exception as e:
            if connection:
                connection.rollback()
            raise
            
        finally:
            # 确保取消超时
            signal.alarm(0)
            
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    # 测试超时(注释掉,因为会实际超时)
    # try:
    #     result = execute_with_timeout("SELECT SLEEP(10)", timeout=3)
    #     print(f"结果: {result}")
    # except Exception as e:
    #     print(f"超时测试: {e}")
    
    # 4. 详细的错误日志
    print("\n4. 详细的错误日志记录")
    
    class DatabaseLogger:
        """数据库操作日志记录器"""
        
        def __init__(self):
            self.logger = logging.getLogger('DatabaseLogger')
            self.logger.setLevel(logging.DEBUG)
            
            # 添加文件处理器
            fh = logging.FileHandler('database_errors.log')
            fh.setLevel(logging.DEBUG)
            
            # 创建格式化器
            formatter = logging.Formatter(
                '%(asctime)s - %(name)s - %(levelname)s - %(message)s\n'
                '  File: %(pathname)s:%(lineno)d\n'
            )
            fh.setFormatter(formatter)
            self.logger.addHandler(fh)
        
        def log_operation(self, operation, sql, params=None, error=None, duration=None):
            """记录数据库操作"""
            log_data = {
                'operation': operation,
                'sql': sql,
                'params': params,
                'duration': duration
            }
            
            if error:
                log_data['error'] = str(error)
                self.logger.error(f"数据库操作失败: {log_data}")
            else:
                self.logger.info(f"数据库操作成功: {log_data}")
        
        def execute_with_logging(self, connection, sql, params=None):
            """带日志的执行"""
            start_time = time.time()
            
            try:
                with connection.cursor(pymysql.cursors.DictCursor) as cursor:
                    cursor.execute(sql, params or ())
                    
                    if cursor.description:
                        result = cursor.fetchall()
                    else:
                        result = cursor.rowcount
                
                duration = time.time() - start_time
                self.log_operation('execute', sql, params, duration=duration)
                
                connection.commit()
                return result
                
            except Exception as e:
                duration = time.time() - start_time
                self.log_operation('execute', sql, params, error=e, duration=duration)
                connection.rollback()
                raise
    
    # 测试日志记录
    print("测试日志记录(查看database_errors.log文件)")
    
    logger = DatabaseLogger()
    
    try:
        connection = pymysql.connect(
            host='localhost',
            user='root',
            password='your_password',
            database='pymysql_tutorial',
            charset='utf8mb4',
            cursorclass=pymysql.cursors.DictCursor
        )
        
        # 成功操作
        result = logger.execute_with_logging(
            connection, 
            "SELECT username, email FROM users WHERE id = %s", 
            (1,)
        )
        print(f"查询结果: {result}")
        
        # 失败操作
        try:
            logger.execute_with_logging(
                connection,
                "SELECT * FROM non_existent_table"
            )
        except Exception as e:
            print(f"预期中的错误: {e}")
        
        connection.close()
        
    except Exception as e:
        print(f"日志记录测试失败: {e}")
    
    # 5. 性能监控
    print("\n5. 查询性能监控")
    
    def monitor_performance():
        """查询性能监控"""
        
        connection = None
        cursor = None
        
        try:
            connection = pymysql.connect(
                host='localhost',
                user='root',
                password='your_password',
                database='pymysql_tutorial',
                charset='utf8mb4',
                cursorclass=pymysql.cursors.DictCursor
            )
            
            cursor = connection.cursor()
            
            # 开启性能监控
            cursor.execute("SET profiling = 1")
            cursor.execute("SET profiling_history_size = 10")
            
            # 执行一些查询
            queries = [
                "SELECT * FROM users WHERE id = 1",
                "SELECT * FROM users WHERE username = '张三'",
                "SELECT * FROM users ORDER BY salary DESC LIMIT 10",
                """
                SELECT u.username, COUNT(o.order_id) as order_count
                FROM users u
                LEFT JOIN orders o ON u.id = o.user_id
                GROUP BY u.id
                HAVING order_count > 0
                """
            ]
            
            for i, query in enumerate(queries, 1):
                start_time = time.time()
                
                cursor.execute(query)
                result = cursor.fetchall()
                
                end_time = time.time()
                duration = (end_time - start_time) * 1000  # 转毫秒
                
                print(f"查询{i}执行时间: {duration:.2f}ms, 返回 {len(result)} 行")
            
            # 查看性能分析
            print("\n性能分析报告:")
            cursor.execute("SHOW PROFILES")
            profiles = cursor.fetchall()
            
            for profile in profiles:
                query_id = profile['Query_ID']
                duration = profile['Duration'] * 1000  # 秒转毫秒
                query = profile['Query'][:50] + "..." if len(profile['Query']) > 50 else profile['Query']
                print(f"  Query {query_id}: {duration:.2f}ms - {query}")
            
            # 获取详细分析
            if profiles:
                cursor.execute(f"SHOW PROFILE FOR QUERY {profiles[-1]['Query_ID']}")
                details = cursor.fetchall()
                print(f"\n最后一个查询的详细分析:")
                for detail in details:
                    if detail['Duration'] > 0.0001:  # 只显示耗时较长的步骤
                        print(f"  {detail['Status']}: {detail['Duration']*1000:.2f}ms")
            
            # 关闭性能监控
            cursor.execute("SET profiling = 0")
            
        except Exception as e:
            print(f"性能监控错误: {e}")
            
        finally:
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    monitor_performance()
    
    # 6. 连接池健康检查
    print("\n6. 连接池健康检查")
    
    class HealthyConnectionPool:
        """带健康检查的连接池"""
        
        def __init__(self, max_connections=5, **connection_args):
            self.pool = []
            self.max_connections = max_connections
            self.connection_args = connection_args
            self.lock = threading.Lock()
        
        def get_connection(self):
            """获取连接,带健康检查"""
            with self.lock:
                # 清理无效连接
                self._clean_pool()
                
                # 尝试从池中获取
                for conn in self.pool:
                    if self._is_connection_healthy(conn):
                        self.pool.remove(conn)
                        return conn
                
                # 创建新连接
                if len(self.pool) < self.max_connections:
                    return self._create_connection()
                else:
                    raise Exception("连接池已满")
        
        def return_connection(self, connection):
            """归还连接"""
            with self.lock:
                if self._is_connection_healthy(connection):
                    self.pool.append(connection)
                else:
                    try:
                        connection.close()
                    except:
                        pass
        
        def _create_connection(self):
            """创建新连接"""
            try:
                conn = pymysql.connect(**self.connection_args)
                conn.ping()  # 测试连接
                return conn
            except Exception as e:
                raise Exception(f"创建连接失败: {e}")
        
        def _is_connection_healthy(self, connection):
            """检查连接是否健康"""
            try:
                connection.ping(reconnect=False)
                return True
            except:
                return False
        
        def _clean_pool(self):
            """清理连接池"""
            healthy_connections = []
            for conn in self.pool:
                if self._is_connection_healthy(conn):
                    healthy_connections.append(conn)
                else:
                    try:
                        conn.close()
                    except:
                        pass
            self.pool = healthy_connections
        
        def close_all(self):
            """关闭所有连接"""
            with self.lock:
                for conn in self.pool:
                    try:
                        conn.close()
                    except:
                        pass
                self.pool.clear()
    
    # 测试健康连接池
    print("测试健康连接池:")
    pool_config = {
        'host': 'localhost',
        'user': 'root',
        'password': 'your_password',
        'database': 'pymysql_tutorial',
        'charset': 'utf8mb4',
        'cursorclass': pymysql.cursors.DictCursor
    }
    
    healthy_pool = HealthyConnectionPool(max_connections=3, **pool_config)
    
    try:
        # 获取连接
        conn1 = healthy_pool.get_connection()
        print("获取连接1成功")
        
        with conn1.cursor() as cursor:
            cursor.execute("SELECT CONNECTION_ID() as id")
            result = cursor.fetchone()
            print(f"连接1 ID: {result['id']}")
        
        # 归还连接
        healthy_pool.return_connection(conn1)
        print("归还连接1")
        
        # 再次获取
        conn2 = healthy_pool.get_connection()
        with conn2.cursor() as cursor:
            cursor.execute("SELECT CONNECTION_ID() as id")
            result = cursor.fetchone()
            print(f"连接2 ID: {result['id']}")
        
        healthy_pool.return_connection(conn2)
        
    except Exception as e:
        print(f"连接池测试错误: {e}")
    
    finally:
        healthy_pool.close_all()
        print("连接池已关闭")

# 运行示例
if __name__ == "__main__":
    error_handling_example()

第9章: 实战项目 - 用户管理系统

python 复制代码
"""
开发思路:
1. 实现完整的用户管理系统
2. 使用面向对象设计
3. 包含所有CRUD操作
4. 添加业务逻辑和验证
"""

import pymysql
import hashlib
import re
import csv
import threading
from typing import Optional, List, Dict, Any
from datetime import datetime

class UserManagementSystem:
    """用户管理系统"""
    
    def __init__(self, host='localhost', user='root', password='', database='pymysql_tutorial'):
        """
        初始化数据库连接
        
        参数:
            host: 数据库主机地址
            user: 数据库用户名
            password: 数据库密码
            database: 数据库名
        """
        self.db_config = {
            'host': host,
            'user': user,
            'password': password,
            'database': database,
            'charset': 'utf8mb4',
            'cursorclass': pymysql.cursors.DictCursor,
            'autocommit': False
        }
        
        # 创建必要的表
        self._create_tables()
    
    def _get_connection(self) -> pymysql.connections.Connection:
        """获取数据库连接"""
        return pymysql.connect(**self.db_config)
    
    def _create_tables(self) -> None:
        """创建系统所需的表"""
        connection = None
        cursor = None
        
        try:
            # 建立数据库连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 创建用户表(如果不存在)
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS system_users (
                    user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '系统用户ID',
                    username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
                    email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
                    password_hash VARCHAR(255) NOT NULL COMMENT '密码哈希',
                    full_name VARCHAR(100) COMMENT '全名',
                    phone VARCHAR(20) COMMENT '电话',
                    department VARCHAR(50) COMMENT '部门',
                    role ENUM('admin', 'manager', 'user') DEFAULT 'user' COMMENT '角色',
                    status ENUM('active', 'inactive', 'locked') DEFAULT 'active' COMMENT '状态',
                    last_login TIMESTAMP NULL COMMENT '最后登录时间',
                    login_attempts TINYINT DEFAULT 0 COMMENT '登录尝试次数',
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
                    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
                    INDEX idx_username (username),
                    INDEX idx_email (email),
                    INDEX idx_status (status),
                    INDEX idx_role (role)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表'
            """)
            
            # 创建操作日志表
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS user_audit_log (
                    log_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '日志ID',
                    user_id INT COMMENT '操作用户ID',
                    action_type VARCHAR(50) NOT NULL COMMENT '操作类型',
                    action_details TEXT COMMENT '操作详情',
                    ip_address VARCHAR(45) COMMENT 'IP地址',
                    user_agent TEXT COMMENT '用户代理',
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
                    FOREIGN KEY (user_id) REFERENCES system_users(user_id) ON DELETE SET NULL,
                    INDEX idx_user_id (user_id),
                    INDEX idx_action_type (action_type),
                    INDEX idx_created_at (created_at)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户审计日志'
            """)
            
            # 提交创建表的操作
            connection.commit()
            print("系统表创建完成")
            
        except Exception as e:
            # 如果发生异常,回滚所有操作
            print(f"创建表错误: {e}")
            if connection:
                connection.rollback()
                
        finally:
            # 确保游标和连接被关闭
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    def _hash_password(self, password: str) -> str:
        """
        哈希密码
        
        参数:
            password: 原始密码
        
        返回:
            str: 哈希后的密码
        """
        # 使用SHA256加盐哈希
        salt = "pymysql_tutorial_salt"
        return hashlib.sha256(f"{password}{salt}".encode()).hexdigest()
    
    def _validate_email(self, email: str) -> bool:
        """
        验证邮箱格式
        
        参数:
            email: 邮箱地址
        
        返回:
            bool: 格式是否正确
        """
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        return re.match(pattern, email) is not None
    
    def _validate_phone(self, phone: str) -> bool:
        """
        验证手机格式
        
        参数:
            phone: 手机号码
        
        返回:
            bool: 格式是否正确
        """
        pattern = r'^1[3-9]\d{9}$'  # 简单的中国手机号验证
        return re.match(pattern, phone) is not None
    
    def _log_audit(self, user_id: Optional[int], action_type: str, details: str, 
                   ip: str = None, user_agent: str = None) -> None:
        """
        记录审计日志
        
        参数:
            user_id: 操作用户ID
            action_type: 操作类型
            details: 操作详情
            ip: IP地址
            user_agent: 用户代理
        """
        connection = None
        cursor = None
        
        try:
            # 建立连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 插入审计日志
            cursor.execute("""
                INSERT INTO user_audit_log 
                (user_id, action_type, action_details, ip_address, user_agent)
                VALUES (%s, %s, %s, %s, %s)
            """, (user_id, action_type, details, ip, user_agent))
            
            # 提交事务
            connection.commit()
            
        except Exception as e:
            # 记录日志失败不影响主业务
            print(f"记录审计日志错误: {e}")
            if connection:
                connection.rollback()
                
        finally:
            # 关闭资源
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    def register_user(self, username: str, email: str, password: str, 
                     full_name: str = None, phone: str = None, 
                     department: str = None, role: str = 'user') -> Dict[str, Any]:
        """
        注册新用户
        
        参数:
            username: 用户名
            email: 邮箱
            password: 密码
            full_name: 全名
            phone: 电话
            department: 部门
            role: 角色 (admin/manager/user)
        
        返回:
            Dict: 包含success, message等字段的结果字典
        """
        
        # 验证输入
        if not username or not email or not password:
            return {'success': False, 'message': '用户名、邮箱和密码不能为空'}
        
        if len(password) < 6:
            return {'success': False, 'message': '密码长度至少6位'}
        
        if not self._validate_email(email):
            return {'success': False, 'message': '邮箱格式不正确'}
        
        if phone and not self._validate_phone(phone):
            return {'success': False, 'message': '手机号格式不正确'}
        
        connection = None
        cursor = None
        
        try:
            # 建立数据库连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 检查用户名和邮箱是否已存在
            cursor.execute("""
                SELECT COUNT(*) as count 
                FROM system_users 
                WHERE username = %s OR email = %s
            """, (username, email))
            
            exists = cursor.fetchone()['count']
            if exists > 0:
                return {'success': False, 'message': '用户名或邮箱已存在'}
            
            # 哈希密码
            password_hash = self._hash_password(password)
            
            # 插入用户
            cursor.execute("""
                INSERT INTO system_users 
                (username, email, password_hash, full_name, phone, department, role)
                VALUES (%s, %s, %s, %s, %s, %s, %s)
            """, (username, email, password_hash, full_name, phone, department, role))
            
            # 获取自增主键
            user_id = cursor.lastrowid
            
            # 记录审计日志
            self._log_audit(user_id, 'REGISTER', f'用户注册: {username}')
            
            # 提交事务
            connection.commit()
            
            # 返回成功结果
            return {
                'success': True,
                'message': '用户注册成功',
                'user_id': user_id,
                'username': username
            }
            
        except pymysql.IntegrityError as e:
            # 完整性错误(如唯一约束违反)
            if connection:
                connection.rollback()
            return {'success': False, 'message': '用户已存在'}
            
        except Exception as e:
            # 其他异常
            if connection:
                connection.rollback()
            return {'success': False, 'message': f'注册失败: {str(e)}'}
            
        finally:
            # 确保资源被关闭
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    def login(self, username: str, password: str, ip: str = None, 
              user_agent: str = None) -> Dict[str, Any]:
        """
        用户登录
        
        参数:
            username: 用户名
            password: 密码
            ip: IP地址(可选)
            user_agent: 用户代理(可选)
        
        返回:
            Dict: 包含登录结果的信息
        """
        
        connection = None
        cursor = None
        
        try:
            # 建立连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 查找用户
            cursor.execute("""
                SELECT user_id, username, password_hash, status, login_attempts, role
                FROM system_users 
                WHERE username = %s AND status != 'inactive'
            """, (username,))
            
            user = cursor.fetchone()
            
            # 用户不存在
            if not user:
                return {'success': False, 'message': '用户不存在或已被禁用'}
            
            # 检查账户是否被锁定
            if user['status'] == 'locked':
                return {'success': False, 'message': '账户已被锁定,请联系管理员'}
            
            # 验证密码
            password_hash = self._hash_password(password)
            
            if user['password_hash'] != password_hash:
                # 密码错误,增加登录尝试次数
                cursor.execute("""
                    UPDATE system_users 
                    SET login_attempts = login_attempts + 1
                    WHERE user_id = %s
                """, (user['user_id'],))
                
                # 如果尝试次数超过5次,锁定账户
                if user['login_attempts'] + 1 >= 5:
                    cursor.execute("""
                        UPDATE system_users 
                        SET status = 'locked',
                            login_attempts = login_attempts + 1
                        WHERE user_id = %s
                    """, (user['user_id'],))
                    connection.commit()
                    return {'success': False, 'message': '密码错误次数过多,账户已锁定'}
                
                # 提交密码错误的更新
                connection.commit()
                return {'success': False, 'message': '密码错误'}
            
            # 登录成功,重置尝试次数,更新最后登录时间
            cursor.execute("""
                UPDATE system_users 
                SET last_login = CURRENT_TIMESTAMP,
                    login_attempts = 0
                WHERE user_id = %s
            """, (user['user_id'],))
            
            # 记录审计日志
            self._log_audit(
                user['user_id'], 
                'LOGIN', 
                f'用户登录: {username}',
                ip,
                user_agent
            )
            
            # 提交登录成功的更新
            connection.commit()
            
            # 返回成功结果
            return {
                'success': True,
                'message': '登录成功',
                'user_id': user['user_id'],
                'username': user['username'],
                'role': user['role']
            }
            
        except Exception as e:
            # 异常处理
            if connection:
                connection.rollback()
            return {'success': False, 'message': f'登录失败: {str(e)}'}
            
        finally:
            # 关闭资源
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    def get_user(self, user_id: int) -> Dict[str, Any]:
        """
        获取用户信息
        
        参数:
            user_id: 用户ID
        
        返回:
            Dict: 包含用户信息和操作结果
        """
        
        connection = None
        cursor = None
        
        try:
            # 建立连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 查询用户信息
            cursor.execute("""
                SELECT 
                    user_id, username, email, full_name, phone, 
                    department, role, status, last_login, created_at
                FROM system_users 
                WHERE user_id = %s
            """, (user_id,))
            
            user = cursor.fetchone()
            
            # 用户不存在
            if not user:
                return {'success': False, 'message': '用户不存在'}
            
            # 隐藏敏感信息
            if 'password_hash' in user:
                del user['password_hash']
            
            # 返回成功结果
            return {
                'success': True,
                'message': '获取成功',
                'user': user
            }
            
        except Exception as e:
            # 异常处理
            return {'success': False, 'message': f'获取用户失败: {str(e)}'}
            
        finally:
            # 关闭资源
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    def update_user(self, user_id: int, **kwargs) -> Dict[str, Any]:
        """
        更新用户信息
        
        参数:
            user_id: 用户ID
            **kwargs: 要更新的字段和值
        
        返回:
            Dict: 更新结果
        """
        
        # 允许更新的字段
        allowed_fields = ['full_name', 'phone', 'department', 'role', 'status']
        update_fields = {}
        
        # 过滤允许更新的字段
        for field in allowed_fields:
            if field in kwargs and kwargs[field] is not None:
                update_fields[field] = kwargs[field]
        
        # 没有要更新的字段
        if not update_fields:
            return {'success': False, 'message': '没有要更新的字段'}
        
        # 特殊验证
        if 'phone' in update_fields and not self._validate_phone(update_fields['phone']):
            return {'success': False, 'message': '手机号格式不正确'}
        
        if 'role' in update_fields and update_fields['role'] not in ['admin', 'manager', 'user']:
            return {'success': False, 'message': '角色不正确'}
        
        connection = None
        cursor = None
        
        try:
            # 建立连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 构建更新语句
            set_clause = ', '.join([f"{field} = %s" for field in update_fields.keys()])
            values = list(update_fields.values())
            values.append(user_id)
            
            # 执行更新
            cursor.execute(f"""
                UPDATE system_users 
                SET {set_clause}
                WHERE user_id = %s
            """, values)
            
            # 检查是否更新成功
            if cursor.rowcount == 0:
                return {'success': False, 'message': '用户不存在或没有变化'}
            
            # 记录审计日志
            changes = ', '.join([f"{k}: {v}" for k, v in update_fields.items()])
            self._log_audit(user_id, 'UPDATE', f'更新用户信息: {changes}')
            
            # 提交事务
            connection.commit()
            
            # 返回成功结果
            return {
                'success': True,
                'message': '更新成功',
                'affected_rows': cursor.rowcount
            }
            
        except Exception as e:
            # 异常处理
            if connection:
                connection.rollback()
            return {'success': False, 'message': f'更新失败: {str(e)}'}
            
        finally:
            # 关闭资源
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    def change_password(self, user_id: int, old_password: str, 
                       new_password: str) -> Dict[str, Any]:
        """
        修改密码
        
        参数:
            user_id: 用户ID
            old_password: 旧密码
            new_password: 新密码
        
        返回:
            Dict: 修改结果
        """
        
        # 验证新密码长度
        if len(new_password) < 6:
            return {'success': False, 'message': '新密码长度至少6位'}
        
        connection = None
        cursor = None
        
        try:
            # 建立连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 获取当前密码哈希
            cursor.execute("""
                SELECT password_hash 
                FROM system_users 
                WHERE user_id = %s
            """, (user_id,))
            
            result = cursor.fetchone()
            if not result:
                return {'success': False, 'message': '用户不存在'}
            
            # 验证旧密码
            old_hash = self._hash_password(old_password)
            if result['password_hash'] != old_hash:
                return {'success': False, 'message': '旧密码不正确'}
            
            # 更新密码
            new_hash = self._hash_password(new_password)
            cursor.execute("""
                UPDATE system_users 
                SET password_hash = %s,
                    login_attempts = 0
                WHERE user_id = %s
            """, (new_hash, user_id))
            
            # 记录审计日志
            self._log_audit(user_id, 'CHANGE_PASSWORD', '修改密码')
            
            # 提交事务
            connection.commit()
            
            # 返回成功结果
            return {
                'success': True,
                'message': '密码修改成功'
            }
            
        except Exception as e:
            # 异常处理
            if connection:
                connection.rollback()
            return {'success': False, 'message': f'修改密码失败: {str(e)}'}
            
        finally:
            # 关闭资源
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    def list_users(self, page: int = 1, page_size: int = 10, 
                  filters: Dict = None) -> Dict[str, Any]:
        """
        获取用户列表(分页)
        
        参数:
            page: 页码
            page_size: 每页大小
            filters: 过滤条件
        
        返回:
            Dict: 用户列表和分页信息
        """
        
        connection = None
        cursor = None
        
        try:
            # 建立连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 构建查询条件
            where_clauses = []
            params = []
            
            if filters:
                if 'username' in filters and filters['username']:
                    where_clauses.append("username LIKE %s")
                    params.append(f"%{filters['username']}%")
                
                if 'email' in filters and filters['email']:
                    where_clauses.append("email LIKE %s")
                    params.append(f"%{filters['email']}%")
                
                if 'role' in filters and filters['role']:
                    where_clauses.append("role = %s")
                    params.append(filters['role'])
                
                if 'status' in filters and filters['status']:
                    where_clauses.append("status = %s")
                    params.append(filters['status'])
            
            # 构建WHERE子句
            where_sql = f"WHERE {' AND '.join(where_clauses)}" if where_clauses else ""
            
            # 查询总数
            count_sql = f"SELECT COUNT(*) as total FROM system_users {where_sql}"
            cursor.execute(count_sql, params)
            total = cursor.fetchone()['total']
            
            # 计算分页信息
            total_pages = (total + page_size - 1) // page_size
            offset = (page - 1) * page_size
            
            # 查询数据
            query_sql = f"""
                SELECT 
                    user_id, username, email, full_name, phone, 
                    department, role, status, last_login, created_at
                FROM system_users 
                {where_sql}
                ORDER BY created_at DESC
                LIMIT %s OFFSET %s
            """
            
            # 添加分页参数
            params.extend([page_size, offset])
            cursor.execute(query_sql, params)
            
            # 获取查询结果
            users = cursor.fetchall()
            
            # 隐藏敏感信息
            for user in users:
                if 'password_hash' in user:
                    del user['password_hash']
            
            # 返回成功结果
            return {
                'success': True,
                'message': '获取成功',
                'data': {
                    'users': users,
                    'pagination': {
                        'page': page,
                        'page_size': page_size,
                        'total': total,
                        'total_pages': total_pages
                    }
                }
            }
            
        except Exception as e:
            # 异常处理
            return {'success': False, 'message': f'获取用户列表失败: {str(e)}'}
            
        finally:
            # 关闭资源
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    def get_audit_logs(self, user_id: int = None, action_type: str = None,
                      start_date: str = None, end_date: str = None,
                      page: int = 1, page_size: int = 20) -> Dict[str, Any]:
        """
        获取审计日志
        
        参数:
            user_id: 用户ID(可选)
            action_type: 操作类型(可选)
            start_date: 开始日期(可选)
            end_date: 结束日期(可选)
            page: 页码
            page_size: 每页大小
        
        返回:
            Dict: 审计日志和分页信息
        """
        
        connection = None
        cursor = None
        
        try:
            # 建立连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 构建查询条件
            where_clauses = []
            params = []
            
            if user_id:
                where_clauses.append("al.user_id = %s")
                params.append(user_id)
            
            if action_type:
                where_clauses.append("al.action_type = %s")
                params.append(action_type)
            
            if start_date:
                where_clauses.append("al.created_at >= %s")
                params.append(start_date)
            
            if end_date:
                where_clauses.append("al.created_at <= %s")
                params.append(end_date)
            
            # 构建WHERE子句
            where_sql = f"WHERE {' AND '.join(where_clauses)}" if where_clauses else ""
            
            # 查询总数
            count_sql = f"""
                SELECT COUNT(*) as total 
                FROM user_audit_log al
                {where_sql}
            """
            cursor.execute(count_sql, params)
            total = cursor.fetchone()['total']
            
            # 计算分页信息
            total_pages = (total + page_size - 1) // page_size
            offset = (page - 1) * page_size
            
            # 查询数据
            query_sql = f"""
                SELECT 
                    al.log_id,
                    al.user_id,
                    u.username,
                    al.action_type,
                    al.action_details,
                    al.ip_address,
                    al.user_agent,
                    al.created_at
                FROM user_audit_log al
                LEFT JOIN system_users u ON al.user_id = u.user_id
                {where_sql}
                ORDER BY al.created_at DESC
                LIMIT %s OFFSET %s
            """
            
            # 添加分页参数
            params.extend([page_size, offset])
            cursor.execute(query_sql, params)
            
            # 获取查询结果
            logs = cursor.fetchall()
            
            # 返回成功结果
            return {
                'success': True,
                'message': '获取成功',
                'data': {
                    'logs': logs,
                    'pagination': {
                        'page': page,
                        'page_size': page_size,
                        'total': total,
                        'total_pages': total_pages
                    }
                }
            }
            
        except Exception as e:
            # 异常处理
            return {'success': False, 'message': f'获取审计日志失败: {str(e)}'}
            
        finally:
            # 关闭资源
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    def export_users_to_csv(self, filename: str = 'users_export.csv') -> Dict[str, Any]:
        """
        导出用户数据到CSV
        
        参数:
            filename: 导出文件名
        
        返回:
            Dict: 导出结果
        """
        
        connection = None
        cursor = None
        
        try:
            # 建立连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 查询所有用户
            cursor.execute("""
                SELECT 
                    user_id, username, email, full_name, phone, 
                    department, role, status, 
                    DATE_FORMAT(last_login, '%%Y-%%m-%%d %%H:%%i:%%s') as last_login,
                    DATE_FORMAT(created_at, '%%Y-%%m-%%d %%H:%%i:%%s') as created_at
                FROM system_users 
                ORDER BY user_id
            """)
            
            users = cursor.fetchall()
            
            # 检查是否有数据
            if not users:
                return {'success': False, 'message': '没有用户数据'}
            
            # 写入CSV文件
            with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
                if users:
                    # 获取表头
                    fieldnames = users[0].keys()
                    writer = csv.DictWriter(f, fieldnames=fieldnames)
                    
                    # 写入表头和数据
                    writer.writeheader()
                    writer.writerows(users)
            
            # 记录审计日志
            self._log_audit(None, 'EXPORT', f'导出用户数据到 {filename}')
            
            # 返回成功结果
            return {
                'success': True,
                'message': f'导出成功,共 {len(users)} 条记录',
                'filename': filename,
                'count': len(users)
            }
            
        except Exception as e:
            # 异常处理
            return {'success': False, 'message': f'导出失败: {str(e)}'}
            
        finally:
            # 关闭资源
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    def delete_user(self, user_id: int, current_user_id: int) -> Dict[str, Any]:
        """
        删除用户(软删除)
        
        参数:
            user_id: 要删除的用户ID
            current_user_id: 当前操作用户ID
        
        返回:
            Dict: 删除结果
        """
        
        connection = None
        cursor = None
        
        try:
            # 建立连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 检查用户是否存在
            cursor.execute("SELECT username FROM system_users WHERE user_id = %s", (user_id,))
            user = cursor.fetchone()
            
            if not user:
                return {'success': False, 'message': '用户不存在'}
            
            # 软删除:将状态设置为inactive
            cursor.execute("""
                UPDATE system_users 
                SET status = 'inactive',
                    updated_at = CURRENT_TIMESTAMP
                WHERE user_id = %s
            """, (user_id,))
            
            # 记录审计日志
            self._log_audit(current_user_id, 'DELETE', f'删除用户: {user["username"]} (ID: {user_id})')
            
            # 提交事务
            connection.commit()
            
            # 返回成功结果
            return {
                'success': True,
                'message': '用户删除成功',
                'affected_rows': cursor.rowcount
            }
            
        except Exception as e:
            # 异常处理
            if connection:
                connection.rollback()
            return {'success': False, 'message': f'删除用户失败: {str(e)}'}
            
        finally:
            # 关闭资源
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    def search_users(self, keyword: str, field: str = 'username', 
                    page: int = 1, page_size: int = 10) -> Dict[str, Any]:
        """
        搜索用户
        
        参数:
            keyword: 搜索关键词
            field: 搜索字段 (username/email/full_name/phone)
            page: 页码
            page_size: 每页大小
        
        返回:
            Dict: 搜索结果
        """
        
        # 验证搜索字段
        valid_fields = ['username', 'email', 'full_name', 'phone']
        if field not in valid_fields:
            return {'success': False, 'message': f'搜索字段无效,可选: {", ".join(valid_fields)}'}
        
        connection = None
        cursor = None
        
        try:
            # 建立连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 构建搜索条件
            if field in ['full_name', 'phone']:
                # 这些字段可能为NULL
                where_sql = f"WHERE ({field} LIKE %s OR {field} IS NULL)"
            else:
                where_sql = f"WHERE {field} LIKE %s"
            
            # 查询总数
            count_sql = f"SELECT COUNT(*) as total FROM system_users {where_sql}"
            cursor.execute(count_sql, (f"%{keyword}%",))
            total = cursor.fetchone()['total']
            
            # 计算分页信息
            total_pages = (total + page_size - 1) // page_size
            offset = (page - 1) * page_size
            
            # 查询数据
            query_sql = f"""
                SELECT 
                    user_id, username, email, full_name, phone, 
                    department, role, status, last_login, created_at
                FROM system_users 
                {where_sql}
                ORDER BY created_at DESC
                LIMIT %s OFFSET %s
            """
            
            # 执行查询
            cursor.execute(query_sql, (f"%{keyword}%", page_size, offset))
            users = cursor.fetchall()
            
            # 隐藏敏感信息
            for user in users:
                if 'password_hash' in user:
                    del user['password_hash']
            
            # 返回成功结果
            return {
                'success': True,
                'message': '搜索成功',
                'data': {
                    'users': users,
                    'pagination': {
                        'page': page,
                        'page_size': page_size,
                        'total': total,
                        'total_pages': total_pages
                    }
                }
            }
            
        except Exception as e:
            # 异常处理
            return {'success': False, 'message': f'搜索失败: {str(e)}'}
            
        finally:
            # 关闭资源
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    def get_user_statistics(self) -> Dict[str, Any]:
        """
        获取用户统计信息
        
        返回:
            Dict: 统计信息
        """
        
        connection = None
        cursor = None
        
        try:
            # 建立连接
            connection = self._get_connection()
            cursor = connection.cursor()
            
            # 查询各种统计信息
            cursor.execute("""
                SELECT 
                    COUNT(*) as total_users,
                    SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active_users,
                    SUM(CASE WHEN status = 'inactive' THEN 1 ELSE 0 END) as inactive_users,
                    SUM(CASE WHEN status = 'locked' THEN 1 ELSE 0 END) as locked_users,
                    SUM(CASE WHEN role = 'admin' THEN 1 ELSE 0 END) as admin_users,
                    SUM(CASE WHEN role = 'manager' THEN 1 ELSE 0 END) as manager_users,
                    SUM(CASE WHEN role = 'user' THEN 1 ELSE 0 END) as normal_users,
                    COUNT(DISTINCT department) as departments_count,
                    DATE(created_at) as date,
                    COUNT(*) as daily_registrations
                FROM system_users
                GROUP BY DATE(created_at)
                ORDER BY date DESC
                LIMIT 30
            """)
            
            stats = cursor.fetchall()
            
            # 返回成功结果
            return {
                'success': True,
                'message': '获取统计信息成功',
                'data': stats
            }
            
        except Exception as e:
            # 异常处理
            return {'success': False, 'message': f'获取统计信息失败: {str(e)}'}
            
        finally:
            # 关闭资源
            if cursor:
                cursor.close()
            if connection:
                connection.close()


# 使用示例
def user_management_demo():
    """用户管理系统演示"""
    
    print("=== 用户管理系统演示 ===")
    
    # 创建系统实例
    system = UserManagementSystem(
        host='localhost',
        user='root',
        password='your_password',
        database='pymysql_tutorial'
    )
    
    # 1. 注册用户
    print("\n1. 注册用户")
    result = system.register_user(
        username='admin',
        email='admin@example.com',
        password='admin123',
        full_name='系统管理员',
        phone='13800138000',
        department='IT',
        role='admin'
    )
    print(f"注册结果: {result}")
    
    # 2. 用户登录
    print("\n2. 用户登录")
    result = system.login('admin', 'admin123')
    print(f"登录结果: {result}")
    
    if result['success']:
        user_id = result['user_id']
        
        # 3. 获取用户信息
        print("\n3. 获取用户信息")
        result = system.get_user(user_id)
        if result['success']:
            print(f"用户信息: {result['user']}")
        
        # 4. 更新用户信息
        print("\n4. 更新用户信息")
        result = system.update_user(user_id, department='技术部', phone='13900139000')
        print(f"更新结果: {result}")
        
        # 5. 修改密码
        print("\n5. 修改密码")
        result = system.change_password(user_id, 'admin123', 'newpassword123')
        print(f"修改密码结果: {result}")
        
        # 6. 获取用户列表
        print("\n6. 获取用户列表")
        result = system.list_users(page=1, page_size=5)
        if result['success']:
            print(f"用户列表: 第{result['data']['pagination']['page']}页,"
                  f"共{result['data']['pagination']['total']}条记录")
            for user in result['data']['users']:
                print(f"  {user['username']} ({user['role']}) - {user['email']}")
        
        # 7. 搜索用户
        print("\n7. 搜索用户")
        result = system.search_users('admin', field='username')
        if result['success']:
            print(f"搜索到 {result['data']['pagination']['total']} 条记录")
            for user in result['data']['users']:
                print(f"  {user['username']} - {user['email']}")
        
        # 8. 获取审计日志
        print("\n8. 获取审计日志")
        result = system.get_audit_logs(page=1, page_size=5)
        if result['success']:
            print(f"审计日志: 共{result['data']['pagination']['total']}条记录")
            for log in result['data']['logs']:
                print(f"  {log['created_at']} - {log['username']} - {log['action_type']}")
        
        # 9. 获取用户统计
        print("\n9. 获取用户统计信息")
        result = system.get_user_statistics()
        if result['success']:
            print(f"统计信息获取成功,共 {len(result['data'])} 天的数据")
        
        # 10. 导出用户数据
        print("\n10. 导出用户数据到CSV")
        result = system.export_users_to_csv('users_export_demo.csv')
        print(f"导出结果: {result}")
        
        # 11. 注册更多用户用于测试
        print("\n11. 注册更多测试用户")
        test_users = [
            ('user1', 'user1@example.com', 'user123', '张三', '13800138001', '销售部', 'user'),
            ('user2', 'user2@example.com', 'user123', '李四', '13800138002', '市场部', 'user'),
            ('user3', 'user3@example.com', 'user123', '王五', '13800138003', '技术部', 'manager'),
        ]
        
        for user_data in test_users:
            result = system.register_user(*user_data)
            if result['success']:
                print(f"  注册成功: {user_data[0]}")
            else:
                print(f"  注册失败: {user_data[0]} - {result['message']}")
    
    print("\n演示完成!")


# 主程序入口
if __name__ == "__main__":
    # 运行演示
    user_management_demo()
    
    # 也可以单独测试某个功能
    # 例如,只创建系统实例但不运行演示:
    # system = UserManagementSystem(
    #     host='localhost',
    #     user='root',
    #     password='your_password',
    #     database='pymysql_tutorial'
    # )

第9章代码功能总结

这个完整的用户管理系统包含以下主要功能:

1. 用户管理核心功能

  • 用户注册(带输入验证)
  • 用户登录(带密码验证和账户锁定机制)
  • 获取用户信息
  • 更新用户信息
  • 修改密码
  • 软删除用户

2. 查询和搜索功能

  • 分页获取用户列表
  • 多条件搜索用户
  • 按字段搜索(用户名、邮箱、姓名、电话)

3. 审计日志功能

  • 记录所有重要操作
  • 查看审计日志
  • 按条件筛选审计日志

4. 数据导出功能

  • 导出用户数据到CSV文件
  • 支持UTF-8编码

5. 统计功能

  • 获取用户统计信息
  • 按日期统计注册人数
  • 按状态和角色统计

6. 安全特性

  • 密码哈希存储
  • 登录尝试次数限制
  • 账户锁定机制
  • SQL注入防护

7. 数据验证

  • 邮箱格式验证
  • 手机号格式验证
  • 密码强度验证
  • 输入数据验证

8. 数据库设计

  • 用户表(system_users)
  • 审计日志表(user_audit_log)
  • 合理的索引设计
  • 外键约束

使用方法:

  1. 初始化系统
python 复制代码
system = UserManagementSystem(
    host='localhost',
    user='root',
    password='your_password',
    database='your_database'
)
  1. 基本操作
python 复制代码
# 注册用户
result = system.register_user(username, email, password)

# 用户登录
result = system.login(username, password)

# 获取用户信息
result = system.get_user(user_id)

# 更新用户信息
result = system.update_user(user_id, department='新部门', phone='新电话')

# 修改密码
result = system.change_password(user_id, '旧密码', '新密码')
  1. 高级功能
python 复制代码
# 分页获取用户列表
result = system.list_users(page=1, page_size=10, filters={'role': 'admin'})

# 搜索用户
result = system.search_users('关键词', field='username')

# 获取审计日志
result = system.get_audit_logs(user_id=1, action_type='LOGIN')

# 导出数据
result = system.export_users_to_csv('users.csv')

# 获取统计信息
result = system.get_user_statistics()

这个系统可以直接用于实际项目中,或者作为学习PyMySQL开发的参考示例。

第10章: 性能优化与最佳实践

python 复制代码
"""
开发思路:
1. 查询性能优化
2. 连接管理优化
3. 批量操作优化
4. 错误处理最佳实践
"""

def performance_optimization():
    """性能优化与最佳实践"""
    
    print("=== 性能优化与最佳实践 ===")
    
    # 1. 使用连接池的最佳实践
    print("\n1. 连接池最佳实践")
    
    class OptimizedConnectionPool:
        """优化的连接池"""
        
        def __init__(self, **config):
            self.config = config
            self.pool = []
            self.max_size = 10
            self.min_size = 2
            self.in_use = set()
            self.lock = threading.Lock()
            
            # 初始化最小连接数
            self._initialize_pool()
        
        def _initialize_pool(self):
            """初始化连接池"""
            for _ in range(self.min_size):
                conn = self._create_connection()
                if conn:
                    self.pool.append(conn)
        
        def _create_connection(self):
            """创建新连接"""
            try:
                config = self.config.copy()
                config.update({
                    'autocommit': True,  # 根据需求设置
                    'connect_timeout': 5,
                    'read_timeout': 30,
                    'write_timeout': 30,
                })
                return pymysql.connect(**config)
            except Exception as e:
                print(f"创建连接失败: {e}")
                return None
        
        def get_connection(self, timeout=5):
            """获取连接(带超时)"""
            import time
            
            start_time = time.time()
            
            while time.time() - start_time < timeout:
                with self.lock:
                    # 从池中获取可用连接
                    for i, conn in enumerate(self.pool):
                        if id(conn) not in self.in_use:
                            try:
                                conn.ping(reconnect=False)
                                self.in_use.add(id(conn))
                                return conn
                            except:
                                # 连接不可用,移除
                                try:
                                    conn.close()
                                except:
                                    pass
                                self.pool.pop(i)
                                break
                    
                    # 创建新连接
                    if len(self.pool) < self.max_size:
                        conn = self._create_connection()
                        if conn:
                            self.pool.append(conn)
                            self.in_use.add(id(conn))
                            return conn
                
                # 等待一段时间再重试
                time.sleep(0.1)
            
            raise TimeoutError("获取连接超时")
        
        def return_connection(self, conn):
            """归还连接"""
            with self.lock:
                if id(conn) in self.in_use:
                    self.in_use.remove(id(conn))
                    
                    # 如果连接池太大,关闭一些连接
                    if len(self.pool) > self.max_size:
                        try:
                            conn.close()
                        except:
                            pass
                        self.pool = [c for c in self.pool if id(c) != id(conn)]
        
        def close_all(self):
            """关闭所有连接"""
            with self.lock:
                for conn in self.pool:
                    try:
                        conn.close()
                    except:
                        pass
                self.pool.clear()
                self.in_use.clear()
    
    # 2. 查询优化技巧
    print("\n2. 查询优化技巧")
    
    def optimized_queries():
        """优化的查询方法"""
        
        connection = None
        cursor = None
        
        try:
            connection = pymysql.connect(
                host='localhost',
                user='root',
                password='your_password',
                database='pymysql_tutorial',
                charset='utf8mb4',
                cursorclass=pymysql.cursors.DictCursor
            )
            
            cursor = connection.cursor()
            
            # 技巧1: 只选择需要的列
            print("技巧1: 只选择需要的列")
            
            # 不好的做法
            # cursor.execute("SELECT * FROM users WHERE id = 1")
            
            # 好的做法
            cursor.execute("""
                SELECT username, email, created_at 
                FROM users 
                WHERE id = 1
            """)
            print(f"  只查询必要字段,减少数据传输量")
            
            # 技巧2: 使用索引优化查询
            print("\n技巧2: 使用索引优化")
            
            # 查看表索引
            cursor.execute("SHOW INDEX FROM users")
            indexes = cursor.fetchall()
            print(f"  users表索引:")
            for idx in indexes:
                if idx['Key_name'] != 'PRIMARY':
                    print(f"    索引: {idx['Key_name']}, 字段: {idx['Column_name']}")
            
            # 技巧3: 避免SELECT * 在大表中
            print("\n技巧3: 分页查询避免全表扫描")
            
            page_size = 100
            last_id = 0
            
            while True:
                cursor.execute("""
                    SELECT id, username, email
                    FROM users
                    WHERE id > %s
                    ORDER BY id
                    LIMIT %s
                """, (last_id, page_size))
                
                batch = cursor.fetchall()
                if not batch:
                    break
                
                last_id = batch[-1]['id']
                print(f"  处理到ID: {last_id}, 本批 {len(batch)} 条记录")
            
            # 技巧4: 使用EXPLAIN分析查询
            print("\n技巧4: 使用EXPLAIN分析查询")
            
            cursor.execute("EXPLAIN SELECT * FROM users WHERE username = '张三'")
            explain_result = cursor.fetchone()
            
            print(f"  查询类型: {explain_result['type']}")
            print(f"  使用的索引: {explain_result['key']}")
            print(f"  扫描行数: {explain_result['rows']}")
            
            # 技巧5: 批量插入优化
            print("\n技巧5: 批量插入优化")
            
            # 使用executemany
            data = [(f'batch_user_{i}', f'batch{i}@example.com', f'hash_{i}') 
                    for i in range(100)]
            
            start_time = time.time()
            
            # 单条插入(慢)
            # for item in data[:10]:  # 只测试10条
            #     cursor.execute("INSERT INTO users (username, email, password_hash) VALUES (%s, %s, %s)", item)
            
            # 批量插入(快)
            cursor.executemany(
                "INSERT INTO users (username, email, password_hash) VALUES (%s, %s, %s)",
                data[:10]  # 只测试10条
            )
            
            connection.commit()
            
            duration = time.time() - start_time
            print(f"  批量插入10条用时: {duration:.3f}秒")
            
            # 技巧6: 使用预处理语句
            print("\n技巧6: 预处理语句重用")
            
            prep_sql = "SELECT * FROM users WHERE age > %s AND salary > %s LIMIT %s"
            
            # 多次执行相同的预处理语句
            queries = [
                (25, 5000, 10),
                (30, 8000, 5),
                (35, 10000, 3)
            ]
            
            for params in queries:
                cursor.execute(prep_sql, params)
                result = cursor.fetchall()
                print(f"  年龄>{params[0]},薪水>{params[1]}: {len(result)}条记录")
            
            # 技巧7: 适当的索引策略
            print("\n技巧7: 复合索引优化")
            
            # 创建复合索引
            cursor.execute("""
                CREATE INDEX IF NOT EXISTS idx_age_salary 
                ON users(age, salary)
            """)
            
            # 复合索引可以优化以下查询
            cursor.execute("EXPLAIN SELECT * FROM users WHERE age > 25 AND salary > 5000")
            explain_compound = cursor.fetchone()
            print(f"  复合索引使用情况: {explain_compound['key']}")
            
            connection.commit()
            
        except Exception as e:
            print(f"查询优化错误: {e}")
            
        finally:
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    optimized_queries()
    
    # 3. 事务优化
    print("\n3. 事务优化策略")
    
    def transaction_optimization():
        """事务优化"""
        
        connection = None
        cursor = None
        
        try:
            connection = pymysql.connect(
                host='localhost',
                user='root',
                password='your_password',
                database='pymysql_tutorial',
                charset='utf8mb4',
                cursorclass=pymysql.cursors.DictCursor,
                autocommit=False
            )
            
            cursor = connection.cursor()
            
            # 策略1: 控制事务大小
            print("策略1: 控制事务大小")
            
            batch_size = 100
            total_records = 1000
            
            try:
                connection.begin()
                
                for i in range(0, total_records, batch_size):
                    # 分批处理
                    data = [(f'opt_user_{i+j}', f'opt{i+j}@example.com') 
                           for j in range(min(batch_size, total_records - i))]
                    
                    cursor.executemany(
                        "INSERT INTO users (username, email, password_hash) VALUES (%s, %s, 'hash')",
                        data
                    )
                    
                    # 每批提交一次
                    connection.commit()
                    connection.begin()  # 开始新的事务
                    
                    print(f"  已处理 {i + len(data)} 条记录")
                
                connection.commit()
                print("  批量插入完成")
                
            except Exception as e:
                connection.rollback()
                print(f"  事务失败: {e}")
            
            # 策略2: 选择合适的隔离级别
            print("\n策略2: 隔离级别选择")
            
            isolation_levels = {
                'READ UNCOMMITTED': '性能最好,但可能脏读',
                'READ COMMITTED': '平衡性能和数据一致性',
                'REPEATABLE READ': 'MySQL默认,保证可重复读',
                'SERIALIZABLE': '最严格,性能最差'
            }
            
            for level, desc in isolation_levels.items():
                print(f"  {level}: {desc}")
            
            # 根据场景选择
            print("\n  推荐场景:")
            print("  - 报表查询: READ COMMITTED")
            print("  - 财务交易: REPEATABLE READ 或 SERIALIZABLE")
            print("  - 数据分析: READ UNCOMMITTED")
            
            # 策略3: 避免长事务
            print("\n策略3: 避免长事务")
            
            # 设置事务超时
            cursor.execute("SET SESSION innodb_lock_wait_timeout = 5")
            cursor.execute("SET SESSION max_execution_time = 10000")  # 10秒
            
            print("  已设置事务超时为10秒")
            
            # 策略4: 使用保存点
            print("\n策略4: 使用保存点进行部分回滚")
            
            try:
                connection.begin()
                
                # 操作1
                cursor.execute("UPDATE users SET salary = salary + 100 WHERE id = 1")
                print("  操作1完成")
                
                # 设置保存点
                cursor.execute("SAVEPOINT sp1")
                
                # 操作2(可能失败)
                try:
                    cursor.execute("UPDATE non_existent_table SET col = 1")
                except:
                    # 回滚到保存点
                    cursor.execute("ROLLBACK TO SAVEPOINT sp1")
                    print("  操作2失败,回滚到保存点")
                
                # 操作3
                cursor.execute("UPDATE users SET salary = salary + 200 WHERE id = 2")
                print("  操作3完成")
                
                connection.commit()
                print("  事务提交成功")
                
            except Exception as e:
                connection.rollback()
                print(f"  事务失败: {e}")
            
        except Exception as e:
            print(f"事务优化错误: {e}")
            
        finally:
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    transaction_optimization()
    
    # 4. 连接管理优化
    print("\n4. 连接管理优化")
    
    def connection_management():
        """连接管理优化"""
        
        # 使用上下文管理器确保连接正确关闭
        class DatabaseContext:
            """数据库上下文管理器"""
            
            def __init__(self, **config):
                self.config = config
                self.connection = None
            
            def __enter__(self):
                self.connection = pymysql.connect(**self.config)
                return self.connection
            
            def __exit__(self, exc_type, exc_val, exc_tb):
                if self.connection:
                    if exc_type:  # 有异常发生
                        self.connection.rollback()
                    else:
                        self.connection.commit()
                    self.connection.close()
        
        # 使用示例
        print("使用上下文管理器:")
        
        db_config = {
            'host': 'localhost',
            'user': 'root',
            'password': 'your_password',
            'database': 'pymysql_tutorial',
            'charset': 'utf8mb4',
            'cursorclass': pymysql.cursors.DictCursor,
            'autocommit': False
        }
        
        try:
            with DatabaseContext(**db_config) as conn:
                with conn.cursor() as cursor:
                    cursor.execute("SELECT COUNT(*) as count FROM users")
                    result = cursor.fetchone()
                    print(f"  用户总数: {result['count']}")
            
            print("  连接已自动关闭")
            
        except Exception as e:
            print(f"  数据库操作失败: {e}")
        
        # 连接参数优化
        print("\n连接参数优化:")
        
        optimized_config = {
            'host': 'localhost',
            'user': 'root',
            'password': 'your_password',
            'database': 'pymysql_tutorial',
            'charset': 'utf8mb4',
            'cursorclass': pymysql.cursors.DictCursor,
            'autocommit': False,  # 手动控制事务
            'connect_timeout': 10,  # 连接超时
            'read_timeout': 30,     # 读取超时
            'write_timeout': 30,    # 写入超时
            'client_flag': pymysql.constants.CLIENT.MULTI_STATEMENTS,
        }
        
        print(f"  连接超时: {optimized_config['connect_timeout']}秒")
        print(f"  读取超时: {optimized_config['read_timeout']}秒")
        print(f"  写入超时: {optimized_config['write_timeout']}秒")
    
    connection_management()
    
    # 5. 监控和调优
    print("\n5. 数据库监控和调优")
    
    def database_monitoring():
        """数据库监控"""
        
        connection = None
        cursor = None
        
        try:
            connection = pymysql.connect(
                host='localhost',
                user='root',
                password='your_password',
                database='pymysql_tutorial',
                charset='utf8mb4',
                cursorclass=pymysql.cursors.DictCursor
            )
            
            cursor = connection.cursor()
            
            # 监控连接状态
            print("连接状态监控:")
            
            cursor.execute("SHOW STATUS LIKE 'Threads_connected'")
            threads_connected = cursor.fetchone()['Value']
            print(f"  当前连接数: {threads_connected}")
            
            cursor.execute("SHOW STATUS LIKE 'Max_used_connections'")
            max_connections = cursor.fetchone()['Value']
            print(f"  历史最大连接数: {max_connections}")
            
            cursor.execute("SHOW VARIABLES LIKE 'max_connections'")
            max_allowed = cursor.fetchone()['Value']
            print(f"  最大允许连接数: {max_allowed}")
            
            # 查询性能监控
            print("\n查询性能监控:")
            
            cursor.execute("SHOW STATUS LIKE 'Slow_queries'")
            slow_queries = cursor.fetchone()['Value']
            print(f"  慢查询数量: {slow_queries}")
            
            cursor.execute("SHOW VARIABLES LIKE 'long_query_time'")
            long_query_time = cursor.fetchone()['Value']
            print(f"  慢查询阈值: {long_query_time}秒")
            
            # 查看当前运行查询
            print("\n当前运行查询:")
            cursor.execute("""
                SELECT 
                    id,
                    user,
                    host,
                    db,
                    command,
                    time,
                    state,
                    LEFT(info, 100) as query
                FROM information_schema.PROCESSLIST
                WHERE command != 'Sleep'
                ORDER BY time DESC
                LIMIT 5
            """)
            
            running_queries = cursor.fetchall()
            for query in running_queries:
                print(f"  ID: {query['id']}, 用户: {query['user']}, "
                      f"时间: {query['time']}秒, 查询: {query['query']}")
            
            # 表状态监控
            print("\n表状态监控:")
            
            cursor.execute("""
                SELECT 
                    TABLE_NAME,
                    ENGINE,
                    TABLE_ROWS,
                    DATA_LENGTH,
                    INDEX_LENGTH,
                    DATA_FREE,
                    CREATE_TIME,
                    UPDATE_TIME
                FROM information_schema.TABLES
                WHERE TABLE_SCHEMA = DATABASE()
                ORDER BY DATA_LENGTH + INDEX_LENGTH DESC
                LIMIT 5
            """)
            
            table_stats = cursor.fetchall()
            for table in table_stats:
                total_size = (table['DATA_LENGTH'] or 0) + (table['INDEX_LENGTH'] or 0)
                total_mb = total_size / 1024 / 1024
                print(f"  表: {table['TABLE_NAME']}, "
                      f"行数: {table['TABLE_ROWS']:,}, "
                      f"大小: {total_mb:.2f}MB")
            
            # 索引使用统计
            print("\n索引使用统计:")
            
            cursor.execute("""
                SELECT 
                    table_name,
                    index_name,
                    COUNT_READ,
                    COUNT_FETCH,
                    COUNT_INSERT,
                    COUNT_UPDATE,
                    COUNT_DELETE
                FROM information_schema.INDEX_STATISTICS
                WHERE TABLE_SCHEMA = DATABASE()
                ORDER BY COUNT_READ DESC
                LIMIT 5
            """)
            
            try:
                index_stats = cursor.fetchall()
                for idx in index_stats:
                    print(f"  表: {idx['table_name']}, "
                          f"索引: {idx['index_name']}, "
                          f"读取次数: {idx['COUNT_READ']:,}")
            except:
                print("  索引统计信息不可用")
            
        except Exception as e:
            print(f"监控错误: {e}")
            
        finally:
            if cursor:
                cursor.close()
            if connection:
                connection.close()
    
    database_monitoring()
    
    # 6. 最佳实践总结
    print("\n6. PyMySQL最佳实践总结")
    
    best_practices = [
        "1. 始终使用参数化查询,防止SQL注入",
        "2. 使用连接池管理数据库连接",
        "3. 及时关闭游标和连接,释放资源",
        "4. 使用事务确保数据一致性",
        "5. 批量操作使用executemany提高性能",
        "6. 查询时只选择需要的字段",
        "7. 为经常查询的字段创建索引",
        "8. 监控慢查询并优化",
        "9. 使用适当的异常处理",
        "10. 记录重要的数据库操作日志",
        "11. 定期备份重要数据",
        "12. 在生产环境使用读写分离",
        "13. 设置合理的连接超时和查询超时",
        "14. 使用上下文管理器确保资源释放",
        "15. 避免在循环中执行SQL语句"
    ]
    
    print("PyMySQL开发最佳实践:")
    for practice in best_practices:
        print(f"  {practice}")

# 运行性能优化示例
if __name__ == "__main__":
    performance_optimization()

总结

本教程全面涵盖了PyMySQL 1.1.2的所有主要功能,包括:

  1. 基础连接与配置 - 建立数据库连接的基本方法
  2. 数据查询操作 - SELECT查询的各种用法
  3. 数据插入操作 - INSERT操作的多种方式
  4. 数据更新删除 - UPDATE和DELETE操作
  5. 事务处理 - 事务管理和隔离级别
  6. 高级特性 - 存储过程、连接池、预处理语句等
  7. 错误处理 - 异常处理和调试技巧
  8. 实战项目 - 完整的用户管理系统
  9. 性能优化 - 查询优化和最佳实践

每个章节都包含:

  • 完整的SQL语句创建测试数据
  • 详细的文档型注释说明开发思路
  • 逐行代码注释解释每行代码的作用
  • 实际可运行的示例代码

通过本教程,您可以掌握使用PyMySQL进行Python数据库开发的所有必要知识。

相关推荐
sqyno1sky2 小时前
Python数据库操作:SQLAlchemy ORM指南
jvm·数据库·python
互联网科技看点2 小时前
2025-2026年研发管理软件推荐:产品研发全流程一体化靠谱解决方案评测
服务器·数据库·人工智能
Yupureki2 小时前
《Linux系统编程》12.基础IO
linux·运维·c语言·开发语言·数据库·c++
瀚高PG实验室2 小时前
nginx中配置数据库连接
运维·数据库·nginx·瀚高数据库
C++ 老炮儿的技术栈2 小时前
两个线程对socket 进行读和写,需要加锁吗
java·服务器·网络
一个天蝎座 白勺 程序猿2 小时前
Oracle替换工程实践深度解析:从迁移挑战到金仓“零改造”实践
数据库·学习·oracle·kingbasees
belldeep2 小时前
python:spaCy 源代码解析,性能优化方法
python·性能优化·cython·spacy
deephub2 小时前
TPU 架构与 Pallas Kernel 编程入门:从内存层次结构到 FlashAttention
人工智能·python·深度学习·tpu
薛定谔的猫喵喵2 小时前
卸载 Python 3.8 报错 “Could not set file security” 的终极解决方案
开发语言·python