采用 Python 进行快速原型开发,藉此获得极高的数据库管理效率。
2011 年 12 月发布
![red_arrow_box.gif](http://www.oracle.com/ocom/groups/public/@ocom/documents/digitalasset/019677.gif)
传统上,当需要为操作系统编写一些脚本时,人们常常会选用 Bash 或 Perl 脚本工具。这些工具易于使用,因而它们几乎变得无处不在,渗透到了包括 Oracle Database 在内的其他软件中,Oracle Database 在很大程度上依赖它们执行各种管理任务。
但是最近,这种趋势有所转变,转向有利于 Python 这类较新的编程工具。Python 可为我们提供直观的开发以及各种灵活的数据结构和库。所有的现代 Unix 和 Linux 系统都附带了 Python;例如,Oracle Linux 6.1 附带了 Python 2.6.6。
本教程将介绍对数据库管理员尤为有用的一些 Python 特性,无论这些管理员要实现一次性代码段还是完全可重用的程序,这些特性对他们来说都十分有用。在本部分中,我们将探究如何与操作系统和远程资源交互,然后了解各种压缩和文件系统遍历模块。
出于本教程的目的,我们将在 Oracle Linux 6.1 和 Python 2.6.6 环境中使用 Oracle Database 11g 快捷版 (XE)。
与文件系统交互
Python 用来与操作系统交互的核心库是 os 模块,您可以通过此模块处理系统进程、识别平台、处理操作系统管道以及使用环境变量 — 以 100 多个函数和变量的形式。
检测当前平台如同访问 os 模块中的预定义字符串那样容易。以下示例展示了 Oracle Linux 6.1 上的结果,并且还显示了此操作系统的默认路径分隔符。
>>> import os
>>> os.name
‘posix’
>>> os.sep
‘/’
通过 os.environ 可以访问所有 Oracle 环境变量的列表。以下示例利用一个内联生成器表达式:
>>> import os
>>> oracle_vars = dict((a,b) for a,b in os.environ.items() if a.find('ORACLE')>=0)
>>> from pprint import pprint
>>> pprint(oracle_vars)
{'ORACLE_HOME': '/u01/app/oracle/product/11.2.0/xe', 'ORACLE_SID':'XE'}
这相当于
SELECT key, value
FROM os.environ
WHERE key LIKE ‘%ORACLE%’
(若用 SQL 编写)。
当我们进一步探索时,我们开始查看文件系统并了解所处位置。下表列出了最常用的文件系统访问函数及其描述。
函数 | 作用 |
os.getcwd() | 获取操作系统中的当前工作目录 |
os.chdir(path) | 将目录更改为给定 path |
os.chroot(path) | 将当前 Python 进程的根路径更改为 path |
os.chown(path, uid, gid) | 与 chmod Linux 命令相同(uid 和 gid 是编号) |
os.listdir(path) | 列出给定 path 下的文件和目录 |
os.mkdir(path, mode) | 在给定 path 下创建目录,并将八进制权限设置为 mode(默认为 0777) |
os.remove(path) | 删除 path 下的一个文件 |
os.rmdir(path) | 删除 path 下的目录 |
os.rename(path,newpath) | 将 path 重命名为 newpath |
os.stat(path) | 使用 OS stat() 调用显示 path 的属性 |
os.walk(path, topdown,onerror, followlinks) | 针对 path 下的文件系统树返回生成器返回字节组(路径、目录、文件) |
掌握了支持文件系统浏览的基本功能之后,我们来了解一下如何使用 Python 快速查看旧的跟踪文件和“未轮转”日志的列表并显示它们使用了多少空间。清单 1 中的程序需要两个参数:Oracle 日志路径(DIAGNOSTIC_DEST 指向的目录)和文件被视为过时的天数。此示例基于 os.walk。
清单 1. walk.py:Oracle 诊断目录下的旧的日志和跟踪文件
import datetime
import os
import sys
import time
from pprint import pprint
def readable(size):
si=('B','KB','MB','GB','TB', 'PB', 'EB', 'ZB', 'YB')
div = [n for n, m in enumerate(si) if pow(1024, n+1)>size][0]
return "%.1f%s"%(size/float(pow(1024, div)), si[div])
total = {"log":0, "trace":0}
for path, dirs, files in os.walk(sys.argv[1]):
for f in files:
filepath = path+os.sep+f
if os.stat(filepath).st_mtime>time.time()-(3600*24*int(sys.argv[2])):
size = readable(os.path.getsize(filepath))
age = datetime.datetime.fromtimestamp(os.stat(filepath).st_mtime)
if f in ("log.xml", "alert.log", "listener.log"):
filetype = "log"
elif f.endswith("trc") or f.endswith("trm"):
filetype = "trace"
else:
filetype = None
if filetype:
total[filetype] += os.path.getsize(filepath)
for a, b in total.items():
total[a] = readable(b)
pprint(total)
运行 walk.py 得到如下输出:
$ python walk.py /home/oracle/app 10
{'log': '132.0MB', 'trace':'0.0B'}
在 os 命名空间中,另有一个名为 os.path 的模块,用于解决路径名称操作。它包含适用于不同系统的平台敏感的实现,因此导入 os.path 将始终获得正确的操作系统版本。
os.path 模块中的常用函数包括:
- basename(path),用于获得给定路径的叶名称
- dirname(path),用于获得文件路径的目录部分;它由 split(path) 函数加以补充,后者返回包含隔开的目录部分和文件部分的字节组
- exists(path),用于查看路径下是否存在文件,针对无法解析的符号链接返回 False
- getsize(path),用于快速查看路径下的字节数
- isfile(path) 和 isdir(path),用于解析路径类型
虽然目前为止我们已经了解了一些丰富的文件系统浏览功能,但我们也只是初涉皮毛,因为还有多个其他模块。例如,filecmp 模块既能够比较文件又能够比较目录,tempfile 可以轻松地管理临时文件,glob 解析符合 Unix 式模式的文件路径(如在 ora_pmon_*.trc、log_*.xml 中,等等),非常有用的 shutil 模块实现高级文件系统操作,如复制和删除多个文件或整个文件树。
与进程通信
os 模块并不仅限于文件管理。还可以用来与系统进程交互和生成系统进程,以及执行系统 kill 和 nice 调用。下表列出了最有用的进程管理函数。这些函数只对 Unix 和 Linux 平台有效,但在 Python 3.2 分支中正在进行一些工作以使这些函数可用于 Windows。
函数 | 作用 |
os.abort() | 向当前 Python 进程发送 SIGABRT |
os.exec*(path, arg1...argN,environ) | exec* 函数系列,用于以 path 指定的进程取代当前进程,可选择提供命令行参数和环境变量 |
os.kill(pid, signal) | 向给定 pid 发送 signal |
os.nice(value) | 更改当前进程的 nice 值 |
os.popen(command, mode,buffersize) | 对给定 command 打开一个未命名管道,有效地实现与进程的进一步交互;mode 表示管道打开处理属性(默认为“r”,表示读取) |
os.spawn*(mode, path,environ) | 在一个新的进程中运行 path 下的程序(这些函数现在已被 subprocess 模块弃用) |
os.system(command) | 此函数通过操作系统 system() 调用(该调用可用于 Unix 和 Windows)运行由 command 定义的新进程 |
虽然其中许多函数可能在较旧的 Python 版本中派上用场,但从版本 2.4 开始,专门创建了一个专用的 subprocess 模块来管理进程。这个新模块最初在 2003 年提交到 Python 增强建议索引 (PEP),现在成为与系统进程通信的首选方法。
Subprocess 以简单、可用并且相当通用的接口取代 os.popen、 os.spawn* 和 os.system 函数。清单 2 显示了 ps.py 程序的代码,此程序执行 ps aux 命令并将结果移到 Python 字典中。这里使用了一个管道来作为 stdout 的目标以捕获所有信息,并阻止输出到屏幕。
清单 2. ps.py:将系统进程映射移到 Python 字典中
import re
import subprocess
args = ['ps', 'aux']
ps = subprocess.Popen(args, stdout=subprocess.PIPE)
processes = ps.stdout.readlines()
header = re.split('\s+', processes.pop(0))[:-1]
header.remove('COMMAND')
PS = {}
for process in processes:
columns = re.split('\s+', process)
if columns[0]!='oracle':
continue
PS[int(columns[1])] = {}
for position, column in enumerate(columns[:9]):
PS[int(columns[1])][header[position].lower()] = column
PS[int(columns[1])]['command'] = ' '.join(columns[10:])
from pprint import pprint
pprint(PS)
输出如下:
...
25892: {'%cpu': '0.0',
'%mem': '3.9',
'command': 'xe_w000_XE ',
'pid': '25892',
'rss': '23672',
'start': '16:02',
'stat': 'Ss',
'tty': '?',
'user': 'oracle',
'vsz': '457240'},
26142: {'%cpu': '2.0',
'%mem': '0.9',
'command': 'python proc.py ',
'pid': '26142',
'rss': '5732',
'start': '16:36',
'stat': 'S+',
'tty': 'pts/2',
'user': 'oracle',
'vsz': '160776'},
26143: {'%cpu': '0.0',
'%mem': '0.1',
'command': 'ps aux ',
'pid': '26143',
'rss': '1100',
'start': '16:36',
'stat': 'R+',
'tty': 'pts/2',
'user': 'oracle',
'vsz':'108044'}}
popen 函数接受多个关键字参数,如 stdin/stdout/stderr 描述符、用于设置进程工作目录的 cwd,或者设置子进程环境变量的 env。要查看命令的状态,只需查看 returncode 属性。进程标识符在 pid 属性下提供。
针对已创建进程的方法包括用于查看进程是否仍在运行的 poll()、用于在程序完成时进行恢复的 wait()、用于发送特定信号的 send_signal(),以及分别用于发送 SIGTERM 或 SIGKILL 信号的 terminate() 或 kill()。最后,要与生成的子进程完全交互,我们使用 communicate() 函数发送 stdin 输入。
为了对此进行说明,我们来创建一个基于遗留的 SYSDBA 连接而发展的简单 SQL*Plus 包装器。
清单 3. sp.py:通过 Python 与 SQL*Plus 进程通信
import os
from subprocess import Popen, PIPE
sqlplus = Popen(["sqlplus", "-S", "/", "as", "sysdba"], stdout=PIPE, stdin=PIPE)
sqlplus.stdin.write("select sysdate from dual;"+os.linesep)
sqlplus.stdin.write("select count(*) from all_objects;"+os.linesep)
out, err = sqlplus.communicate()
print out
This return output similar to:
SYSDATE
--------------
02-DEC-11
COUNT(*)
--------------
76147
报告服务
涉及走出数据库的一项最令人头疼的任务是发送警报或推送从数据仓库提取的经常性报告。好消息是,Python 不仅成功实现了一个全球流行的邮件列表系统 — Mailman,而且还提供一个丰富的电子邮件处理库,此库支持 MIME、附件、消息编码以及与电子邮件处理有关的各个方面。email 模块将协议本身内容与表示层相分离以便仅专注于构建邮件消息,而交付工作通过 smtplib 模块处理。email.message 中的 Message 类代表用于处理电子邮件的核心类。email.mime 命名空间中的各个处理程序用于处理不同的附件类型。但在此示例中,我们将使用最通用的一个处理程序:email.mime.base 中的 MIMEBase。不过我们这方面略施小计,利用了电子表格软件将以表格格式打开 HTML 文件(如果它们具有 .xls 扩展名)的事实。我们还将利用 tempfile 模块的帮助。
Oracle Linux 并未预先安装 cx_Oracle 模块,因此您将需要从 cx-oracle.sourceforge.net 获得此模块。此外,为了能够导入 cx_Oracle 并使用网络配置文件,在启动 Python 解释器之前需要设置 ORACLE_HOME 和 LD_LIBRARY_PATH。
[root@xe ~]# rpm -ivh cx_Oracle-5.1-11g-py26-1.x86_64.rpm
Preparing...########################################### [100%]
1:cx_Oracle ########################################### [100%]
[root@xe ~]#
[root@xe ~]# su - oracle
[oracle@xe ~]$ export ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe
[oracle@xe ~]$ export LD_LIBRARY_PATH=$ORACLE_HOME/lib
请参见清单 4 了解完整程序,此程序连接到 Oracle Database 11 g XE,提取员工数据,并将此数据打包为电子表格附件以便发送到电子邮件组。
清单 4. report.py:电子邮件报告服务
import cx_Oracle
import datetime
import smtplib
import tempfile
from email.message import Message
from email.encoders import encode_base64
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
today = datetime.datetime.now()
msg = MIMEMultipart()
msg['From'] = 'Reports Service <reports@company.intranet>'
msg['To'] = 'receipients@company.intranet'
msg['Subject'] = 'Monthly employee report %d/%d ' % (today.month, today.year)
db = cx_Oracle.connect('hr', 'hrpwd', 'localhost/xe')
cursor = db.cursor()
cursor.execute("select * from employees order by 1")
report = tempfile.NamedTemporaryFile()
report.write("<table>")
for row in cursor:
report.write("<tr>")
for field in row:
report.write("<td>%s</td>" % field)
report.write("</tr>")
report.write("</table>")
report.flush()
cursor.close()
db.close()
attachment = MIMEBase('application', 'vnd.ms-excel')
report.file.seek(0)
attachment.set_payload(report.file.read())
encode_base64(attachment)
attachment.add_header('Content-Disposition', 'attachment;filename=emp_report_%d_%d.xls' % (today.month, today.year))
msg.attach(attachment)
emailserver = smtplib.SMTP("localhost")
emailserver.sendmail(msg['From'], msg['To'], msg.as_string())
emailserver.quit()
如果我们更进一步采用此示例,可以使用 Python 图形处理库 (PIL) 获取统计图,附加存储在数据库中的 BLOB 的缩略图,或者借助 ReportLab 生成 PDF 报告以便发送到相关组。email 模块的功能非常强大,足以应对每种可能的情形。