数据库API规范 v2.0 (PEP 249)
GitHub@orca-j35,所有笔记均托管于 python_notes 仓库
欢迎任何形式的转载,但请务必注明出处。参考:
- https://www.python.org/dev/pe...
- http://sinhub.cn/2019/01/pep2...
- https://zh.wikipedia.org/wiki...
- https://en.wikipedia.org/wiki...
介绍
PEP 249 是 Python 数据库 API 规范的 2.0 版本,PEP 248 是 1.0 版本。PEP 249 不仅包含 Python 数据库 API 规范,还描述了一组具备通用性的可选扩展。早先的 1.0 版本(PEP 248)依然具备参考价值,但鼓励模块编写者在开发新接口时以 PEP 249 为参考。PEP 249 的目标是让"数据库访问模块"提供相似的接口,实现一致性,从而让代码便于移植。
如果需要了解与数据库接口相关的各种库,请看 Database Topic Guide。
模块接口
构造器
本规范约定,需通过 Connection 对象来访问数据库,因此"数据库访问模块"必须提供用来创建 Connection 对象的构造器。
connect
connect( parameters... )
该构造器用于创建数据库连接,返回值是 Connection 对象。
parameters... 表示与数据库相关的参数。
注释:
构造器 connect( parameters... ) 的参数应以关键字参数的形式实现,并按照下表中的先后顺序排列:
Parameter Meaning dsn
Data source name as string user
User name as string (optional) password
Password as string (optional) host
Hostname (optional) database
Database name (optional) E.g. a connect could look like this:
connect(dsn='myhost:MYDB', user='guido', password='234$')
模块级全局变量
数据库访问模块必须定义本节中给出的各种全局变量
apilevel
apilevel
一个字符串常量,表示模块支持的 DB API 版本。
目前只允许使用字符串 "1.0" (对应 PEP 248) 和 "2.0" (对应 PEP 249)。如果未给出该常量,则假设模块使用 DB-API 1.0。
threadsafety
threadsafety
一个整数常量,用于说明模块接口支持的线程安全级别,可选值如下:
threadsafety | Meaning |
---|---|
0 | Threads may not share the module. |
1 | Threads may share the module, but not connections. |
2 | Threads may share the module and connections. |
3 | Threads may share the module, connections and cursors. |
share 意味着两个线程在使用同一个资源时,无需使用互斥信号(mutex semaphore)来实现资源锁。注意,即使已使用互锁机制(mutex)来管理访问权限,也无法始终确保外部资源的线程安全: 因为外部资源可能会依赖于全局变量,或依赖于你无法控制的其它外部资源。
paramstyle
一个字符串常量,用于说明模块接口期望的参数标记(parameter marker)的格式化风格,可选值如下:
paramstyle | Meaning |
---|---|
qmark |
Question mark style, e.g. ...WHERE name=? |
numeric |
Numeric, positional style, e.g. ...WHERE name=:1 |
named |
Named style, e.g. ...WHERE name=:name |
format |
ANSI C printf format codes, e.g. ...WHERE name=%s |
pyformat |
Python extended format codes, e.g. ...WHERE name=%(name)s |
模块实现者应优先考虑使用 numeric
, named
或 pyformat
,因为它们清晰度和灵活性更佳。
import sqlite3
print(sqlite3.paramstyle) #> qmark
import pymysql
print(pymysql.paramstyle) #> pyformat
import mysql.connector
print(mysql.connector.paramstyle) #> pyformat
可见,sqlite3
使用 ?
作为参数标记,因此可使用以下语句插入数据:
cursor.execute("INSERT INTO user (id,name) VALUES (?, ?)", (6, 'liuyi'))
Exceptions...
数据库访问模块应通过下述 Exception 类(或其子类)来提供全部错误信息:
-
☣
Warning
必须继承自 Python
StandardError
,遇到下述类似情况时,应抛出该异常:- 在插入时遇到数据被截断
- ☣
Error
必须继承自 Python
StandardError
,模块中其它所有 error 异常都应是Error
的子类。你可以在单个except
语句中使用Error
来捕捉所有 error 异常。Warning
不应被视为 error,因此不是Error
的子类。 - ☣
InterfaceError
必须是
Error
的子类表示那些与数据库接口有关,而与数据库本身无关的错误。 - ☣
DatabaseError
必须是
Error
的子类,表示那些与数据库有关的错误。 - ☣
DataError
必须是
DatabaseError
的子类,表示因处理数据而导致的错误,例如: 除数是 0;或数值超出范围 - ☣
OperationalError
必须是
DatabaseError
的子类,表示与数据库操作相关的错误,并且不一定是在程序员的控制之下造成的错误。比如,意外情况导致断开连接;找不到数据源名称;无法处理事务,处理期间发生内存分配错误等 - ☣
IntergrityError
必须是
DatabaseError
的子类,表示数据操作违反了数据库的完整性约束。比如,外键检查失败。 - ☣
InternalError
必须是
DatabaseError
的子类,遇到数据库内部错误时会抛出该异常。比如,游标(cursor)不再有效,或事务不同步等。 - ☣
ProgrammingError
必须是
DatabaseError
的子类,表示程序逻辑出错。比如,未找到表或表已经存在;SQL 语句中的语法错误;指定了错误的参数数量等。 - ☣
NotSupportedError
必须是
DatabaseError
的子类,当使用了数据库不支持的 API 或方法时会抛出该异常。比如,在不支持 SQL 事务(或已关闭事务)的 connection 上请求.rollback()
方法。
继承结构
下面是 Exception 类的继承结构:
StandardError
|__Warning
|__Error
|__InterfaceError
|__DatabaseError
|__DataError
|__OperationalError
|__IntegrityError
|__InternalError
|__ProgrammingError
|__NotSupportedError
⚠The values of these exceptions are not defined. They should give the user a fairly good idea of what went wrong, though.
Connection 对象
Connection 对象必须实现本节中给出的各种方法。
Connection 方法
.close()
立即关闭连接(而不是每次都调用 .__del__()
方法)
在 Connection 对象上调用 .close()
后,"连接"将不再可用。如果试图对此"连接"做任何操作都会抛出 Error
(或其子类)异常。这同样适用于试图使用此"连接"的所有游标(cursor)对象。
⚠如果没有 commit 变更就关闭了"连接",将隐式执行 rollback 操作。
.commit()
将任何挂起的事务(transaction)提交到数据库。
⚠如果数据库支持自动提交(auto-commit)功能,必须先关闭自动提交功能。还可以提供一个接口方法来重新开启自动提交功能。
如果数据库模块不支持事务,则应使用一个空方法(void functionality)来实现 .commit()
.rollback()
这是一个可选方法,因为并非所有数据库都支持事务。
如果数据库确实支持事务,.rollback()
方法会使数据库回滚到任何挂起事务的之前。如果没有 commit 变更就关闭了"连接",将隐式执行 rollback 操作。
注释:If the database does not support the functionality required by the method, the interface should throw an exception in case the method is used.
The preferred approach is to not implement the method and thus have Python generate an
AttributeError
in case the method is requested. This allows the programmer to check for database capabilities using the standardhasattr()
function.For some dynamically configured interfaces it may not be appropriate to require dynamically making the method available. These interfaces should then raise a
NotSupportedError
to indicate the non-ability to perform the roll back when the method is invoked.
.cursor()
使用 Connection 对象返回一个新的 Cursor 对象。
如果数据库并没有直接提供游标的概念,则模块必须使用其他方式来模拟规范所需的“游标”。
数据库接口可以选择是否为.cursor()
提供一个字符串参数来构建具名 cursor。该功能并不属于规范中的一部分,因为它会使.fetch*()
方法的语义复杂化。
Cursor 对象
Cursor 对象表示数据库的游标(cursor),用于管理 fetch 操作的上下文(context)。由同一个 connection 创建的 cursor 之间并不是孤立的(isolated),某个 cursor 对数据库进行的任何改动都会被其它 cursor 立即看到。由不同 connection 创建的 cursor 之间可能是相互孤立的,也可能不是相互孤立的,这取决于事务支持的实现方式。(还可以看看 connection 的 rollback()
和 .commit()
方法)
Cursor 对象必须实现本节中给出的各种方法和属性。
Cursor 属性
.description
该只读属性本身是一个序列,序列中的每个元素又对应为一个子序列,子序列由以下 7 个条目组成。子序列中存放着对结果列(column)的描述信息,每个子序列对应一个结果列。
name
-
type_code
- 此列在数据库中的类型 display_size
internal_size
precision
scale
null_ok
前两个条目(name
和 type_coe
)是必填项,后面五个是选填项。如果不能提供有意义的值,则设置为 None
。
在遇到以下两种情况时,.description
的值是 None
:
- curcor 尚未通过
.execute*()
方法执行操作 - 被执行的操作不会返回任何行(row)
如果需要了解 type_code
的含义,请参考 Type Objects 小节
.rowcount
该只读属性用于存放以下两种情况涉及到的行数:
- 最近一次
.execute*()
调用所得结果的行数( 针对 SELECT 等 DQL 语句) - 最近一次
.execute*()
调用所影响的行数( 针对 UPDATE 或 INSERT 等 DML 语句)
注释:The term number of affected rows generally refers to the number of rows deleted, updated or inserted by the last statement run on the database cursor. Most databases will return the total number of rows that were found by the corresponding
WHERE
clause of the statement. Some databases use a different interpretation forUPDATE
s and only return the number of rows that were changed by theUPDATE
, even though theWHERE
clause of the statement may have found more matching rows. Database module authors should try to implement the more common interpretation of returning the total number of rows found by theWHERE
clause, or clearly document a different interpretation of the.rowcount
attribute.
在遇到以下两种情况时,.rowcount
的值为 -1
:
- cursor 尚未执行过
.execute*()
方法 - 接口无法确定最后一次操作的涉及到的行数
在今后的 DB API 规范中会重新定义此种情况,将返回值由
-1
改为None
Therowcount
attribute may be coded in a way that updates its value dynamically. This can be useful for databases that return usablerowcount
values only after the first call to a .fetch*() method.
.arraysize
该可读/写属性表示每次调用 fetchmany()
时可获取到的结果集中的行数。该字段的默认值是 1
,意味着每次调用 fetchmany()
函数会获取结果集中的一行数据。
在实现 .fetchmany()
时必须遵守 .arraysize
字段,但在实现过程中也可以一次一行的和数据库交互。在实现 executemany()
方法时,也可使用 .arrarsize
字段。
Cursor 方法
.callproc()
.callproc( procname [, parameters ] )
(此方法是可选实现,因为并非所有数据库都支持"存储过程(stored procedures)")
注释:If the database does not support the functionality required by the method, the interface should throw an exception in case the method is used.
The preferred approach is to not implement the method and thus have Python generate an
AttributeError
in case the method is requested. This allows the programmer to check for database capabilities using the standardhasattr()
function.For some dynamically configured interfaces it may not be appropriate to require dynamically making the method available. These interfaces should then raise a
NotSupportedError
to indicate the non-ability to perform the roll back when the method is invoked.
❓Q: 什么是"存储过程"?
A: 可参考《SQL 必知必会》->第19课 使用存储过程,简单来说存储过程就好像是自定义函数。
.callproc()
的功能是调用名为 procname 的"存储过程",参数序列 parameters 中包含的条目便是"存储过程"所需的参数。调用结果将被表示为输入序列的副本,并在返回前对参数进行修改:
- "存储过程"的输入(
IN
)参数保持不变 - 输出(
OUT
)参数和输入/输出(INOUT
)参数将被替换为可能的新值
"存储过程"也可以提供一个作为输出的结果集。此时必须通过标准的 .fetch*()
方法来获取。
某些库的实现方式可能并不兼容本条约定,比如 pymysql
:
"""Defining the Stored Routine in MySQL:
CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT)
BEGIN
SET pProd := pFac1 * pFac2;
END
"""
import pymysql
try:
conn = pymysql.connect(
host="localhost",
port=3306,
user='root',
password='orca_j35',
database='test',
)
cursor = conn.cursor()
args = (5, 5, 0)
# callproc()直接返回输入序列,不兼容本条约定
print(cursor.callproc('multiply', args)) #> (5, 5, 0)
print(cursor.execute('SELECT @_multiply_2')) #> 1
print(cursor.fetchall()) #> ((25,),)
except Exception as ex:
print(ex)
.close()
.close()
立即关闭 cursor (而不是每次都调用 .__del__()
方法)
在 cursor 对象上调用 .close()
后,cursor 将不在可用。如果之后试图使用这个 cursor 执行任何操作,都会引发一个 Error
或其(子类)的异常。
.execute()
.execute(operation [, parameters])
准备并执行一个数据库操作: query 或 command
parameters 可以是序列或映射,parameters 中的元素将被绑定到操作 operation 内含的变量中。需以数据库特定的标识符来表示变量(详见 paramstyle
属性)
注释:The module will use the
__getitem__
method of the parameters object to map either positions (integers) or names (strings) to parameter values. This allows for both sequences and mappings to be used as input.The term bound refers to the process of binding an input value to a database execution buffer. In practical terms, this means that the input value is directly used as a value in the operation. The client should not be required to "escape" the value so that it can be used — the value should be equal to the actual database value.
cursor 将保留(retain)对 operation 的引用。如果再次传入相同的 operation 对象,那么 cursor 可以优化这种行为。这对于那种(多次)使用相同的 operation,但会将不同 parameters 绑定到该 operation 的情况最有效。
为了在重用 operation 时获得最大的效率,最好使用 .setinputsizes()
方法提前指定 parameters 中各参数的类型和大小。参数与预定义信息不匹配也属于合法行为,但在实现上应考虑对可能会出现的效率损失进行补偿(compensate)。
parameters 也可以是元组列表(list of tuples),例如,在单个操作中插入多行。但是不推荐这种做法: 应使用 .executemany()
方法。
此方法未定义返回值。
示例 -
import pymysql
print(pymysql.paramstyle) #> pyformat
conn = pymysql.connect(
host="localhost",
port=3306,
user='root',
password='orca_j35',
database='test',
)
cursor = conn.cursor()
cursor.execute("INSERT INTO user(name) VALUES (%s)", ('小刘', ))
cursor.execute('SELECT name,id FROM user Where name=%s', ('小刘', ))
print(cursor.fetchall())
#> (('小刘', 15),)
.executemany()
.executemany( operation, seq_of_parameters )
准备一个数据库操作(query 或 command),然后逐一使用 seq_of_parameters 中包含的序列或映射来执行该数据库操作。也就是说,.executemany()
方法可以在一次调用中处理 seq_of_parameters 中的所有序列。
数据库模块有两种实现 .executemany()
的方式:
- 第一种是多次调用
.execute()
方法 - 第二种是使用数组操作(array operations)
如果 .executemany()
执行了会生成一个或多个结果集(result sets)的 operation,便会构成未定义行为;当检测到通过调用操作创建了结果集时,允许(非必须)实现抛出一个异常。
适用于 .execute()
的注释也适用于此方法。
此方法未定义返回值。
.fetchone()
.fetchone()
获取"查询结果集(query result set)"中的下一行,并将其表示为单个序列。如果没有更多可用数据则会返回 None
。
注释:数据库接口可能会使用数组和其他优化方式来实现数据行的获取。因此在调用
fetchone()
时,并不能保证相应的 cursor 只会向前移动一行,可能会移动多行。
如果先前调用的 .execute*()
方法并未产生任何结果集,或者尚未调用 .execute*()
,则应抛出 Error
(或其子类)异常。
.fetchmany()
.fetchmany([size=cursor.arraysize])
向后获取查询结果集中的多个行,并返回一个包含各行数据的序列(e.g. a list of tuples),每一行数据会被表示为一个子序列。当结果集中没有更多可用行时,则会返回一个空序列。
每次调用所获取的行数由 size 参数决定。如果没有传入 size 参数,则由 cursor 的 arraysize
属性来确定要获取的行数。该方法应尽力尝试获取 size 行数据,如果结果集中的可用行数小于 size,则返回的函数可能少于 size。
如果先前调用的 .execute*()
方法并未产生任何结果集,或者尚未调用 .execute*()
,则应抛出 Error
(或其子类)异常。
⚠: size 参数的大小会影响性能。为了获得最佳性能,通常最好将 cursor 的 arraysize
属性用作 size 的值。如果使用自定义 size 值,那么最好在每次调用 .fetchmany()
时都使用相同的 size 值。
.fetchall()
.fetchall()
获取查询结果集中所有(剩余)的行,并返回一个包含各行数据的序列(e.g. a list of tuples),每一行数据会被表示为一个子序列。
⚠cursor 的 arraysize
属性可能会影响此操作的性能。
如果先前调用的 .execute*()
方法并未产生任何结果集,或者尚未调用 .execute*()
,则应抛出 Error
(或其子类)异常。
.nextset()
.nextset()
(此方法是可选实现,因为并非所有数据库都支持"多结果集(multiple result sets)")
注释:If the database does not support the functionality required by the method, the interface should throw an exception in case the method is used.
The preferred approach is to not implement the method and thus have Python generate an
AttributeError
in case the method is requested. This allows the programmer to check for database capabilities using the standardhasattr()
function.For some dynamically configured interfaces it may not be appropriate to require dynamically making the method available. These interfaces should then raise a
NotSupportedError
to indicate the non-ability to perform the roll back when the method is invoked.
此方法会使 cursor 跳至下一个可用的结果集,并丢弃当前结果集中剩余的所有行。
如果不存在更多的结果集,该方法将返回 None
;否则,该方法会返回一个真值,在这之后调用的 .fetch()
方法将返回下一个结果集中的行。
如果先前调用的 .execute*()
方法并未产生任何结果集,或者尚未调用 .execute*()
,则应抛出 Error
(或其子类)异常。
"""在MySQL中定义一个返回多结果集的存储过程:
CREATE DEFINER=`root`@`localhost` PROCEDURE `multi_select`()
BEGIN
SELECT name FROM `user`;
SELECT id FROM `user`;
END
"""
"""user表中的数据如下:
name | id
"小明" | "1"
"小红" | "2"
"小刚" | "3"
"小灿" | "4"
"""
import pymysql
conn = pymysql.connect(
host="localhost",
port=3306,
user='root',
password='orca_j35',
database='test',
)
cursor = conn.cursor()
cursor.callproc('multi_select')
print(cursor.fetchone())
if cursor.nextset():
print(cursor.fetchall())
输出:
('小明',)
((1,), (2,), (3,), (4,))
.setinputsizes()
.setinputsizes(sizes)
.execute( operation [, parameters]).executemany( operation, seq_of_parameters )
在调用 .execute*()
之前,可使用该方法预定义 .execute*()
的"操作参数"(parameters 或 seq_of_parameters)的内存区域。
sizes 参数应是一个序列,该序列中的项应和"输入参数"中的项相互对应,并且是相同的 Type Object
或者是表示字符串参数的最大长度的整数。如果某一项的值是 None
,则不会为该列保留预定义的内存区域——这样可以避免为大尺寸的"输入参数"预留过大的内存区域。
应在使用 .execte*()
之前调用 .setinputsizes()
。
数据库模块的实现者可以自由选择是否实现此方法,并且模块的使用者也可以自由选择是否使用此方法。
.setoutputsize()
.setoutputsize(size [, column])
为获取"大数据列"(e.g. LONG
s, BLOB
s, etc.)而设置的列缓冲区。column 参数用于指定目标列在结果序列中的索引位置。如果不设置 column,则默认为 cursor 中的全部大数据列设置尺寸为 size 的缓冲区。
应在使用 .execte*()
之前调用 .setoutputsize()
。
数据库模块的实现者可以自由选择是否实现此方法,并且模块的使用者也可以自由选择是否使用此方法。
数据类型的构造器...
许多数据库都需要具备特定格式的输入,以便通过 SQL 操作将输入参数绑定到数据库中。例如,假如要向数据库的 DATE
列输入数据,就必须以"特定的字符串格式"将输入数据绑定到数据库中。"Row ID" 列或 large binary 项(e.g. blobs or RAW
columns)也存在类似的问题。这点对于 Python 来说并不友好,因为传递给 execute*()
的参数是无类型的。当数据库模块看到 Python 字符串对象时,它并不知道应该将其绑定到何种数据库类型 —— 数据库模块会很困惑,是该绑定到 CHAR
列,还是绑定到 BINARY
项,抑或是绑定到 DATE
喃?
为了解决上述问题,模块必须提供以下构造器,从而可以创建能够保持特定值的对象。当我们将下述对象传递给 cursor 的方法时,模块就能够检查到输入参数的正确类型,并将其绑定到数据库中。
模块将包含以下的构造函数:
- Date(year, month, day)
此函数会构造一个保存 data 值的对象。
- Time(hour, minute, second)
此函数会构造一个保存 time 值的对象。
- Timestamp(year, month, day, hour, minute, second)
此函数会构造一个保存 time stamp 值的对象。
- DateFromTicks(ticks)
此函数会根据给定的 ticks 值来构造一个保存 data 值的对象。ticks 是自纪元(epoch)以来秒数,详见 the standard Python time module
- TimeFromTicks(ticks)
此函数会根据给定的 ticks 值来构造一个保存 time 值的对象。ticks 是自纪元(epoch)以来秒数,详见 the standard Python time module
- TimestampFromTicks(ticks)
此函数会根据给定的 ticks 值来构造一个保存 time stamp 值的对象。ticks 是自纪元(epoch)以来秒数,详见 the standard Python time module
-
Binary(string)
此函数会构造一个能够保存 binary (long) 字符串值的对象。
# Binary's source code in pymysql def Binary(x): """Return x as a binary type.""" if PY2: return bytearray(x) else: return bytes(x)
详见笔记: ﹝bytes.md﹞
在输入/输出中,SQL 的 NULL
值会表示为 Python 的 None
单例对象。
⚠: 在数据库接口中使用 Unix ticks 可能会导致不必要的麻烦,因为 Unix ticks 仅覆盖有限的日期范围
类型对象...
Cursor 对象的 .descripton
字段会返回"查询结果"中每一列的相关信息,其中的 type_code
必须等于下述某个 Type Object,同一个 Type Object 可能会与多个类型代码(type code)相等 (e.g. DATETIME
could be equal to the type codes for date, time and timestamp columns; see the Implementation Hints below for details).
模块将包含以下单例(singleton)形式的 Type Object:
- STRING type
此 Type Object 用于描述数据库中的 string-based 列,如
CHAR
- BINARY type
此 Type Object 用于描述数据库中的 (long) binary 列,如
LONG
,RAW
,BLOB
- NUMBER type
此 Type Object 用于描述数据库中的 numeric 列
- DATETIME type
此 Type Object 用于描述数据库中的 date/time 列
- ROWID type
此 Type Object 用于描述数据库中的 "Row ID" 列
扩展阅读: Equivalent of Oracle’s RowID in MySQL
给模块实现者的建议
译者注: 这部分内容是给模块的实现者的一些建议。如果你是模块的使用者,无需过分关心这部分内容,随意浏览一下就好,原文内容也比较简单,因此我没有进行翻译。如果你想了解模块的具体实现过程,那么建议你仔细看一看。
- Date/time objects can be implemented as Python datetime module objects (available since Python 2.3, with a C API since 2.4) or using the mxDateTime package (available for all Python versions since 1.5.2). They both provide all necessary constructors and methods at Python and C level.
-
Here is a sample implementation of the Unix ticks based constructors for date/time delegating work to the generic constructors:
import time def DateFromTicks(ticks): return Date(*time.localtime(ticks)[:3]) def TimeFromTicks(ticks): return Time(*time.localtime(ticks)[3:6]) def TimestampFromTicks(ticks): return Timestamp(*time.localtime(ticks)[:6])
- The preferred object type for Binary objects are the buffer types available in standard Python starting with version 1.5.2. Please see the Python documentation for details. For information about the C interface have a look at
Include/bufferobject.h
andObjects/bufferobject.c
in the Python source distribution. -
This Python class allows implementing the above type objects even though the description type code field yields multiple values for on type object:
class DBAPITypeObject: def __init__(self,*values): self.values = values def __cmp__(self,other): if other in self.values: return 0 if other < self.values: return 1 else: return -1
The resulting type object compares equal to all values passed to the constructor.
-
Here is a snippet of Python code that implements the exception hierarchy defined above:
import exceptions class Error(exceptions.StandardError): pass class Warning(exceptions.StandardError): pass class InterfaceError(Error): pass class DatabaseError(Error): pass class InternalError(DatabaseError): pass class OperationalError(DatabaseError): pass class ProgrammingError(DatabaseError): pass class IntegrityError(DatabaseError): pass class DataError(DatabaseError): pass class NotSupportedError(DatabaseError): pass
In C you can use the
PyErr_NewException(fullname, base, NULL)
API to create the exception objects.
可选DB API扩展...
在 DB API 2.0 的生命周期中,模块编写者经常会编写超出 DB API 2.0 规范的实现。为了增强兼容性并(为本规范的未来版本)提供干净的升级路径,本节为核心 DB API 2.0 规范定义了一组通用扩展。
与所有 DB API 可选功能一样,数据库模块的编写者可以自由决定是否需要实现这些额外的字段和方法。如果调用了未实现的属性,则会抛出 AttributeError
;对于只能在运行时才能检查可用性的属性,则会抛出 NotSupportedError 。
为了让模块使用者哪些扩展 API 可用,建议模块编写者通过 Python warning 框架向程序员发出 Python warning。为了使 warning 功能可用,必须对警告消息进行标准化,以便能够屏蔽它们。“标准的消息”是指下述内容中提到的 Warning Message 。
- Cursor.rownumber
只读属性,表示 cursor 在当前结果集中的索引位置(起点是 0)。如果无法确定索引位置,则会返回
None
。索引位置是指 cursor 在序列(结果集)中的位置。下一次 fetch 操作将获取序列(结果集)中第
.rownumber
行。Warning Message: "DB-API extension cursor.connection used"
- Connection.Error, Connection.ProgrammingError, etc.
所有按照 DB API 标准定义的异常类都应作为
Connection
对象的属性对外公开(在模块作用域可用的除外)。这些属性可简化多连接(multi-connection)环境中的错误除了。
Warning Message: "DB-API extension connection.
used" - Cursor.connection
只读属性,返回创建 Cursor 对象的 Connection 对象的引用。
该属性可简化在多连接(multi-connection)环境中编写多态(polymorph)代码的过程。
Warning Message: "DB-API extension cursor.connection used"
-
Cursor.scroll(value [, mode='relative' ])
改变 cursor 在结果集中的索引位置。
如果
mode='relative'
(默认情况),则将 value 视作结果集中当前位置的偏移量;如果mode='absolute'
,则将 value 视作结果集中的绝对目标位置。Note
This method should use native scrollable cursors, if available, or revert to an emulation for forward-only scrollable cursors. The method may raise NotSupportedError to signal that a specific operation is not supported by the database (e.g. backward scrolling).
Warning Message: "DB-API extension cursor.scroll() used"
- Cursor.messages
该字段是一个
list
对象,从 cursor 的底层数据库中接受到的所有消息(异常类、异常值)都会以元组形式追加到.messages
列表中。在调用各种标准的 cursor 方法时(
.fetch*()
除外)都会自动清理.messages
列表,从而避免占用过多的内存。还可用通过del cursor.messages[:]
手动清理.messages
列表。由数据库生成的所有 error 和 warning 消息都会被放
.messages
列表中,因此用户可以通过检查.messages
来核查 cursor 方法调用的 SQL 操作是否正确。使用
.messages
的目的是消除对 Warning 异常的需求(某些 warning 实际上只包含信息字符)Warning Message: "DB-API extension cursor.messages used"
- Connection.messages
与
Cursor.messages
相同,不过列表中的信息是面向 connection 的。在调用各种标准的 connection 方法时(执行调用之前)都会自动清理
.messages
列表,从而避免占用过多的内存。还可用通过del connection.messages[:]
手动清理.messages
列表。Warning Message: "DB-API extension connection.messages used"
-
Cursor.next()
与
.fetchone()
拥有相同的语义——从当前执行的 SQL 语句中返回下一行。在高于 Python 2.2 版本中,当结果集耗尽时会抛出StopIteration
异常;在低于 Python 2.2 的版本中没有StopIteration
异常,可以使用IndexError
替代。实例:# source code in mysql.connector def next(self): """Used for iterating over the result set.""" return self.__next__() def __next__(self): """ Used for iterating over the result set. Calles self.fetchone() to get the next row. """ try: row = self.fetchone() except errors.InterfaceError: raise StopIteration if not row: raise StopIteration return row
Warning Message: "DB-API extension cursor.next() used"
-
Cursor.__iter__()
让 cursors 对象兼容迭代器协议。
译者注: 如果 Cursor 实现了
__next__()
,直接返回self
即可;如果没有实现,则需要返回一个可迭代对象,例如:# source code in mysql.connector def __iter__(self): """ Iteration over the result set which calls self.fetchone() and returns the next row. """ return iter(self.fetchone, None)
注释:
Implementation Note: Python C extensions will have to implement the
tp_iter
slot on the cursor object instead of the.__iter__()
method.Warning Message: "DB-API extension cursor.__iter__() used"
- Cursor.lastrowid
只读属性,将提供最近一次修改的行的 rowid (大多数数据库仅在执行单个
INSERT
操作时会返回 rowid)。如果 SQL 操作未设置 rowid,或者数据库不支持 rowid,则会将该属性设置为None
。如果最后一次执行的 SQL 语句修改了多个行,此时
.lastrowid
的语言无法定义。例如,用executemany()
执行INSERT
语句。Warning Message: "DB-API extension cursor.lastrowid used"
可选错误处理扩展...
核心 DB API 规范仅引入了一组异常,在某些情况下,异常可能对程序的流程造成极大的破坏,甚至会导致无法执行。
对于这种情况,为了简化处理数据库时的错误处理(error handling),数据库模块的作者可以选择是否实现能够由用户自定义的错误处理器(handler)。本节将介绍定义这些错误处理器的标准方法。
在创建 cursor 时,cursor 应从 connection 对象处继承 .errorhandler
的设置。
-
Connection.errorhandler, Cursor.errorhandler
以上两个属性均是可读/写属性,它们被用来引用一个错误处理器,以便在满足错误条件时调用。
错误处理程序必须是一个可调用的 Python 对象,且具备以下参数:
errorhandler(connection, cursor, errorclass, errorvalue)
参数说明:
-
connection
- cursor 操作所属的 connection 对象 -
cursor
- cursor 对象,如果错误处理器不会应用于 cursor 对象,则实参值为None
-
errorclass
- error 类,会使用errorvalue
作为构造参数进行实例化
-
标准错误处理器应将错误信息添加到相应的 .messages
属性(Connection.messages
或Cursor.messages
),并抛出由 errorclass
和 errorvalue
定的异常。
如果没有设置 .errorhandler
(即该字段值是 None
),则会采用外层的标准错误处理方案。
Warning Message: "DB-API extension .errorhandler used"
可选二阶段提交扩展
—— Optional Two-Phase Commit Extensions
许多数据库都支持"二阶段提交(two-phase commit - TPC)"。TPC 允许在管理事务时可以跨多个数据库连接和其它资源。
❓Q: 什么是 TPC ?
A: 二阶段提交(英语:Two-phase Commit)是指在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法。另见:
- https://zh.wikipedia.org/wiki...
- https://en.wikipedia.org/wiki...
如果数据库后端支持 TPC,并且数据库模块的作者希望公开支持 TPC,则应实现以下 API。如果只能在运行时才能检查数据库后端对 TPC 的支持,当遇到不支持的情况时,应抛出 NotSupportedError
。
TPC Transaction IDs...
—— TPC 事务 ID
大多数数据库都遵循 XA 规范,因此 transaction ID 应包含以下三个组件:
- a format ID
- a global transaction ID
- a branch qualifier - 分支限定符
对于特定的全局事务(global transaction),前两个组件对于所有资源应该都是相同的。应为全局事务中的每个资源分配不同的 branch qualifier。
上述组件必须满足以下标准:
- format ID: 一个 32 位的非负整数
- global transaction ID & branch qualifier: 不超过 64 个字符的 byte 字符串
应使用 .xid()
Connection 方法创建 transaction ID:
- .xid(format_id, global_transaction_id, branch_qualifier)
返回一个适合传递给 connection 的
.tpc_*()
方法的自定义 transaction ID 对象。如果数据连接不支持 TPC,应抛出
NotSupportedError
异常。.xid()
返回的自定义 transaction ID 对象的类型,并没有强制要求,但 transaction ID 对象必须具备"序列"行为,并允许访问其中的三个组件。一个合格的数据库模块在表示 transaction ID 时,应该既可以使用自定义对象,也可以使用 transaction ID。
TPC Connection Methods...
- .tpc_begin(xid)
使用指定的 xid (transaction ID) 来开始一个 TPC 事务。
调用此方法时,应位于事务的外部,即自上次调用
.commit()
或rollback()
以来没有执行过任何操作。此外,在 TPC 事务中调用
.commit()
或.rollback()
会导致 error。如果应用程序在激活 TPC 事务后调用.commit()
或.rollback()
,应抛出ProgrammingError
异常。如果数据库连接不支持 TPC,应抛出
NotSupportedError
异常。 - .tpc_prepare()
调用
.tpc_prepare()
开始一个 TPC 事务后,使用.tpc_prepare()
来执行事务的第一阶段。如果在 TPC 事务外部调用.tpc_prepare()
,应抛出ProgrammingError
异常。在调用
.tpc_prepare()
之后,直至调用.tpc_commit()
或.tpc_rollback()
之前不能执行任何语句。 - .tpc_commit([ xid ])
以无参数形式调用
.tpc_commit()
时,该方法会提交之前使用.tpc_prepare()
准备的 TPC 事务。如果在
.tpc_prepare()
之前调用.tpc_commit()
,则会执行一个单阶段提交(single phase commit)。如果仅有一个资源参与到全局事务中,那么事务管理器可以选择执行此操作。如果在调用
.tpc_commit()
时传递了 xid (transaction ID) ,数据库则会提交指定的事务。如果所提供的 xid 无效,应抛出ProgrammingError
异常。应在事务外部使用这种调用形式,用来执行恢复操作。该方法返回时,表示 TPC 事务已结束。
- .tpc_rollback([ xid ])
以无参数形式调用
.rollback()
时,该方法会回滚一个 TPC 事务。在.tpc_prepare()
前后均可调用.rollback()
。如果在调用
.rollback()
时传递了 xid (transaction ID) ,则会回滚指定事务。如果所提供的 xid 无效,应抛出ProgrammingError
异常。应在事务外部使用这种调用形式,用来执行恢复操作。该方法返回时,表示 TPC 事务已结束。
- .tpc_recover()
返回一个适用于
.tpc_commit(xid)
或.tpc_rollback(xid)
的列表,列表中的项由挂起的 transaction ID 组成。如果数据库不支持事务恢复,应返回一个空列表,或抛出
NotSupportedError
异常。
常见的问题
在数据库 SIG (Special Interest Groups - 特别兴趣组) 中经常会重复出现一些与 DB API 规范相关的问题。本节将涵盖一些与本规范相关的常见文通。
问: 如何从 .fetch*()
返回的元组中构造一个字典?
答: 有几种现成的工具可以帮你完成这项任务。这些工具大多会使用 cursor.description
属性中定义的列名来作为行字典的键。
为什么不扩展 DB API 规范,以使 fetch*()
返回字典形式的内容喃?因为这样的做法存在以下弊端:
- 某些数据库的列名对大小写不敏感,或者会自动将列名全部转为小写(或大写)字符
- 结果集中的列是通过 SQL 查询(例如使用 SQL 函数)产生的,它不一定与数据库表中的列名匹配,数据库通常会用一种数据库特有的方式来生成列名。
因此,在不同的数据库中,用来访问列的字典的键是不同的,这使得无法编写具备可移植性的代码。
1.0 和 2.0 的主要区别
相较于 1.0 版,在 2.0 中引入了一些重大改动。2.0 中的某些改动将导致现有的基于 DB API 1.0 的脚本发生终端,因此调整了主版本号以反映此更改的力度。
从 1.0 和 2.0 到最重要的变化如下:
- 移除了对单独的 dbi 模块的需求,并将其包含的功能合并到模块接口自身中。
- 为 date/time 值添加了新的 构造函数 和 Type Objects,
RAW
Type Object 被重命名为BINARY
。结果集应涵盖现代 SQL 数据库常见的所有基本数据类型。 - 添加了新的常量(
apilevel
,threadsfety
,paramstyle
)和方法(executemany()
,nextset()
),以便提供更好的数据库绑定。 - 现在清楚定了调用存储过程所需的
.callproc()
的语义 - 修改了
.execute()
返回值的定义。以前,返回值基于 SQL 语句类型,很难正确实现;现在,改为未定义。以前.execute()
可能会返回结果集的行数,现在请使用.rowcount
。模块可以自由返回旧样式返回值,但这不再是规范的强制要求的,应该被视为依赖于数据库接口。 - 基于 exceptions 的异常类被纳入规范。模块实现者可以通过继承定义的异常类来自由扩展本规范中定义的异常布局。
在 DB API 2.0 规范发布后,添加的内容:
- 为核心 DB API 2.0 规提供了一组可选 DB API 扩展。
已知问题
尽管 2.0 规范中澄清了 1.0 版中尚未解决的许多问题,但仍有一些问题需要在未来版本中解决:
-
.nextset()
的返回值应能表现出新结果集的可用情况 - 集成十进制模块(decimal module) Decimal 对象,以用作无损货币和十进制交换格式。
欢迎关注公众号: import hello