返回博客

49. 数据库操作:SQLite 与 MySQL

49. 数据库操作:SQLite 与 MySQL

第四十九篇:数据库操作——SQLite 与 MySQL

前言

几乎所有的应用都需要持久化存储数据,而数据库是最常见的方案。Python 标准库自带了 SQLite 支持,无需安装任何服务端即可使用;对于生产环境,MySQL 是最流行的关系型数据库之一。本文将系统讲解 SQLite 和 MySQL 的增删改查操作,以及 SQL 注入防护。

SQLite——零配置的轻量级数据库

为什么选 SQLite

┌───────────────────┐     ┌───────────────────┐
│     SQLite         │     │      MySQL        │
├───────────────────┤     ├───────────────────┤
│ 单文件存储          │     │ 服务端 + 客户端    │
│ 零配置,开箱即用    │     │ 需安装配置服务     │
│ 适合小型应用        │     │ 适合中大型应用     │
│ 支持事务            │     │ 支持事务           │
│ 并发读,单写        │     │ 高并发读写         │
└───────────────────┘     └───────────────────┘

SQLite 的核心优势:

  • Python 内置 sqlite3 模块,无需 pip install
  • 整个数据库就是一个 .db 文件,便于备份和迁移
  • 支持 SQL 标准语法、事务、外键
  • 适合原型开发、移动应用、桌面工具

连接数据库与建表

import sqlite3

# 连接数据库(文件不存在则自动创建)
conn = sqlite3.connect('test.db')

# 创建游标对象(执行 SQL 语句)
cursor = conn.cursor()

# 创建学生表
cursor.execute('''
    CREATE TABLE IF NOT EXISTS students (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        age INTEGER,
        grade TEXT DEFAULT 'A',
        email TEXT UNIQUE,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
''')

# 创建课程表(含外键)
cursor.execute('''
    CREATE TABLE IF NOT EXISTS courses (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        student_id INTEGER,
        course_name TEXT NOT NULL,
        score REAL,
        FOREIGN KEY (student_id) REFERENCES students(id)
    )
''')

conn.commit()
print("✓ 表创建成功")

增删改查(CRUD)

import sqlite3

conn = sqlite3.connect('test.db')
cursor = conn.cursor()

# ---------- 增(Create)----------

# 单条插入
cursor.execute(
    "INSERT INTO students (name, age, grade, email) VALUES (?, ?, ?, ?)",
    ("张三", 20, "A", "zhangsan@example.com")
)

# 批量插入
students_data = [
    ("李四", 21, "B", "lisi@example.com"),
    ("王五", 19, "A", "wangwu@example.com"),
    ("赵六", 22, "C", "zhaoliu@example.com"),
    ("钱七", 20, "B", "qianqi@example.com"),
]
cursor.executemany(
    "INSERT INTO students (name, age, grade, email) VALUES (?, ?, ?, ?)",
    students_data
)
conn.commit()
print(f"✓ 插入了 {cursor.rowcount} 条记录")

# ---------- 查(Read)----------

# 查询所有记录
print("\n--- 所有学生 ---")
cursor.execute("SELECT * FROM students")
for row in cursor:
    print(f"ID={row[0]}, 姓名={row[1]}, 年龄={row[2]}, 等级={row[3]}")

# 条件查询
print("\n--- 年龄 >= 20 的学生 ---")
cursor.execute("SELECT name, age, grade FROM students WHERE age >= ?", (20,))
for row in cursor:
    print(f"  {row[0]}: {row[1]}岁, 等级{row[2]}")

# 排序与限制
print("\n--- 按年龄降序前3名 ---")
cursor.execute("SELECT name, age FROM students ORDER BY age DESC LIMIT 3")
for row in cursor:
    print(f"  {row[0]}: {row[1]}岁")

# 聚合查询
print("\n--- 统计信息 ---")
cursor.execute("SELECT COUNT(*), AVG(age), MIN(age), MAX(age) FROM students")
count, avg_age, min_age, max_age = cursor.fetchone()
print(f"  总人数: {count}, 平均年龄: {avg_age:.1f}, 最小: {min_age}, 最大: {max_age}")

# 分组查询
print("\n--- 按等级分组 ---")
cursor.execute("SELECT grade, COUNT(*) as cnt FROM students GROUP BY grade ORDER BY cnt DESC")
for row in cursor:
    print(f"  等级 {row[0]}: {row[1]} 人")

# ---------- 改(Update)----------

cursor.execute(
    "UPDATE students SET grade = ? WHERE name = ?",
    ("A", "赵六")
)
conn.commit()
print(f"\n✓ 更新了 {cursor.rowcount} 条记录")

# ---------- 删(Delete)----------

cursor.execute("DELETE FROM students WHERE name = ?", ("钱七",))
conn.commit()
print(f"✓ 删除了 {cursor.rowcount} 条记录")

conn.close()

事务管理

import sqlite3

conn = sqlite3.connect('test.db')
cursor = conn.cursor()

# 开启事务(sqlite3 默认自动开启事务)
try:
    # 转账:张三给李四转 100 分
    cursor.execute("UPDATE students SET age = age + 1 WHERE name = '张三'")
    cursor.execute("UPDATE students SET age = age - 1 WHERE name = '李四'")
    
    # 模拟异常
    # raise Exception("模拟转账失败")
    
    conn.commit()
    print("✓ 事务提交成功")
except Exception as e:
    conn.rollback()
    print(f"❌ 事务回滚: {e}")
finally:
    conn.close()

使用 Row 工厂(按列名访问)

import sqlite3

conn = sqlite3.connect('test.db')

# 设置行工厂,让查询结果可以通过列名访问
conn.row_factory = sqlite3.Row

cursor = conn.cursor()
cursor.execute("SELECT * FROM students WHERE grade = ?", ("A",))

print("--- 等级 A 的学生 ---")
for row in cursor:
    # 通过列名访问,更直观
    print(f"  {row['name']} (ID: {row['id']}) - {row['age']}岁, 邮箱: {row['email']}")

conn.close()

封装数据库操作类

import sqlite3
from contextlib import contextmanager

class StudentDB:
    """学生数据库操作封装"""
    
    def __init__(self, db_path='students.db'):
        self.db_path = db_path
        
        # 启用外键支持
        with self._get_cursor() as cursor:
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS students (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL,
                    age INTEGER,
                    grade TEXT DEFAULT 'A',
                    email TEXT UNIQUE
                )
            ''')
    
    @contextmanager
    def _get_cursor(self):
        """上下文管理器:自动提交和关闭"""
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        cursor = conn.cursor()
        try:
            yield cursor
            conn.commit()
        except Exception as e:
            conn.rollback()
            raise e
        finally:
            conn.close()
    
    def add_student(self, name, age, grade='A', email=None):
        with self._get_cursor() as cursor:
            cursor.execute(
                "INSERT INTO students (name, age, grade, email) VALUES (?, ?, ?, ?)",
                (name, age, grade, email)
            )
            return cursor.lastrowid
    
    def get_student(self, student_id):
        with self._get_cursor() as cursor:
            cursor.execute("SELECT * FROM students WHERE id = ?", (student_id,))
            row = cursor.fetchone()
            return dict(row) if row else None
    
    def get_all_students(self, grade=None):
        with self._get_cursor() as cursor:
            if grade:
                cursor.execute("SELECT * FROM students WHERE grade = ?", (grade,))
            else:
                cursor.execute("SELECT * FROM students ORDER BY id")
            return [dict(row) for row in cursor]
    
    def update_student(self, student_id, **kwargs):
        if not kwargs:
            return False
        
        fields = []
        values = []
        for key, value in kwargs.items():
            fields.append(f"{key} = ?")
            values.append(value)
        values.append(student_id)
        
        with self._get_cursor() as cursor:
            cursor.execute(
                f"UPDATE students SET {', '.join(fields)} WHERE id = ?",
                values
            )
            return cursor.rowcount > 0
    
    def delete_student(self, student_id):
        with self._get_cursor() as cursor:
            cursor.execute("DELETE FROM students WHERE id = ?", (student_id,))
            return cursor.rowcount > 0
    
    def search_students(self, keyword):
        with self._get_cursor() as cursor:
            cursor.execute(
                "SELECT * FROM students WHERE name LIKE ? OR email LIKE ?",
                (f"%{keyword}%", f"%{keyword}%")
            )
            return [dict(row) for row in cursor]


# 使用示例
if __name__ == '__main__':
    db = StudentDB()
    
    # 批量添加
    ids = []
    for name, age, grade, email in [
        ("孙悟空", 500, "S", "wukong@huaguo.com"),
        ("猪八戒", 300, "B", "bajie@tianzhu.com"),
        ("沙悟净", 400, "A", "wujing@liuhe.com"),
    ]:
        sid = db.add_student(name, age, grade, email)
        ids.append(sid)
        print(f"✓ 添加: {name} -> ID={sid}")
    
    # 查询所有
    print("\n所有学生:")
    for s in db.get_all_students():
        print(f"  {s['id']}. {s['name']} - {s['age']}岁, 等级{s['grade']}")
    
    # 条件查询
    print("\n等级 A 及以上:")
    for s in db.get_all_students(grade='A'):
        print(f"  {s['name']}: {s['age']}岁")
    
    # 搜索
    print("\n搜索 '孙':")
    for s in db.search_students("孙"):
        print(f"  {s['name']}")
    
    # 更新
    db.update_student(ids[1], grade='A', age=350)
    print(f"\n✓ 更新猪八戒: 等级->A, 年龄->350")
    
    # 验证
    student = db.get_student(ids[1])
    print(f"  当前: {student['name']}, {student['age']}岁, 等级{student['grade']}")
    
    # 删除
    db.delete_student(ids[2])
    print(f"✓ 删除沙悟净")

MySQL——生产级关系型数据库

安装与连接

# 安装 MySQL 驱动
pip install PyMySQL
import pymysql

# 连接 MySQL
conn = pymysql.connect(
    host='localhost',
    port=3306,
    user='root',
    password='your_password',
    database='test_db',
    charset='utf8mb4',
    cursorclass=pymysql.cursors.DictCursor  # 返回字典格式
)

# 创建数据库(需要先连接 MySQL 服务器,不指定 database)
# conn = pymysql.connect(host='localhost', user='root', password='your_password')
# cursor.execute("CREATE DATABASE IF NOT EXISTS test_db CHARACTER SET utf8mb4")

cursor = conn.cursor()
print("✓ MySQL 连接成功")

MySQL 建表与操作

import pymysql

def get_connection():
    return pymysql.connect(
        host='localhost',
        port=3306,
        user='root',
        password='your_password',
        database='test_db',
        charset='utf8mb4',
        cursorclass=pymysql.cursors.DictCursor
    )

# 建表
with get_connection() as conn:
    with conn.cursor() as cursor:
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS products (
                id INT AUTO_INCREMENT PRIMARY KEY,
                name VARCHAR(100) NOT NULL,
                price DECIMAL(10, 2) NOT NULL,
                stock INT DEFAULT 0,
                category VARCHAR(50),
                description TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        ''')
    conn.commit()
    print("✓ products 表创建成功")

# 插入数据
with get_connection() as conn:
    with conn.cursor() as cursor:
        # 单条
        cursor.execute(
            "INSERT INTO products (name, price, stock, category) VALUES (%s, %s, %s, %s)",
            ("Python 编程书", 59.90, 100, "图书")
        )
        
        # 批量
        products = [
            ("机械键盘", 299.00, 50, "电子产品"),
            ("无线鼠标", 89.90, 200, "电子产品"),
            ("保温杯", 39.90, 300, "日用品"),
            ("蓝牙耳机", 199.00, 80, "电子产品"),
            ("笔记本支架", 129.00, 150, "办公用品"),
        ]
        cursor.executemany(
            "INSERT INTO products (name, price, stock, category) VALUES (%s, %s, %s, %s)",
            products
        )
    conn.commit()
    print(f"✓ 插入 {len(products) + 1} 条产品")

# 查询数据
with get_connection() as conn:
    with conn.cursor() as cursor:
        # 模糊查询
        cursor.execute(
            "SELECT * FROM products WHERE name LIKE %s OR category = %s ORDER BY price DESC",
            ("%键%", "电子产品")
        )
        print("\n--- 电子产品或名称含'键' ---")
        for row in cursor.fetchall():
            print(f"  {row['name']}: ¥{row['price']}, 库存{row['stock']}")
        
        # 聚合查询
        cursor.execute("""
            SELECT category, COUNT(*) as count, 
                   AVG(price) as avg_price,
                   SUM(stock) as total_stock
            FROM products 
            GROUP BY category 
            ORDER BY avg_price DESC
        """)
        print("\n--- 按分类统计 ---")
        for row in cursor.fetchall():
            print(f"  {row['category']}: {row['count']}个商品, "
                  f"均价¥{row['avg_price']:.2f}, 总库存{row['total_stock']}")
        
        # 分页查询
        page, size = 1, 3
        offset = (page - 1) * size
        cursor.execute(
            "SELECT * FROM products ORDER BY id LIMIT %s OFFSET %s",
            (size, offset)
        )
        print(f"\n--- 第{page}页(每页{size}条)---")
        for row in cursor.fetchall():
            print(f"  [{row['id']}] {row['name']}")

SQL 注入与参数化查询

import pymysql

# ✗ 危险!直接拼接 SQL 字符串 —— 存在 SQL 注入漏洞
def unsafe_login(cursor, username, password):
    # 攻击者输入 username = "' OR '1'='1" 即可绕过验证!
    sql = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
    cursor.execute(sql)  # 危险!
    return cursor.fetchone()

# ✓ 安全!使用参数化查询
def safe_login(cursor, username, password):
    sql = "SELECT * FROM users WHERE username = %s AND password = %s"
    cursor.execute(sql, (username, password))  # 安全
    return cursor.fetchone()


# SQL 注入演示
print("""
=== SQL 注入原理 ===

恶意输入: username = ' OR '1'='1
拼接后 SQL: SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'xxx'
结果: WHERE 条件恒为真,返回所有用户!

修复方案: 使用参数化查询(占位符 ? 或 %s)
数据库驱动会自动转义特殊字符,杜绝注入。
""")

# 参数化查询的正确写法
def search_products(cursor, keyword, min_price=None, max_price=None):
    """安全的参数化查询示例"""
    sql = "SELECT * FROM products WHERE name LIKE %s"
    params = [f"%{keyword}%"]
    
    if min_price is not None:
        sql += " AND price >= %s"
        params.append(min_price)
    
    if max_price is not None:
        sql += " AND price <= %s"
        params.append(max_price)
    
    sql += " ORDER BY price DESC"
    cursor.execute(sql, params)
    return cursor.fetchall()

# 使用
# results = search_products(cursor, "键盘", min_price=100)

MySQL 连接池

from dbutils.pooled_db import PooledDB
import pymysql

# 创建连接池(需安装:pip install dbutils)
pool = PooledDB(
    creator=pymysql,
    maxconnections=10,        # 最大连接数
    mincached=2,              # 初始空闲连接数
    maxcached=5,              # 最大空闲连接数
    host='localhost',
    port=3306,
    user='root',
    password='your_password',
    database='test_db',
    charset='utf8mb4',
    cursorclass=pymysql.cursors.DictCursor
)

def query_products(category):
    """从连接池获取连接执行查询"""
    conn = pool.connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute(
                "SELECT * FROM products WHERE category = %s ORDER BY price",
                (category,)
            )
            return cursor.fetchall()
    finally:
        conn.close()  # 实际是归还到连接池

# 批量查询
for cat in ["电子产品", "图书", "日用品"]:
    results = query_products(cat)
    print(f"{cat}: {len(results)} 件商品")

SQLite 与 MySQL 对比实战

import sqlite3
import time
import os

def benchmark_sqlite():
    """SQLite 批量插入性能测试"""
    
    db_path = 'benchmark.db'
    if os.path.exists(db_path):
        os.remove(db_path)
    
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE benchmark_data (
            id INTEGER PRIMARY KEY,
            value REAL,
            label TEXT
        )
    ''')
    
    # 逐条插入
    start = time.time()
    for i in range(1000):
        cursor.execute(
            "INSERT INTO benchmark_data (value, label) VALUES (?, ?)",
            (i * 0.1, f"item_{i}")
        )
    conn.commit()
    single_time = time.time() - start
    
    # 清空重来
    cursor.execute("DELETE FROM benchmark_data")
    conn.commit()
    
    # 批量插入
    start = time.time()
    cursor.executemany(
        "INSERT INTO benchmark_data (value, label) VALUES (?, ?)",
        [(i * 0.1, f"batch_{i}") for i in range(1000)]
    )
    conn.commit()
    batch_time = time.time() - start
    
    # 验证
    cursor.execute("SELECT COUNT(*) FROM benchmark_data")
    count = cursor.fetchone()[0]
    
    conn.close()
    os.remove(db_path)
    
    print(f"SQLite 性能测试 ({count} 条记录):")
    print(f"  逐条插入: {single_time:.3f}s")
    print(f"  批量插入: {batch_time:.3f}s")
    print(f"  批量快 {single_time / batch_time:.1f} 倍")

benchmark_sqlite()

选择建议

对比维度 SQLite MySQL
安装 内置,零配置 需安装服务端
存储方式 单文件 服务端管理
并发能力 单写入 高并发读写
数据量 GB 级别 TB 级别
适用场景 原型、桌面应用、测试 Web 应用、企业系统
用户管理 无 多用户权限
网络访问 本地文件 TCP 网络
运维成本 几乎为零 需专业运维

实战项目:学生成绩管理系统

import sqlite3

class GradeManagementSystem:
    """学生成绩管理系统"""
    
    def __init__(self, db_path='grades.db'):
        self.db_path = db_path
        self._init_db()
    
    def _init_db(self):
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.executescript('''
            CREATE TABLE IF NOT EXISTS students (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL UNIQUE,
                class TEXT
            );
            
            CREATE TABLE IF NOT EXISTS subjects (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL UNIQUE,
                full_score REAL DEFAULT 100
            );
            
            CREATE TABLE IF NOT EXISTS scores (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                student_id INTEGER NOT NULL,
                subject_id INTEGER NOT NULL,
                score REAL NOT NULL,
                exam_date TEXT DEFAULT (DATE('now')),
                FOREIGN KEY (student_id) REFERENCES students(id),
                FOREIGN KEY (subject_id) REFERENCES subjects(id),
                UNIQUE(student_id, subject_id, exam_date)
            );
        ''')
        conn.commit()
        conn.close()
    
    def add_student(self, name, class_name):
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        try:
            cursor.execute(
                "INSERT INTO students (name, class) VALUES (?, ?)",
                (name, class_name)
            )
            conn.commit()
            print(f"✓ 添加学生: {name} ({class_name})")
            return cursor.lastrowid
        except sqlite3.IntegrityError:
            print(f"⚠ 学生 '{name}' 已存在")
            return None
        finally:
            conn.close()
    
    def add_subject(self, name, full_score=100):
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        try:
            cursor.execute(
                "INSERT INTO subjects (name, full_score) VALUES (?, ?)",
                (name, full_score)
            )
            conn.commit()
            return cursor.lastrowid
        except sqlite3.IntegrityError:
            print(f"⚠ 科目 '{name}' 已存在")
            return None
        finally:
            conn.close()
    
    def record_score(self, student_name, subject_name, score):
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        # 查找学生和科目 ID
        cursor.execute("SELECT id FROM students WHERE name = ?", (student_name,))
        student = cursor.fetchone()
        if not student:
            print(f"❌ 学生 '{student_name}' 不存在")
            conn.close()
            return
        
        cursor.execute("SELECT id, full_score FROM subjects WHERE name = ?", (subject_name,))
        subject = cursor.fetchone()
        if not subject:
            print(f"❌ 科目 '{subject_name}' 不存在")
            conn.close()
            return
        
        if score > subject[1]:
            print(f"❌ 分数 {score} 超过满分 {subject[1]}")
            conn.close()
            return
        
        try:
            cursor.execute(
                "INSERT INTO scores (student_id, subject_id, score) VALUES (?, ?, ?)",
                (student[0], subject[0], score)
            )
            conn.commit()
            print(f"✓ 录入成绩: {student_name} {subject_name} = {score}分")
        except sqlite3.IntegrityError:
            print(f"⚠ {student_name} 的 {subject_name} 成绩已存在,使用更新")
            cursor.execute(
                "UPDATE scores SET score = ? WHERE student_id = ? AND subject_id = ?",
                (score, student[0], subject[0])
            )
            conn.commit()
        finally:
            conn.close()
    
    def get_student_report(self, student_name):
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        cursor = conn.cursor()
        
        cursor.execute("""
            SELECT s.name AS student, s.class,
                   sub.name AS subject, sc.score, sub.full_score,
                   ROUND(sc.score * 100.0 / sub.full_score, 1) AS percentage
            FROM students s
            JOIN scores sc ON sc.student_id = s.id
            JOIN subjects sub ON sub.id = sc.subject_id
            WHERE s.name = ?
            ORDER BY sub.name
        """, (student_name,))
        
        rows = cursor.fetchall()
        conn.close()
        
        if not rows:
            print(f"未找到学生 '{student_name}' 的成绩记录")
            return
        
        print(f"\n{'='*50}")
        print(f"  学生成绩单: {rows[0]['student']} ({rows[0]['class']})")
        print(f"{'='*50}")
        
        total_score = 0
        total_full = 0
        for row in rows:
            print(f"  {row['subject']:>8s}: {row['score']:>6.1f} / {row['full_score']:.0f} "
                  f"({row['percentage']}%)")
            total_score += row['score']
            total_full += row['full_score']
        
        avg = total_score / len(rows) if rows else 0
        overall_pct = total_score * 100.0 / total_full if total_full else 0
        print(f"{'-'*50}")
        print(f"  {'总分':>8s}: {total_score:>6.1f} / {total_full:.0f} ({overall_pct:.1f}%)")
        print(f"  {'平均分':>8s}: {avg:.1f}")
        print(f"{'='*50}")
    
    def class_ranking(self, subject_name=None):
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        cursor = conn.cursor()
        
        if subject_name:
            cursor.execute("""
                SELECT s.name AS student, s.class, sc.score
                FROM students s
                JOIN scores sc ON sc.student_id = s.id
                JOIN subjects sub ON sub.id = sc.subject_id
                WHERE sub.name = ?
                ORDER BY sc.score DESC
            """, (subject_name,))
            rows = cursor.fetchall()
            print(f"\n--- {subject_name} 成绩排名 ---")
            for rank, row in enumerate(rows, 1):
                print(f"  {rank}. {row['student']} ({row['class']}): {row['score']}分")
        else:
            cursor.execute("""
                SELECT s.name AS student, s.class,
                       SUM(sc.score) AS total_score,
                       AVG(sc.score) AS avg_score,
                       COUNT(sc.score) AS subject_count
                FROM students s
                LEFT JOIN scores sc ON sc.student_id = s.id
                GROUP BY s.id
                ORDER BY total_score DESC NULLS LAST
            """)
            rows = cursor.fetchall()
            print(f"\n--- 总成绩排名 ---")
            for rank, row in enumerate(rows, 1):
                if row['total_score']:
                    print(f"  {rank}. {row['student']} ({row['class']}): "
                          f"总分{row['total_score']:.1f}, 均分{row['avg_score']:.1f}")
                else:
                    print(f"  {rank}. {row['student']} ({row['class']}): 暂无成绩")
        
        conn.close()


# ===== 运行系统 =====
if __name__ == '__main__':
    system = GradeManagementSystem()
    
    # 添加学生
    system.add_student("刘备", "蜀汉班")
    system.add_student("关羽", "蜀汉班")
    system.add_student("张飞", "蜀汉班")
    system.add_student("曹操", "魏国班")
    system.add_student("孙权", "吴国班")
    
    # 添加科目
    system.add_subject("语文")
    system.add_subject("数学")
    system.add_subject("英语")
    
    # 录入成绩
    system.record_score("刘备", "语文", 95)
    system.record_score("刘备", "数学", 88)
    system.record_score("刘备", "英语", 92)
    
    system.record_score("关羽", "语文", 98)
    system.record_score("关羽", "数学", 90)
    system.record_score("关羽", "英语", 85)
    
    system.record_score("张飞", "语文", 80)
    system.record_score("张飞", "数学", 75)
    system.record_score("张飞", "英语", 82)
    
    system.record_score("曹操", "语文", 90)
    system.record_score("曹操", "数学", 95)
    system.record_score("曹操", "英语", 88)
    
    system.record_score("孙权", "语文", 85)
    system.record_score("孙权", "数学", 82)
    system.record_score("孙权", "英语", 90)
    
    # 查看成绩单
    system.get_student_report("关羽")
    system.get_student_report("曹操")
    
    # 排名
    system.class_ranking("数学")
    system.class_ranking()

小结

知识点 说明
sqlite3 模块 Python 内置,无需安装,单文件存储
pymysql 模块 连接 MySQL 的纯 Python 驱动
参数化查询 使用 ? 或 %s 占位符,防止 SQL 注入
事务管理 commit() 提交,rollback() 回滚
executemany() 批量操作,性能远超逐条执行
连接池 复用连接,减少开销,适合高并发场景
外键约束 保证数据完整性,关联表一致性

选择建议:开发和测试阶段用 SQLite,上线部署用 MySQL。Python 的 DB-API 2.0 接口标准让两者代码结构几乎一致,迁移成本低。

练习题

  1. 使用 SQLite 创建一个图书管理数据库,包含图书表(书名、作者、价格、库存)和借阅记录表(读者、图书、借阅日期、归还日期),实现借书和还书功能。

  2. 编写一个函数,使用参数化查询从 MySQL 中按多个条件搜索商品(分类、价格范围、关键词),返回排序后的结果列表。

  3. 为成绩管理系统增加"删除学生"功能,注意处理外键约束——当学生有成绩记录时,应先删除关联成绩再删除学生,或使用 ON DELETE CASCADE 外键策略。