3、Python数据库编程基础:DB-API 2.0规范、Connection对象、Cursor对象、事务管理、异常处理

好,咱们进入正题。Python 操作数据库,说白了就是靠一套统一的接口规范——DB-API 2.0。你可能会问,为什么要有这个规范?

我刚开始写 Python 连数据库的时候,用的是 MySQLdb,后来换成 psycopg2 连 PostgreSQL,再后来用 sqlite3。每个库的用法都不一样,换个数据库就得重学一遍 API,那叫一个痛苦。DB-API 2.0 就是来解决这个问题的。

3.1 DB-API 2.0 规范是什么?

DB-API 2.0 是 Python 社区制定的一套标准接口。所有主流的数据库驱动,比如 sqlite3、pymysql、psycopg2、cx_Oracle,都遵循这个规范。你只要学会一套写法,换数据库就是改个连接字符串的事。

核心就两个对象:ConnectionCursor。再加上几个关键方法:execute()fetchone()fetchall()commit()rollback()

记住这个口诀:连接用 Connection,操作用 Cursor,改完记得 commit,出错就 rollback。

3.2 Connection 对象——你的数据库通道

Connection 对象代表与数据库的一次连接。说白了,就是你和数据库之间的一条专线。

创建连接时,你需要提供数据库的地址、端口、用户名、密码、数据库名这些信息。以 sqlite3 为例,它最简单,连用户名密码都省了:

import sqlite3

# 创建连接
conn = sqlite3.connect('mydb.db')
# 或者用内存数据库
# conn = sqlite3.connect(':memory:')

Connection 对象有几个重要方法:

  • close():关闭连接。用完一定要关,不然会泄漏连接。
  • commit():提交事务。你做的修改,只有 commit 了才会真正写入数据库。
  • rollback():回滚事务。出错了就撤销所有未提交的修改。
  • cursor():创建游标对象。这是你执行 SQL 的“手”。

我的习惯:用 with 语句管理连接,这样能自动关闭,省心。

with sqlite3.connect('mydb.db') as conn:
    # 在这里操作数据库
    pass  # 连接会自动关闭

3.3 Cursor 对象——你的执行之手

Cursor 对象才是真正干活的。你通过它执行 SQL 语句,获取查询结果。

创建游标很简单:

cursor = conn.cursor()

Cursor 的核心方法:

  • execute(sql, parameters):执行单条 SQL 语句。参数用占位符 ?%s,避免 SQL 注入。
  • executemany(sql, seq_of_parameters):批量执行,比如一次插入多条数据。
  • fetchone():取一条结果。
  • fetchall():取所有结果。
  • fetchmany(size):取指定数量的结果。

来看个完整的例子:

import sqlite3

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

# 创建表
cursor.execute('''
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY,
        name TEXT NOT NULL,
        age INTEGER
    )
''')

# 插入数据
cursor.execute('INSERT INTO users (name, age) VALUES (?, ?)', ('Alice', 25))
cursor.execute('INSERT INTO users (name, age) VALUES (?, ?)', ('Bob', 30))

# 提交事务
conn.commit()

# 查询数据
cursor.execute('SELECT * FROM users')
rows = cursor.fetchall()
for row in rows:
    print(row)

# 关闭
cursor.close()
conn.close()

这里要注意,execute() 的参数用 ? 占位,而不是用 f-string 拼接字符串。为什么?因为拼接字符串容易引发 SQL 注入攻击。我在项目中见过有人用 f-string 拼接用户输入,结果被注入了删表语句,数据全没了。嗯,那场面...你懂的。

3.4 事务管理——要么全做,要么全不做

事务是数据库的基石。一个事务里的操作,要么全部成功,要么全部失败回滚。

在 DB-API 2.0 中,事务是自动开启的。你执行 execute() 后,修改不会立即生效,需要调用 conn.commit() 才会真正写入。如果出错了,调用 conn.rollback() 撤销所有修改。

典型的转账场景:

def transfer(from_id, to_id, amount):
    conn = sqlite3.connect('bank.db')
    cursor = conn.cursor()
    try:
        # 扣钱
        cursor.execute('UPDATE accounts SET balance = balance - ? WHERE id = ?', (amount, from_id))
        # 加钱
        cursor.execute('UPDATE accounts SET balance = balance + ? WHERE id = ?', (amount, to_id))
        # 提交
        conn.commit()
        print('转账成功')
    except Exception as e:
        # 出错就回滚
        conn.rollback()
        print(f'转账失败,已回滚:{e}')
    finally:
        cursor.close()
        conn.close()

我曾经踩过的坑:忘记调用 commit(),数据没写进去,查了半天。还有一次是异常处理里忘了 rollback(),导致后续操作都基于一个脏连接。记住:commit 和 rollback 要成对出现,就像打开和关闭文件一样。

3.5 异常处理——别让程序崩了

数据库操作最容易出异常。网络断了、权限不够、SQL 语法错误、唯一约束冲突...各种情况。

DB-API 2.0 定义了一套异常层次结构:

异常类 说明
Warning 警告,比如数据截断
Error 所有错误的基类
InterfaceError 数据库接口错误,比如连接断开
DatabaseError 数据库层面的错误
DataError 数据问题,比如除零、数值越界
OperationalError 操作错误,比如表不存在、连接超时
IntegrityError 完整性约束违反,比如主键重复
InternalError 数据库内部错误
ProgrammingError 编程错误,比如 SQL 语法错误
NotSupportedError 调用了不支持的 API

实际开发中,我一般这样处理:

import sqlite3
from sqlite3 import Error

try:
    conn = sqlite3.connect('mydb.db')
    cursor = conn.cursor()
    cursor.execute('SELECT * FROM non_existent_table')
except sqlite3.OperationalError as e:
    print(f'表不存在?检查一下:{e}')
except sqlite3.IntegrityError as e:
    print(f'数据冲突了:{e}')
except Error as e:
    print(f'其他数据库错误:{e}')
finally:
    if conn:
        conn.close()

我的建议:不要只捕获通用的 Exception,尽量捕获具体的异常类。这样你能知道到底出了什么问题,调试起来快得多。

3.6 完整的实践模板

最后,给你一个我常用的模板。每次写数据库操作,我都按这个结构来:

import sqlite3
from contextlib import closing

def safe_db_operation():
    conn = None
    try:
        conn = sqlite3.connect('mydb.db')
        with closing(conn.cursor()) as cursor:
            # 执行操作
            cursor.execute('...')
            conn.commit()
    except sqlite3.Error as e:
        if conn:
            conn.rollback()
        print(f'数据库操作失败:{e}')
        raise  # 或者返回错误信息
    finally:
        if conn:
            conn.close()

这个模板的好处是:连接和游标都能正确关闭,异常能回滚,错误能记录。你直接套用就行。

好了,DB-API 2.0 的核心就这些。说白了就是:连接、游标、执行、提交、回滚、异常处理。把这六个点吃透,Python 连任何数据库都不在话下。