一、SQLite异常处理的重要性

在开发数据访问层时,异常处理是保证系统稳定性的关键。SQLite虽然轻量,但如果不妥善处理异常,可能会导致数据丢失、连接泄漏甚至应用崩溃。想象一下,用户正在提交重要数据,突然程序因为一个未捕获的SQL异常而退出,这体验得多糟糕?

SQLite的常见异常包括:

  • 数据库文件被占用或损坏
  • SQL语法错误
  • 并发访问冲突
  • 磁盘空间不足

我们来看一个反面教材(技术栈:Python + sqlite3):

import sqlite3

# 错误示例:没有异常处理的危险代码
conn = sqlite3.connect('test.db')
cursor = conn.cursor()
cursor.execute("INSERT INTO users (name) VALUES ('张三')")  # 如果表不存在就崩溃
conn.commit()
conn.close()

二、基础异常处理框架

让我们构建一个安全的数据库操作模板(技术栈:Python):

import sqlite3
from contextlib import contextmanager

@contextmanager
def get_db_connection():
    """上下文管理器自动处理连接生命周期"""
    conn = None
    try:
        conn = sqlite3.connect('app.db', timeout=10)
        conn.execute("PRAGMA foreign_keys = ON")  # 启用外键约束
        yield conn
    except sqlite3.Error as e:
        print(f"数据库连接失败: {e}")
        raise
    finally:
        if conn:
            conn.close()

def safe_execute(sql, params=None):
    """安全执行SQL语句的封装"""
    try:
        with get_db_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(sql, params or ())
            conn.commit()
            return cursor.rowcount
    except sqlite3.OperationalError as e:
        print(f"操作失败: {e}")
        return -1
    except sqlite3.IntegrityError as e:
        print(f"数据完整性错误: {e}")
        return -2

这个模板解决了几个关键问题:

  1. 使用上下文管理器确保连接总是关闭
  2. 区分不同类型的SQLite错误
  3. 提供统一的错误代码返回值

三、高级异常处理策略

3.1 事务回滚机制

def transfer_funds(sender, receiver, amount):
    """带事务的资金转账示例"""
    try:
        with get_db_connection() as conn:
            conn.execute("BEGIN TRANSACTION")
            
            # 检查发送方余额
            cursor = conn.execute("SELECT balance FROM accounts WHERE user_id=?", (sender,))
            if cursor.fetchone()[0] < amount:
                raise ValueError("余额不足")
                
            # 执行转账
            conn.execute("UPDATE accounts SET balance=balance-? WHERE user_id=?", (amount, sender))
            conn.execute("UPDATE accounts SET balance=balance+? WHERE user_id=?", (amount, receiver))
            
            conn.commit()
            return True
    except (sqlite3.Error, ValueError) as e:
        conn.rollback()  # 关键的回滚操作
        print(f"转账失败: {e}")
        return False

3.2 自定义异常类

class DBException(Exception):
    """自定义数据库异常基类"""
    pass

class TableExistsError(DBException):
    """表已存在异常"""
    pass

def create_table_safe():
    """安全建表示例"""
    try:
        with get_db_connection() as conn:
            conn.execute("CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT)")
    except sqlite3.OperationalError as e:
        if "already exists" in str(e):
            raise TableExistsError("用户表已存在") from e
        raise

四、实战:完整的DAO层实现

下面是一个完整的用户数据访问对象实现(技术栈:Python):

class UserDAO:
    """用户数据访问对象"""
    
    def __init__(self, db_file="app.db"):
        self.db_file = db_file
        
    def _execute(self, sql, params=None):
        """内部执行方法"""
        try:
            with sqlite3.connect(self.db_file) as conn:
                cursor = conn.cursor()
                cursor.execute(sql, params or ())
                conn.commit()
                return cursor
        except sqlite3.Error as e:
            print(f"数据库操作失败: {e}")
            raise DBException("数据库操作失败") from e
    
    def create_user(self, name, email):
        """创建用户"""
        try:
            self._execute(
                "INSERT INTO users (name, email) VALUES (?, ?)",
                (name, email)
            )
            return True
        except sqlite3.IntegrityError:
            raise ValueError("邮箱已存在")
    
    def get_user(self, user_id):
        """获取单个用户"""
        cursor = self._execute(
            "SELECT id, name, email FROM users WHERE id=?",
            (user_id,)
        )
        if row := cursor.fetchone():
            return {"id": row[0], "name": row[1], "email": row[2]}
        raise ValueError("用户不存在")
    
    def search_users(self, keyword):
        """搜索用户"""
        cursor = self._execute(
            "SELECT id, name FROM users WHERE name LIKE ?",
            (f"%{keyword}%",)
        )
        return [{"id": row[0], "name": row[1]} for row in cursor]

五、性能与可靠性平衡

5.1 连接池优化

import sqlite3
from queue import Queue

class SQLiteConnectionPool:
    """简单的SQLite连接池"""
    
    def __init__(self, db_file, pool_size=5):
        self.db_file = db_file
        self.pool = Queue(pool_size)
        for _ in range(pool_size):
            conn = sqlite3.connect(db_file)
            self.pool.put(conn)
    
    def get_conn(self):
        """获取连接"""
        return self.pool.get()
    
    def release_conn(self, conn):
        """释放连接"""
        self.pool.put(conn)
    
    def close_all(self):
        """关闭所有连接"""
        while not self.pool.empty():
            conn = self.pool.get()
            conn.close()

5.2 重试机制

import time
from functools import wraps

def retry_on_deadlock(max_retries=3, delay=0.1):
    """死锁重试装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            retries = 0
            while retries < max_retries:
                try:
                    return func(*args, **kwargs)
                except sqlite3.OperationalError as e:
                    if "database is locked" in str(e):
                        retries += 1
                        time.sleep(delay * retries)
                        continue
                    raise
            raise Exception("超过最大重试次数")
        return wrapper
    return decorator

六、最佳实践总结

  1. 防御性编程:总是假设数据库操作可能失败
  2. 资源管理:使用with语句或RAII模式确保资源释放
  3. 错误分类:区分业务错误和技术错误
  4. 日志记录:记录足够多的上下文信息以便排查问题
  5. 单元测试:为异常场景编写专门的测试用例
# 测试用例示例
import unittest

class TestUserDAO(unittest.TestCase):
    def setUp(self):
        self.dao = UserDAO(":memory:")
        self.dao._execute("CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT, email TEXT UNIQUE)")
    
    def test_create_duplicate_email(self):
        self.dao.create_user("张三", "zhang@example.com")
        with self.assertRaises(ValueError):
            self.dao.create_user("李四", "zhang@example.com")
    
    def test_get_nonexistent_user(self):
        with self.assertRaises(ValueError):
            self.dao.get_user(999)