精通 Oracle+Python,第 4 部分:事务和大型对象
通过 Python 管理数据事务、处理大型对象
2010 年 3 月发布
事务包含一组 SQL 语句,这组 SQL 语句构成数据库中的一个逻辑操作,如转帐或信用卡支付操作。将 SQL 语句聚合到一个逻辑组中,其效果完全取决于事务的成败,事务成功则提交更改,事务失败则撤销内部 SQL 的结果(整体撤消)。通过 Python,您可以利用 Oracle 数据库所提供的原子性、一致性、孤立性和持久性优势。
利用大型对象,可在一列中保存大量数据(从 Oracle Databaase 11g 起该数量可达到 128TB),但这种灵活性是要付出代价的 — 用于访问和操作 LOB 的方法不同于常规查询方法。
注意:Python 的 2.x 版本已升级到 2.6,cx_Oracle 模块已发展到 5.0。从现在起,在 MO+P 中将使用这些版本。此外,本教程依旧基于可用于 Oracle Database 10g 第 2 版快捷版中的 HR 模式。
这就是 ACID
一个数据库事务是一些语句组成的一个逻辑组,具有以下四个特征:
- 原子性:所有操作要么全部成功,要么全部失败
- 一致性:提交事务不会导致数据损坏
- 孤立性:其他事务始终不知道此事务的执行
- 持久性:即使在数据库崩溃的情况下,事务中提交的操作也将持续有效。
Python Oracle 数据库 API 提供了一种处理事务的自然方式,可将对数据的逻辑操作存储在数据库回滚段中以等待最终的决定:是提交还是回滚整组语句。
当第一条 SQL 语句通过 cursor.execute() 方法传给数据库时,一个事务就启动了。当没有其他事务已从该会话启动时,可以使用 db.begin() 方法显式启动一个新事务。为了获得最高一致性,当连接对象被关闭或删除时,cx_Oracle 会默认回滚所有事务。
cx_Oracle.Connection.autocommit 属性仍可设置为 1,从而使 Oracle 可提交通过 cursor.execute* 系列方法发出的每条语句。开发人员还应知道,由于 Oracle 的 DDL 不是事务性的,所有 DDL 语句都会隐式提交。最后,与 SQL*Plus 相反,用 db.close() 关闭连接不会提交正在进行的事务。
有一些 Oracle 事务语句包装在 cx_Oracle 中,如 db.begin()、db.commit() 和 db.rollback(),但您可通过显式地调用 SET TRANSACTION 语句来使用其余事务语句。无论如何,新的事务从会话中的首个 DML 开始,因此没必要显式进行,除非您需要利用特定的事务属性,如 SET TRANSACTION [ READ ONLY | ISOLATION LEVEL SERIALIZABLE ]。
我们来看一个执行具有事务特征的 HR 操作的类:
import cx_Oracle class HR:
def __enter__(self):
self.__db = cx_Oracle.Connection("hr/hrpwd@localhost:1521/XE")
self.__cursor = self.__db.cursor()
return self def __exit__(self, type, value, traceback):
self.__db.close() def swapDepartments(self, employee_id1, employee_id2):
assert employee_id1!=employee_id2 select_sql = """select employee_id, department_id from employees
where employee_id in (:1, :2)"""
update_sql = "update employees set department_id=:1 where employee_id=:2" self.__db.begin()
self.__cursor.execute(select_sql, (employee_id1, employee_id2))
D = dict(self.__cursor.fetchall())
self.__cursor.execute(update_sql, (D[employee_id2], employee_id1))
self.__cursor.execute(update_sql, (D[employee_id1], employee_id2))
self.__db.commit() def raiseSalary(self, employee_ids, raise_pct):
update_sql = "update employees set salary=salary*:1 where employee_id=:2" self.__db.begin() for employee_id in employee_ids:
try:
self.__cursor.execute(update_sql, [1+raise_pct/100, employee_id])
assert self.__cursor.rowcount==1
except AssertionError:
self.__db.rollback()
raise Warning, "invalid employee_id (%s)" % employee_id self.__db.commit() if __name__ == "__main__":
with HR() as hr:
hr.swapDepartments(106, 116)
hr.raiseSalary([102, 106, 116], 20)
上述代码中定义了两个方法:一个方法可在指定的员工之间互换部门,另一个方法可为任意数量的员工加薪。为了安全起见,此类操作只能通过事务实现。为确保不会发生其他事务,这两个方法中显式调用了 self.__db.begin()(否则会引发 DatabaseError ORA-01453 错误)。双下划线前缀在 Python 中具有特殊含义,因为该语言实际上不允许您声明私有变量(类中一切皆为公有),从而增加了变量的访问难度:变量是通过 _Class__Member 语法(上例中的 _HR__db 和 _HR_cursor)公开的。同时,我们在该类中声明 __enter__ 和 __exit__ 方法,这样我们可以使用 WITH 语句,在 WITH 代码块结束时会自动关闭连接。
除了通过 db.commit() 和 db.rollback() 语句执行 DCL 语句的标准方式外,还可使用 cursor.execute() 方法运行原始的 SQL 命令(例如,为了能够使用保存点)。使用 cursor.execute('ROLLBACK') 和 db.rollback() 除以下之外并无任何不同:前者可以带额外的参数,如 cursor.execute('ROLLBACK TO SAVEPOINT some_savepoint')。SQL 方法还需要对命令像一般 SQL 语句一样解析和执行,而 db.commit() 和 db.rollback() 方法映射到一个低级 API 调用并允许 cx_Oracle 驱动程序跟踪事务状态。
大型对象 (LOB)
提到 Oracle 数据库的表列可用的数据类型,VARCHAR2 最多只能存储 4000 个字节的值。大型对象以其存储大型数据(如文本、图像、视频和其他多媒体格式)的能力而适用于大容量存储的情况。并且以您几乎不能称之为有限的存储能力而适用于这种情况 — 一个 LOB 的最大容量可高达 128 TB(自 11g 第 2 版开始)。
在 Oracle 数据库中可以使用几种类型的 LOB:二进制大型对象 (BLOB)、字符大型对象 (CLOB)、国家字符集大型对象 (NCLOB) 和外部二进制文件 (BFILE)。最后这种 LOB 用于以只读模式访问外部操作系统文件,而所有其他类型的 LOB 能以永久模式或临时模式在数据库中存储大量数据。每个 LOB 包含一个实际值和一个指向该值的小型定位器。传递 LOB 通常只是意味着传递 LOB 定位器。
在任何给定时间,一个 LOB 只能处于以下三种已定义状态之一:NULL、empty 或 populated。这类似于其他 RDBMS 引擎中常规 VARCHAR 列的行为(empty 字符串不等同于 NULL)。最后,对 LOB 有几个限制,其中的主要限制是:
- LOB 不能是主键
- LOB 不能是集群的一部分
- LOB 不能与 DISTINCT、ORDER BY 和 GROUP BY 子句一起使用
Oracle Database Application Developer's Guide中提供了有关大型对象的大量文档资料。
Python 的 cx_Oracle 模块支持对所有类型 LOB 的访问:
>>> [i for i in dir(cx_Oracle) if i.endswith('LOB') or i=='BFILE']
['BFILE', 'BLOB', 'CLOB', 'LOB', 'NCLOB']
cx_Oracle 中针对所有那些不能自动断定其长度或类型的 Oracle 类型提供了一种特殊的数据类型。该数据类型专门用于处理存储过程的 IN/OUT 参数。我们来看一个变量对象,它填充了 1 MB 的数据(将一个哈希字符重复 2 的 20 次方):
>>> db = cx_Oracle.Connection('hr/hrpwd@localhost:1521/XE')
>>> cursor = db.cursor()
>>> clob = cursor.var(cx_Oracle.CLOB)
>>> clob.setvalue(0, '#'*2**20)
为了在 Python 中创建一个 LOB 对象,我们将一个 cx_Oracle.CLOB 类型传递给了该 Variable 对象的构造函数,该对象提供两个基本方法(另外还有别的方法):
- getvalue(pos=0) 用于获取给定位置(默认为 0)的值
- setvalue(pos, value) 用于在给定位置设置值
为了进行一些 LOB 试验,我们使用下面的 DDL 创建如下所列的一些对象:
CREATE TABLE lobs (
c CLOB,
nc NCLOB,
b BLOB,
bf BFILE
); CREATE VIEW v_lobs AS
SELECT
ROWID id,
c,
nc,
b,
bf,
dbms_lob.getlength(c) c_len,
dbms_lob.getlength(nc) nc_len,
dbms_lob.getlength(b) b_len,
dbms_lob.getlength(bf) bf_len
FROM lobs;
在本示例中,BFILE 定位器将指向 /tmp 目录中的一个文件。以 SYSTEM 用户身份运行:
create directory lob_dir AS '/tmp';
grant read on directory lob_dir to HR;
最后,使用编辑器创建 /tmp/example.txt 文件,其中包含您所选的任何虚拟文本。
为了深入了解用于 LOB 表的默认的表创建选项,试着用 DBMS_METADATA.GET_DDL 过程生成全部 DDL:
SET LONG 5000
SELECT
DBMS_METADATA.GET_DDL('TABLE', TABLE_NAME)
FROM USER_TABLES
WHERE TABLE_NAME = 'LOBS';
输出结果中有两个有趣的参数值得一瞧:
- ENABLE STORAGE IN ROW 指示 Oracle 在 LOB 数据不超过 4000 个字节减去系统元数据这个大小时,尝试将该 LOB 数据与表数据放在一起(相反的参数是 DISABLE STORAGE IN ROW)。
- CHUNK 确定处理 LOB 时分配的字节数(在 Python 中可通过 cx_Oracle.LOB.getchunksize() 方法访问该信息)。
创建表时可使用这些选项对其行为和性能进行调优。
以下代码是使用大型对象的一个更为完整的示例。其中显示了四种不同类型的 LOB 以及用于将 LOB 插入数据库或从数据库选择 LOB 的四个方法。
# -*- coding: utf8 -*-
import cx_Oracle
import operator
import os
from hashlib import md5
from random import randint class LobExample:
def __enter__(self):
self.__db = cx_Oracle.Connection("hr/hrpwd@localhost:1521/XE")
self.__cursor = self.__db.cursor()
return self def __exit__(self, type, value, traceback):
# calling close methods on cursor and connection -
# this technique can be used to close arbitrary number of cursors
map(operator.methodcaller("close"), (self.__cursor, self.__db)) def clob(self):
# populate the table with large data (1MB per insert) and then
# select the data including dbms_lob.getlength for validating assertion
self.__cursor.execute("INSERT INTO lobs(c) VALUES(:1)", ["~"*2**20])
self.__cursor.execute("SELECT c, c_len FROM v_lobs WHERE c IS NOT NULL")
c, c_len = self.__cursor.fetchone()
clob_data = c.read()
assert len(clob_data)==c_len
self.__db.rollback() def nclob(self):
unicode_data = u"€"*2**20
# define variable object holding the nclob unicode data
nclob_var = self.__cursor.var(cx_Oracle.NCLOB)
nclob_var.setvalue(0, unicode_data)
self.__cursor.execute("INSERT INTO lobs(nc) VALUES(:1)", [nclob_var])
self.__cursor.execute("SELECT nc, nc_len FROM v_lobs WHERE nc IS NOT NULL")
nc, nc_len = self.__cursor.fetchone()
# reading only the first character just to check if encoding is right
nclob_substr = nc.read(1, 1)
assert nclob_substr==u"€"
self.__db.rollback() def blob(self):
# preparing the sample binary data with random 0-255 int and chr function
binary_data = "".join(chr(randint(0, 255)) for c in xrange(2**2))
binary_md5 = md5(binary_data).hexdigest()
binary_var = self.__cursor.var(cx_Oracle.BLOB)
binary_var.setvalue(0, binary_data)
self.__cursor.execute("INSERT INTO lobs(b) VALUES(:1)", [binary_var])
self.__cursor.execute("SELECT b FROM v_lobs WHERE b IS NOT NULL")
b, = self.__cursor.fetchone()
blob_data = b.read()
blob_md5 = md5(blob_data).hexdigest()
# data par is measured in hashes equality, what comes in must come out
assert binary_md5==blob_md5
self.__db.rollback() def bfile(self):
# to insert bfile we need to use the bfilename function
self.__cursor.execute("INSERT INTO lobs(bf) VALUES(BFILENAME(:1, :2))",
["LOB_DIR", "example.txt"])
self.__cursor.execute("SELECT bf FROM v_lobs WHERE bf IS NOT NULL")
# selecting is as simple as reading other types of large objects
bf, = self.__cursor.fetchone()
bfile_data = bf.read()
assert bfile_data
self.__db.rollback() if __name__ == "__main__":
with LobExample() as eg:
eg.clob()
eg.nclob()
eg.blob()
eg.bfile()
该文件包含 UTF-8 字符,因此第一行包含 Python 的源代码编码声明 (PEP-0263)。为确保以该编码方式将数据传输给数据库,应将环境变量 NLS_LANG 设置为“.AL32UTF8”。这向 Oracle Client 库指明了应使用哪个字符集。该变量的设置应在 Oracle Client 初始化其内部数据结构之前进行,但不能保证发生在程序的哪个特定点。为安全起见,最好在调用该程序的 shell 环境中设置该变量。源代码中包含了其他一些解释和注释。
这里要注意几点。就 NCLOB 示例而言,unicode_data 不能用作绑定变量,因为如果它超过 4000 个字符,会引发“ValueError:unicode data too large”异常。BLOB 示例中将出现类似的问题。如果我们不使用 binary_var,则会引发“DatabaseError:ORA-01465:invalid hex number”异常,因为必须显式地声明绑定的内容。
通过 LOB 参数(IN 或者 OUT)调用存储过程还需要使用 cx_Oracle 的 Variable 对象,但这是本系列另一部分要讨论的内容。
总结
在本教程中,您已经了解 Python 环境中有关事务处理和大型对象处理的各个方面。现在您应该已经熟悉了 cx_Oracle 模块在事务封装和访问所有四种 LOB 以及应对 UTF-8 编码需求这些方面的特点。
精通 Oracle+Python,第 4 部分:事务和大型对象的更多相关文章
- 精通 Oracle+Python,第 1 部分:查询最佳应践
原文链接:http://www.oracle.com/technetwork/cn/articles/dsl/mastering-oracle-python-1391323-zhs.html 在 Py ...
- 精通 Oracle+Python,第 9 部分:Jython 和 IronPython — 在 Python 中使用 JDBC 和 ODP.NET
成功的编程语言总是会成为顶级开发平台.对于 Python 和世界上的两个顶级编程环境 Java 和 Microsoft .NET 来说的确如此. 虽然人们因为 Python 能够快速组装不同的软件组件 ...
- 精通 Oracle+Python,第 8 部分:适合 Oracle DBA 使用的 Python
传统上,当需要为操作系统编写一些脚本时,人们常常会选用 Bash 或 Perl 脚本工具.这些工具易于使用,因而它们几乎变得无处不在,渗透到了包括 Oracle Database 在内的其他软件中,O ...
- 精通 Oracle+Python,第 6 部分:Python 支持 XML
无可辩驳的是,XML 现在是软件中信息交换的实际标准. 因此,Oracle 数据库附带了各种与 XML 相关的增强和工具,它们统称为 Oracle XML DB.XML DB 包含一系列嵌入到数据库中 ...
- 精通 Oracle+Python,第 7 部分:面向服务的 Python 架构
面向服务的架构 (SOA) 在当今的业务战略中具有至关重要的作用.混搭企业组件已成为所有任务关键的企业应用程序的标准要求,从而确保在企业架构的各层实现顺畅的服务编排.对此,Python 是一个不错的选 ...
- 精通 Oracle+Python,第 5 部分:存储过程、Python 编程
调用数据库存储过程及其他感兴趣的高级 Python 编程功能. 2010 年 3 月发布 对于涉及数据库的软件开发来说,有两种主流开发方法:一种是在应用程序中(对于三层体系结构,也可以是在中间件中)实 ...
- 精通 Oracle+Python,第 3 部分:数据解析
进行数据解析的理由不计其数,相关的工具和技巧也同样如此.但是,当您需要用这些数据做一些新的事情时,即使有“合适的”工具可能也是不够的.这一担心对于异类数据源的集成同样存在.用来做这项工作的合适工具迟早 ...
- 精通 Oracle+Python,第 2 部分:处理时间和日期
从 Python 2.4 版开始,cx_Oracle 自身可以处理 DATE 和 TIMESTAMP 数据类型,将这些列的值映射到 Python 的 datetime 模块的 datetime 对象中 ...
- 《精通Oracle SQL(第2版) 》
<精通Oracle SQL(第2版) > 基本信息 作者: (美)Karen Morton Kerry Osborne Robyn Sands Riyaj Shamsud ...
随机推荐
- .net互转java 转行必备
.net与java其实是差不多的语言,学习起来只需要弄清楚差异及查库的方法,转起来还是很快的 以下列出几点,希望能给正在转行的你一些帮助 1,java与c#语言超详细对比 http://www.har ...
- a href=#与 a href=javascript:void(0) 的差别
a href="#"> 点击链接后,页面会向上滚到页首,# 默认锚点为 #TOP <a href="javascript:void(0)" onCl ...
- mybatis10 实现类代理对象开发
mapper实现类代理对象开发 要想让mybatis自动创建dao接口实现类的代理对象,必须遵循一些规则: SqlSession sqlSession = sqlSessionFactory.open ...
- SQL Server存储内幕系列
http://blog.itpub.net/355374/list/1/?cid=75087
- hadoop错误Operation category READ is not supported in state standby
报如下错误 解决方法: 方法一:(结果不起作用) 通过Shell命令方式,hadoop/bin/hdfs haadmin -failover --forceactive hadoop2 hadoop1 ...
- js验证邮箱
<html> <head> <script> function verifyAddress(obj) { var email = obj ...
- HDU2084JAVA
数塔 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submissi ...
- ORA-00928: 缺失 SELECT 关键字
ORA-00928: 缺失 SELECT 关键字 一般是表的列名使用了关键字,解决办法就是加双引号 来自为知笔记(Wiz)
- Java根据ip地址获取Mac地址,Java获取Mac地址
Java根据ip地址获取Mac地址,Java获取Mac地址 >>>>>>>>>>>>>>>>>&g ...
- 使用Android Studio时so文件打包不到APK中
1,需要在build中添加如下配置,这是必备的 Android { sourceSets { main { jniLibs.srcDirs = ['libs'] ...