Python-DB2-基础知识-全-
Python DB2 基础知识(全)
一、Db2 简介
欢迎学习 Db2 简介。既然您在这里,那么您可能正在寻找开始使用 Db2 的地方。我们希望这本书将是你寻找第一步。这本书旨在介绍 Db2 环境和 Python 接口。本书的前半部分将在管理员和程序员都感兴趣的水平上介绍 Db2。它将涵盖您将在这两个角色中使用的 Db2 的许多方面。本书的后半部分将集中讨论如何使用 Python 编程语言与 Db2 接口。虽然主要面向程序员,但管理员也会发现它对他们的一些日常任务也很有用。
Db2 历史悠久,是第一个关系数据库实现。它最初是由 Edgar Frank“Ted”Codd 在 1969 年的一篇题为“大型共享数据库的数据关系模型”的论文中提出的,当时他在 IBM 位于加利福尼亚的圣何塞研究实验室工作。在接下来的四年中,IBM 研究人员致力于创建一个基于 Codd 论文中描述的原则的系统(称为 System R)。在此期间,显然需要一种新的语言来与新的系统进行交互。Codd 写了一篇新论文“一种基于关系演算的数据库子语言”,它成为了称为 DSL/Alpha 的新语言的基础。这很快经历了一些名称的变化,但最终被称为 SQL,结构化查询语言的缩写。
最终,在 20 世纪 70 年代,有人尝试将 DSL/Alpha 移植到 370 大型机环境中。它在 1982 年被重新命名为数据库 2。第二年,它以另一个名字 DB2 对公众开放。这是一个有限的版本,但是受到了客户的高度评价。客户实际上推动 IBM 向更广泛的客户提供 DB2。IBM 有些不情愿,因为他们试图保住他们的 IMS/DB 市场份额。但是最终客户赢了,DB2 开始扩展到其他平台,包括 OS/2、AIX/RS6000 和 Windows。
在接下来的二十年里,该产品经历了多次名称变更和几个平台代码库。最近,随着 11.1 版本的发布,IBM 重塑了整个产品线,并将代码库合并到少量代码库中。以下一组产品现在是标准产品:
-
Db2(以前的 DB2 LUW)
-
Db2 for z/OS(以前的 DB2 for z/OS)
-
Db2 托管(以前称为云上的 DB2)
-
云上的 Db2(以前是事务的 dashDB)
-
Db2 事件存储(一个新的用于事件驱动事务处理的内存数据库)
-
云上的 Db2 仓库(以前称为 dashDB)
-
Db2 仓库(以前是 dashDB Local)
-
IBM Integrated Analytics System(一个新的系统平台,结合了 IBM PureData 系统和 IBM Netezza 的分析性能和功能)
今天的 Db2 产品的代码库共享一个公共的代码库,这使得将代码移植到另一个硬件/软件平台成为一个相对容易的过程。SQL 代码库已经过标准化,因此在所有平台上都是相同的,从编程的角度来看,迁移到另一个平台是一件容易的事情。
使用嵌入式 SQL 编写应用也是当前的趋势。如果不对代码进行重大修改,这些类型的应用很难从一个平台移植到另一个平台。相反,IBM 正在(在可能的情况下)转向一种 API,可以调用该 API 来处理 SQL 语句,并利用编程语言变量将值添加到 SQL 语句中。这是用于 Python 的方法,我们将在本书的后面探讨。
过去,Db2 被认为对于大多数应用来说太大了。但是随着个人计算机变得越来越强大,然后即使是很少的人使用的数据库也变得非常大,Db2 在管理这些数据方面变得越来越有吸引力。此外,这些平台上 Db2 的定价模型变得更有竞争力。因此,如果您需要管理大量数据的性能和能力,Db2 可能是一个非常有吸引力的产品。
什么是关系数据库?
关系数据库实际上很难定义,因为在今天的市场上没有关系数据库系统真正实现 E. F. Codd 定义关系模型的 12 条规则。相反,商业关系数据库只实现了其中的七到九条规则。这足以使他们的产品在商业上有用,而不必承担执行其他规则的负担。事实上,其他规则对大多数数据库用户没有用,所以它们是否能在商业产品中实现还是个问题。事实上,大多数商业数据库供应商实际上是通过添加不是真正的表的实体来扩展关系模型的,比如 BLOBs(我们将在后面讨论)。
关系模型
粗略地说,关系数据库实现了用行和列表示的实体的概念。客户记录表中有一行代表一个客户。表的列(或元组)将包含该客户的属性。每一列都包含一个属性,并有一个定义的数据类型来限制它可以包含的信息类型。典型的客户地址表可以如表 1-1 所示实现。
表 1-1
示例客户表
|断续器
|
姓氏
|
LNAME
|
街道
|
状态
|
活力
|
| --- | --- | --- | --- | --- | --- |
| 000000001 | 疯狂的 | 兔子 | 鲁尼巷 1201 号 | 加利福尼亚 | Seventy-seven thousand seven hundred and seventy-seven |
| 000000002 | 马文 | 火星人 | 2342 年 3 月大道 | 马萨诸塞州 | Thirty thousand one hundred and forty-three |
| 000000003 | 疯狂的 | 杜克 | 白蚁梯田 5462 号 | 加利福尼亚 | Seventy-seven thousand seven hundred and forty-five |
| 000000004 | 埃尔默(Aylmer 的异体)(m.) | Fudd | 亨特湾 23 号 | 加利福尼亚 | Seventy-seven thousand seven hundred and thirty-two |
这个表是一个很好的例子,因为它显示了可以在关系数据库中表达的关系类型。CUSTNO 列为每个客户(或行)保存一个唯一的客户号。该列是该表的主键。FNAME 和 LNAME 标识客户的名和姓。街道是用于给客户开账单的地址。州列是地址所在的美国州。邮政编码列标识邮寄邮政编码。
域
域(或属性)将列中的数据限制为特定类型。例如,数据可能是特定类型的整数、货币值、固定或可变长度的字符串、日期或时间值,或者已经定义的一些其他域。
键和索引
表 1-1 中的 STATE 列是外键——即另一个表中的主键。可以设置一个规则,以便当您向 customer 表中添加一个新行时,系统检查您的 proposed STATE 字段中的值是否是 STATE 表中的一个有效条目。如果不是,则不会插入记录,并且会生成一个错误。键也称为索引。在表中创建主键时,会创建一个特殊的索引表来保存有效的键。该表类似于系统中的任何其他表;可以对其进行查询、添加和删除。因此,所有有效的键都可以插入到表中并在表中进行维护,并且可以像任何其他表一样进行维护。只有特殊的规则才能使索引表变得特殊。
关系
所有这些仅仅展示了可以创建(或派生)来正确维护一组表的一些关系。可能还会创建其他类型的客户表,如维护客户信用评级的表、客户送货地址表、客户联系人表等。所有这些都是与其他客户表有关系的专门化实体。这就是关系数据库的全部内容。这个想法是表达一种与数据组织的关系,所以数据只存储在需要的地方,希望只存储一次。
关系允许用户创建定制的报告,这些报告可以表达来自数据库的数据的定制视图。乍一看,这些报告可能看起来与数据库中包含的数据没有关系,但它们可以提供通过其他方式不容易实现的数据洞察力。
处理
关系数据库的另一个方面是支持事务。这意味着提供一种锁定机制,允许在其他人读取数据或修改其他数据的同时修改数据。这就是所谓的酸性测试,是以下测试的缩写:
-
原子性定义了组成完整数据库事务的所有元素。这可能构成一个或多个 SQL 语句。
-
一致性定义了在事务处理后将数据点保持在正确状态的规则。这通常是通过一个 SQL 语句完成的,比如 COMMIT 或 ROLLBACK。
-
隔离使事务的效果在提交之前对其他人不可见,以避免混淆。这是指数据库防止对数据库的多次更改破坏数据库完整性的能力。
-
持久性确保一旦提交事务,数据更改就变成永久的。这是通过确保在提交事务之前对所有已修改的表进行写入来实现的。
存储过程
关系数据库通常实现存储过程。这些是存储在数据库中的程序,用于在几种情况下实现特殊处理。这些过程通常涉及业务逻辑,需要在所有数据库用户中实现相同的业务逻辑。在许多情况下,只有存储过程具有修改、插入、删除或访问被操作数据的适当权限。这可以防止用户在没有正确权限的情况下修改数据。
限制
关系数据库的另一个特性是约束。这些使得进一步限制属性成为可能。例如,一个列可能被定义为一个整数。但是数字的范围实际上可能需要被限制在一个特定的范围内。这就是约束所提供的。
约束可以有多种形式,并有多种用途。它们存储在数据库中,在添加或修改数据时使用。
正常化
规范化是关系数据库的另一个属性。这种设计过程减少了非原子值的数量和数据库中的重复数据,从而防止了异常和完整性的损失。这个过程被称为规范化。
规范化过程通常由管理员在创建数据库的过程中执行。用户通常不参与此活动。
结构化查询语言
在创建第一个关系数据库的最初几年里,很明显需要某种东西来实际操作关系数据库中的数据。这种语言是由 IBM 的唐纳德·d·钱伯林和雷蒙德·f·博伊斯在 20 世纪 70 年代早期发明的。它最初被称为 SEQUEL,但是这个名字被注册了商标,并被改为结构化查询语言(SQL)。SQL 后来被 ANSI 标准化了,但是每个关系数据库产品都根据需要对其进行了扩展,以符合它们的产品或硬件平台。
SQL 分为四类语句:
-
数据定义语言
-
数据查询语言(DQL)
-
数据控制语言
-
数据操作语言
大多数 SQL 书籍将大部分资源花在 SQL 的 DML 部分,但其他部分也同样重要,尤其是对数据库管理员而言。
数据定义语言
数据定义语言子集由 CREATE、DROP、ALTER 和 TRUNCATE 语句组成。这些语句操作(或重新组织)数据库中的数据。
CREATE 语句在数据库中创建新实体。这可以包括表、索引、视图和过程。许多数据库系统也包括字符集、排序规则、事务和断言。
DROP 语句从数据库中删除实体。这些包括可以用 CREATE 语句创建的所有实体。一些数据库将用户作为一个可以删除的实体。
ALTER 语句可以改变现有实体,例如向表中添加新列。
TRUNCATE 语句从表或索引中删除所有数据。它比 DELETE SQL 语句快得多。
数据查询语言(DQL)
数据查询语言子集仅由 SELECT 语句组成。这用于数据库中的所有信息查询。SELECT 语句的语法将在第七章中介绍。
数据控制语言
数据控制语言子集由 GRANT 和 REVOKE 语句组成。这些语句授予和撤销数据库用户的权限。数据库系统指定可以授予用户的特权类型。一些数据库系统,如 SQLite,依赖于操作系统的特权机制,因此不支持 GRANT 和 REVOKE 语句。
数据操作语言
数据操作语言子集由 INSERT、UPDATE 和 DELETE 语句组成。有些系统还在这个子集中包含 SELECT 语句,但是由于 SELECT 不会对数据进行更改,所以将 SELECT 语句放在这里似乎有些奇怪。
INSERT 语句向表中添加新行。这个新数据也可以自动改变表格数据视图的内容。
UPDATE 语句改变现有表(以及依赖于该表的任何视图)中的一行或多行。该语句不会向表中添加新行,它只会改变现有数据。
DELETE 语句从表(以及依赖于该表的任何视图)中删除行。
ibm_db 项目
ibm_db项目开始于 2007 年的某个时候,2008 年 2 月 9 日向 GitHub 提交了第一份代码。该项目是 IBM 和 Python 社区共同努力的结果,目的是创建一个从 Python 到 Db2 和 Informix 的编程接口。大约在同一时间,Informix 被 IBM 收购,这是将 Informix 集成到 Db2 产品线项目的一部分。从那时起,该项目经历了稳步进展,最近进行了一些重大修复。
通过在 GitHub 上建立项目,人们认为在社区的支持下,项目不仅会随着时间的推移而改进,而且会从社区的参与中受益。事实证明,在项目的整个生命周期中,许多社区志愿者都为项目做出了贡献。
随着时间的推移,ibm_db_dbi模块被添加到项目中,并且基于ibm_db模块引入了新的项目来支持其他 Python 项目。这些项目包括ibm_db_django、ibm_db_alembic、ibm_db_sa。
有关更多信息,请参见第八章。
摘要
本章介绍了构成关系数据库的一些基本概念以及支持关系数据库的 SQL。几乎所有的关系数据库都有这种信息。
二、安装 Db2
本章介绍在 Linux 和 Windows 上安装 Db2。两种环境都使用相同的安装程序来执行用户 id 和权限的安装和创建。安装过程非常简单明了,但是在开始安装之前,您可能会遇到一些问题,安装程序无法回答这些问题。当我们遇到这些问题时,我们将尽力回答它们。
我的开发环境
我的开发环境不是你典型的 Windows home 系统。它由两台 Linux 服务器、一台 Windows 客户机和两台 Linux 客户机工作站组成。Linux 服务器不是典型的工作站,而是通常在小型企业环境中使用的专用服务器。我将尝试描述每个系统,以便您对我的环境有所了解。
第一个系统是安装了四个 2 TB 驱动器的 Lenovo ThinkSystem ST250 服务器。它有 32 GB 的内存和一个六核至强 CPU。这是我的主服务器,存放着我所有的文件。它可以通过我的防火墙经由 Apache Web 服务器在互联网上看到。操作系统是 Fedora 32。它还通过 Samba 托管我的 Windows 文件,并通过 libvirt 托管许多虚拟机。这是托管我的主要 Db2 数据库的系统。
第二个系统是联想 ThinkStation P320,256 GB 固态硬盘和 1 TB 硬盘,32 GB 内存,六核/六线程至强 CPU。这是我的开发服务器,实际上一年要改变两次配置。它目前运行的是 CentOS Linux 8.2。这台机器还托管了我的测试 Db2 环境。
第三台是我的 Windows 10 工作站。它通过服务器上的共享驱动器(Samba)连接到第一个 Linux 系统。这是我写作以及 Windows 开发和测试的地方。
第四个系统是联想 ThinkCentre M920,256 GB 固态硬盘,1 TB 硬盘,16 GB 内存。它有一个 i7 CPU。这是我的开发工作站,我在这里测试我所有的 Linux 开发和对服务器的远程访问。操作系统是 Fedora 32。
第五个系统是联想 ThinkPad T580,256 GB 固态硬盘,16 GB 内存,i7 CPU。它运行 Fedora 32,通常作为我的旅行机器,但也作为一个辅助测试设备。
安装先决条件
在启动 Db2 安装程序之前,需要满足一些先决条件,然后才能成功安装所有的东西。这本书的安装环境是 CentOS 8.2。该操作系统的先决条件对于 RHEL 8.2 应该是相同的,但是对于其他 Linux 环境可能有所不同。Windows 环境有自己的先决条件,但它们类似于 Linux 的先决条件。
我们首先需要的是 Db2 安装程序和支持文件。要获得 Db2 的免费版本,只需访问
www.ibm.com/account/reg/us-en/signup?formid=urx-33669
这将使您进入帐户注册页面。您可以使用现有的用户 id 登录,也可以创建一个新的用户 id。登录后,网站会向您显示一个首选项页面。选择您的首选项,然后选择继续按钮。在下一个网页上,选择您需要的下载。除非你需要它们,否则忽略 pureScale 支持下载。下载完成后,将下载的文件移动到一个安全的位置,然后解压缩。这将在当前目录下创建一个新的子目录,安装文件在其中展开。
Caution!
不要将下载的文件移动到要放置数据库的目录中。当您创建第一个数据库时,您手上会有一大堆文件。
在撰写本书时,可用的 Db2 版本是 11.5.4。当 IBM 推出新版本时,旧的免费 Db2 版本就不可用了。
现在您已经有了 Db2 的评估版本,您可能想知道它的局限性是什么。评估版永远不会过期。它对数据库的数量没有限制。唯一的限制是所有数据库的总大小,目前限制为 100 GB。这对于您自己执行软件评估来说已经足够了,除非您试图测试一个非常大的系统。如果这是您的计划,IBM 将非常乐意为 Db2 的生产版本甚至一些工程师提供帮助。
在这一点上,我们现在准备找出我们的先决条件是什么。为此,只需运行安装程序。这个程序位于我们之前创建的 untarred 子目录中。展开的目录被命名为server_dec。要运行安装程序,只需运行以下命令:
$ cd server_dec
$ ./db2setup
如果 Db2 需要先决条件,安装程序会列出它们,然后退出。确保阅读安装程序输出的所有内容。在我们的 CentOS 8.2 系统上,它列出了两个先决条件:
-
libpam.so* (32 位)
-
libstdc++.so.6 (32 位)
“等等。为什么 Db2 需要 32 位库?”你问。Db2 是一系列产品的集合,其中一些产品已经十年甚至更久没有改变过。IBM 没有在系统中引入新的错误,而是选择保留其中一些产品的旧版本。因此,需要这些必备库来支持这些产品。
使用系统的软件安装程序安装必备软件包。对于 Linux,这将是基于 RPM 的包的dnf或yum程序。一旦安装了先决条件,就可以开始真正安装 Db2 了。但在此之前,我们需要做一点规划。
规划 Db2 安装
在开始安装之前,我们需要了解安装的结果是什么,这样我们就可以提前计划,避免以后出现意外。在这方面有两个要点需要讨论。
安装的第一个结果是您将在目标机器上创建两个新的 userids。这些 id 的默认名称是db2inst1和db2fenc1。这个db2inst1 ID 将有一些文件存储在它的主目录中,你需要确保有足够的空间来容纳它们。文件的数量有限,但是您应该确保为它们做好准备。db2fenc1 ID 只有非常有限数量的文件存储在其主目录中。
第二个结果并不是真正的安装结果。它更像是作为安装结果而设置的默认值。此项是将安装数据库的位置。数据库的默认位置是在db2inst1主目录中!这不是您想要存储数据库的地方!您可能会将它们存储在不同的驱动器/位置。在我们介绍完安装过程之后,在创建sample数据库之前,我们将再次讨论这一点。
安装过程中还会出现一些小问题,但是我们会在以后讨论这些问题。
安装 Db2
我们现在准备安装 Db2。这个过程必须由root(在 Linux 上)或admin(在 Windows 上)用户完成,所以从成为root/admin用户开始。在 Windows 上,您需要注销,然后以管理员用户身份重新登录。在 Linux 上,只需执行以下命令:
$ su - root
在这之后,您需要切换到放置 Db2 下载包的扩展文件的目录。然后运行db2setup安装包:
$ cd server_dec
$ ./db2setup
如果您已经安装了所有的先决条件,这将向您显示欢迎页面(图 2-1 )。

图 2-1
Db2 安装欢迎页面
点击新安装按钮后,下一个窗口出现,允许您选择要安装的产品(图 2-2 )。

图 2-2
Db2 安装选择产品页面
该页面有许多产品可供您选择。第一个产品是 Db2 服务器,,这是我们将要安装的产品。第二个产品是带 pureScale 的 Db2,,我们没有下载。第三个产品是用于分布式数据库的 Db2 Connect 服务器。第四个产品是 Db2 Connect Client,,用于构建包含 SQL 语句的编译代码。最后一个产品是 Db2 运行时客户端,它用于运行来自 Db2 Connect 客户端版本的编译代码。
此时,您应该选择列表中的第一项, Db2 服务器版本。如果您下载了较新版本的 Db2,版本号可能会有所不同。此时,其他选择并不重要。点击下一个按钮后,将出现下一页。

图 2-3
Db2 安装配置页面
配置页面允许您选择典型或定制安装。此时,选择一个典型的安装。在继续之前,您还必须选中复选框以同意 IBM 条款。完成后,点击下一页按钮,将出现如图 2-4 所示的下一页。

图 2-4
Db2 安装实例所有者页面
您可以查看自定义安装选项,了解您可能想要更改的一些项目。大多数用户只是执行一个典型的安装。
Instance Owner 页面允许您创建将拥有第一个 Db2 实例的帐户和新用户的组名。只需保留出现的db2inst1信息,然后输入新账户的密码和确认信息。这是将在主目录中安装新文件的帐户。当您输入新密码并确认后,点击下一步,将显示如图 2-5 所示的页面。

图 2-5
Db2 安装防护用户页面
db2fenc1用户将在 Db2 数据库的地址空间之外运行用户定义的函数和存储过程。输入db2fenc1密码并确认。完成后,点击下一步,将显示图 2-6 中的页面。

图 2-6
Db2 安装响应文件和摘要页面
下一页是响应文件和摘要页。响应文件是安装文件集的一部分,因此不必创建它。点击完成按钮,开始安装 Db2,如图 2-7 所示。

图 2-7
Db2 安装进度页面
两个状态栏跟踪所有组件的安装进度。完成后,您将自动进入下一页(图 2-8 )。

图 2-8
Db2 安装完成页面
当安装完成时,这是将显示的页面。您可以选择查看安装后的步骤,查看日志文件,或者单击完成按钮退出安装。
至此,Db2 安装步骤已经完成,我们已经准备好查看需要考虑的安装后任务。
Note
在 Ubuntu Linux 中,默认情况下,Db2 安装创建的 Db2 实例所有者和 fenced 用户被分配 Dash shell。这可能会导致脚本以行 #开头!/bin/bash 停止工作。如果您想在登录到这些用户中的任何一个时使用 Bash 作为默认 shell,那么在执行任何其他操作之前,请执行命令 csh -s /bin/bash username (其中 username 是 Db2 实例所有者或受保护用户的名称)。
Db2 安装后任务
Db2 安装完成后,需要执行一些任务:
-
尝试使用您在
db2setup命令中设置的密码登录到db2fenc1用户 id。纠正你可能发现的任何问题。 -
尝试使用您在
db2setup命令中设置的密码登录到db2inst1用户 id。纠正你可能发现的任何问题。您应该记下 sqllib 目录的位置及其内容,因为您可能需要熟悉它们。这可能是也可能不是另一个位置的链接。 -
如果您不希望数据库存储在
db2inst1目录树中,那么现在是时候改变这个位置了。以用户 iddb2inst1的身份登录并运行以下命令:
$ db2 update dbm cfg using dftdbpath /your/db/path
这将更新 Db2 配置中的路径,这样所有属于db2inst1的数据库都将存储在新位置。
此时,我们已经完成了安装后的任务,并准备好继续安装sample数据库。
安装 Db2 示例数据库
Db2 附带了一个名为sample的默认数据库。这个数据库虽然很小,但足够全面地展示了数据库应该如何构造,并有助于演示一系列可能的 SQL 命令。这本书广泛使用了sample数据库来演示 Db2 概念、SQL 用法和 Python 编程示例。
要安装sample数据库,请遵循以下步骤。
登录到db2inst1用户 id:
$ su - db2inst1
需要启动 Db2 数据库实例,因此这是我们的下一步:
$ db2start
现在我们可以实际安装示例数据库了。该命令需要几分钟时间,所以请耐心等待:
$ db2sampl -force -sql
-force选项确保如果已经安装了数据库,它将被默认内容覆盖。-sql选项告诉 Db2 不要将 XML 数据安装到数据库中。
现在我们可以连接到数据库并测试它:
$ db2 connect to sample
如果成功,您应该会看到如下所示的输出:
Database Connection Information
Database server = DB2/LINUXX8664 11.5.0.0
SQL authorization ID = DB2INST1
Local database alias = SAMPLE
现在我们可以运行一个测试 SQL 命令来查询数据库:
$ db2 "select * from department"
Important
一定要在 db2 后面的文本周围加上引号。如果省略引号,操作系统将试图错误地处理该命令,从而导致错误。
如果一切正常,您应该会看到总共显示了 14 条记录。我们现在可以使用以下命令终止与数据库的连接:
$ db2 connect reset
如果成功,我们与数据库的连接将被终止。
摘要
本章介绍了安装和验证 Db2 安装的步骤。请务必阅读本章中的“注意”、“注意”和“重要”部分,因为它们对某些操作系统有重要的信息。
三、Db2 管理
Db2 的管理不是一件容易的事情。它不像任何开源关系数据库。有点像甲骨文,但又有区别。这是因为用于 Linux、Unix 和 Windows 的 Db2 很像维护大型机数据库,只是规模更小、更容易。虽然有许多工具可以帮助管理产品本身包含的 Db2 环境,但是正确管理的关键是理解产品的体系结构以及它如何管理硬件资源。没有这些知识,很容易陷入困境,过度扩展机器的硬件。通过适当的管理,一个硬件既可以管理几个大型数据库,也可以管理许多较小的数据库,而不会过度消耗机器上的资源。
本章将为您提供足够的知识,以便您可以正确地设置 Db2 环境,而不会让您负担过重,因为这些事实几乎没有任何后果。
Db2 实例
是 Db2 中的顶级架构结构。当您安装 Db2 时,您创建了第一个 Db2 Instance,它的默认名称是db2inst1。这也是保存我们在前一章安装的sample数据库的Instance。
在一个Instance中可以安装任意数量的数据库。但是过载一个Instance可能会导致性能问题。这是因为 Db2 为每个Instance留出固定数量的进程和线程,并且它不会在Instance的活动生命周期内动态添加或移除资源。您必须停止实例,调整资源数量,然后重启Instance。当然,Instance拥有的数据库也必须停止并重启。这是关键:停止和启动一个Instance会对可用性和包含的数据库产生影响。如果你不断地停止和重启Instance s,你的用户将不会对你的管理技能很满意。
描述很容易,但证明却很难。你必须了解一些历史,才能正确理解它们从哪里来,以及它们为什么存在。Instance在 2000 年后首次推出。他们的理由是多驱动器机器上的数据库开始变得非常大。它们太大了,用户太多了,网络接口成了处理数据库请求的瓶颈。看起来现在机器在处理数据库请求的能力上远远超过了网络。Db2 在等待网络接口处理下一个请求时有大量空闲时间。当时最快的普通网络只有 10 MB 的容量。需要的是一种在多个网络端口之间划分数据库的方法。于是,实例诞生了。
不仅给了他们自己的网络端口,还给了他们自己的进程、线程和信号量。因此,如果数据库由于任何原因崩溃,它只会破坏包含Instance的数据库。其他的Instance和数据库将继续正常运行,而数据库管理员处理关闭的Instance。
数据库管理员可以通过多种方式使用,但最常见的用途是防止网络端口拥塞。第二个最常见的用途是出于安全目的将数据库集彼此分开。由于每个Instance都有自己的一组管理员和用户,这有助于加强Instance之间的安全性
instance的另一个属性是它在您的磁盘系统上没有物理存在,或者至少没有多少物理存在。很难指着磁盘系统中的某个位置说,“这是一个instance驻留的地方。”有几个用instance创建的文件,但是这些没有描述instance在哪里。你应该把它想成一个逻辑实体,而不是物理实体。
管理员可以使用一组命令来管理 i nstance s。
Note
本章中使用的所有命令都可以以两种方式之一运行。它们可以通过登录到db2inst1用户来运行,也可以通过使用命令的前缀sudo db2inst1来运行。
db2ilist
该命令列出了系统上可用的所有Instance:
db2ilist
输出:
db2inst1
db2inst2
db2inst3
实例环境命令
这些命令对于处理 Db2 CLI 中的实例排列非常有用。
以下命令获取当前实例:
db2 get instance
输出:
The current database manager instance is : db2inst1
要启动或停止 Db2 UDB 上一个实例的数据库管理器,需要对当前实例执行以下命令:
set db2instance=db2inst1
使用该命令,您可以启动一个Instance。在此之前,您需要运行“设置实例”:
db2start
输出:
SQL1063N DB2START processing was successful
该命令用于停止当前Instance:
db2stop
输出:
SQL1064N DB2STOP processing was successful.
创建实例
让我们看看如何创建一个新的Instance。
如果要新建一个Instance,需要用 root 登录。一个Instance ID 不是一个根 ID 或根名称。
下面是创建新的Instance的步骤:
步骤 1:为Instance创建一个操作系统用户。
useradd -u <ID> -g <group name> -m -d <user location> <user name> -p <password>
示例:
useradd -u 1000 -g db2iadm1 -m -d /home/db2inst2 db2inst2 -p db2inst2
第 2 步:以 root 用户身份转到 Db2 Instance目录,创建一个新的Instance。
位置:
cd /opt/ibm/db2/v10.1/instance
步骤 3:使用以下语法创建一个实例。
./db2icrt -s ese -u <inst id> <instance name>
示例:
./db2icrt -s ese -u db2inst2 db2inst2
输出:
DBI1446I The db2icrt command is running, please wait.
....
.....
DBI1070I Program db2icrt completed successfully.
为实例安排通信端口和主机
编辑/etc/services 文件并添加端口号。在下面的语法中,inst_name表示Instance的名称,inst_port表示Instance的端口号:
db2c_<inst name> <inst_port>/tcp
示例:
sudo - db2c_db2inst2 50001/tcp
语法 1:用服务名更新配置。在下面的语法中,“svcename表示Instance服务名,“inst_name表示Instance名。
db2 update database manager configuration using svcename db2c_&<inst_name>
示例 1:为Instance " db2inst2"使用值为"db2c_db2inst2"的变量 svcename 更新 DBM 配置
db2 update database manager configuration using svcename db2c_db2inst2
输出:
DB20000I The UPDATE DATABASE MANAGER CONFIGURATION command completed successfully.
语法 2:为当前Instance.设置“tcpip”通信协议
db2set DB2COMM=tcpip
语法 3:停止并启动当前的Instance以从数据库管理器配置中获取更新的值。
db2stop
db2start
更新实例
您可以使用以下命令更新实例:
db2iupdt
该命令用于更新同一版本发布中的Instance。在执行这个命令之前,您需要使用“db2stop”命令停止Instance数据库管理器。下面的语法“inst_name表示以前发布或安装的 Db2 服务器Instance的名称,您希望将它更新到新发布或安装的 Db2 服务器版本。
语法 1:在正常模式下更新实例
db2iupdt <inst_name>
示例:
./db2iupdt db2inst2
语法 2:在调试模式下更新一个Instance
db2iupdt -D <inst_name>
示例:
db2iupdt -D db2inst2
升级实例
您可以将实例从以前版本的 Db2 拷贝升级到新安装的 Db2 拷贝版本:
db2iupgrade
在 Linux 或 UNIX 系统上,这个命令位于 DB2DIR/instance 目录中。在下面的语法中,“inst_name表示 Db2 Instance,的先前版本,而“inst_username表示当前为Instance用户安装的 Db2 副本的版本:
db2iupgrade -d -k -u <inst_username> <inst_name>
示例:
db2iupgrade -d -k -u db2inst2 db2inst2
命令参数:
-
-d:打开调试模式
-
-k:保持升级前的类型
Instance,如果 Db2 拷贝支持的话,从 Db2 拷贝运行这个命令
如果在 Linux 上对 db2iupgrade 命令使用超级用户(su ),则必须发出带有“-”选项的“su”命令。
删除实例
您可以删除由“db2icrt”命令创建的实例:
db2idrop
在 Linux 和 UNIX 操作系统上,该命令位于 DB2 _ installation _ folder/instance 目录中。
语法:
db2idrop -u <inst_username> <inst_name>
示例:
./db2idrop -u db2inst2 db2inst2
对实例使用其他命令
下面是找出我们现在正在处理哪个 Db2 实例的命令。
语法 1:
db2 get instance
输出:
The current database manager instance is: db2inst1
语法 2:
db2pd -inst | head -2
示例:
db2pd -inst | head -2
输出:
Instance db2inst1 uses 64 bits and DB2 code release SQL10010
语法 3:
db2 select inst_name from sysibmadm.env_inst_info
示例:
db2 select inst_name from sysibmadm.env_inst_info
输出:
INST_NAME --------------------------------------
db2inst1
1 record(s) selected.
语法 4:
db2set db2instdef=<inst_name> -g
示例:
db2set db2instdef=db2inst2 -g
数据库
数据库是 Db2 中可管理对象的下一层。每个数据库都包含在一个Instance,中,但是一个Instance中可以包含多个数据库。
该数据库没有与外界的通信接口。相反,Instance控制每个包含的数据库和外部世界之间的通信。当多个数据库归Instance所有时,这减少了通信代码的冗余。
数据库包含一大组对象,它们共同构成了数据库的管理和存储位置。这些对象的部分列表如下:
-
数据库目录
-
全局死锁文件
-
表空间信息
-
存储组控制文件
-
临时表空间
-
全局配置文件
-
历史文件
-
日志文件
-
自动化存储容器
这些对象中的每一个都将在以下章节以及本书后面的第四章中详细讨论。
Db2 目录视图
Db2 编目表用于保存所有数据库表、视图、索引和数据库中包含的其他实体的信息。每个数据库都包含自己的编目表,它们不与任何其他数据库共享。
编目表非常重要,应该只由 Db2 系统更新。为此,您甚至不能再阅读目录表。相反,系统为每个表创建一个只读视图。要读取目录表,必须使用视图;您不能直接读取目录表。
Db2 当前的 11.x 版本包含 140 个目录视图,另外还有 9 个包含统计信息的视图。其中许多视图包含对管理员和高级用户都非常有用的信息。因此,对于对该部分数据库拥有控制权限的每个人来说,目录视图都是只读的。例如,如果用户对数据库表没有读取权限,则该表上的信息在目录视图中对用户不可用。
有关目录视图的更多详细信息,请参见 IBM 文档
www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.sql.ref.doc/doc/r0008443.html
该文档详细描述了每个视图的完整布局,以及关于许多列的所有可能值的附加信息。
锁定事件监视器
Db2 11 中的新特性是锁定事件监视器。这取代了不推荐使用的死锁事件监视器。旧的监视器有许多不足,导致一些死锁没有被检测到。新的日志事件监视器在检测死锁时比等待某种死锁超时要好得多。它为用户和管理员提供了一个更健壮的系统。
死锁信息可以写入两个不同的数据库表中,但它始终是二进制格式的。在这两个表之间进行选择可能会很棘手,因此应该仔细考虑您的选择。通过使用 SQL 查询该表,系统将解释二进制数据并翻译成支持的语言,如英语。
当您为其执行 CREATE 语句时,会自动启用锁定事件监视器。以下语句将启用锁定事件监视器:
create event monitor for locking
检测到死锁时,会在事件日志中收集以下信息:
-
导致事件的锁
-
持有导致锁定事件的锁的应用
-
等待或请求导致锁定事件的锁定的应用
-
锁定事件期间应用在做什么
表空间信息
表空间用于包含数据库表。创建数据库时,会自动创建三个表空间。第一个表空间 SYSCATSPACE 包含数据库目录和视图。第二个表空间 USERSPACE1 是用于保存数据库表的默认空间。第三个是 TEMPSPACE1,用于保存查询结果的临时表。默认情况下,所有这些表空间都是在安装 Db2 系统时指定的位置创建的。
有很多关于表空间的信息,这本书有整整一章专门讨论这个主题。更多信息见第四章。
获取表空间信息的基本命令如下:
mon_get_tablespace (tblsp_name, member)
如果tblsp_name为 NULL 或空字符串,那么将返回所有表空间。
member指定了表空间的成员号。如果为–1,则返回当前成员信息。如果为–2,则返回所有成员信息。如果指定了 NULL,则与指定–1 相同。
存储组控制文件
存储组是数据库及其表空间之间的中间级别。您可以使用存储组将表空间组合在一起。这允许整个表空间集驻留在同一个位置。一个数据库可以有许多存储组,分布在任意数量的磁盘设备上。
除非另外指定,否则在创建数据库时会创建一个默认存储组。所有表空间都将驻留在该存储组中。更多信息见第四章。
全局配置文件
全局配置文件由管理员创建。它用于为 Db2 系统设置备份过程。有关更多信息,请参见 IBM 文档。
历史文件
历史文件是特定于实例的,存储在与实例信息相同的位置。它们包含关于实例的一些全局信息,比如最后的开始和停止时间以及其他有时有用的数据。这些文件的数量在不同的版本之间可能会有所不同,因此请参考特定于版本的文档以了解更多信息。
日志文件
日志文件特定于数据库,存储 Db2 系统记录的信息。文件的数量和类型取决于您的数据库配置。
自动化存储容器
这些将在第四章中详细讨论。创建数据库时会创建一个默认存储容器。
创建数据库
创建数据库时,它会自动附加到当前实例,即您当前登录的实例。如果这是有史以来创建的第一个数据库,将会为您创建一个新的默认实例。数据库的默认所有者始终是创建数据库的管理员。所有数据库系统表都放在名为“IBMSTOGROUP”的默认存储组中
可以创建两种类型的数据库,一种是限制性的,另一种是非限制性的。这两个数据库的区别在于限制性数据库没有被授予“PUBLIC”特权。这意味着只有数据库管理员有权访问限制性数据库。
所有数据库表上的默认特权是 CREATETAB、BINDADD、CONNECT、IMPLICIT_SCHEMA 和 SELECT。非限制性数据库也有 PUBLIC 特权。这些权限适用于数据库中的所有表。
要创建非限制性数据库,请使用以下语法:
db2 create database [dbname]
dbname是要创建的数据库的名称。对于 Db2 管理的所有数据库,它必须是惟一的。以下是一个例子:
$ db2 create database mydb
DB20000I The CREATE DATABASE command completed successfully.
要创建限制性数据库,请使用以下语法:
db2 create database [dbname] restrictive
dbname是要创建的数据库的名称。对于 Db2 管理的所有数据库,它必须是惟一的。下面是一个例子:
$ db2 create database myrestrict db restrictive
DB20000I The CREATE DATABASE command completed successfully.
不必在默认位置创建数据库,也就是说,默认表空间可以位于服务器文件系统中的任何位置。要将数据库放在您选择的位置,请使用以下语法:
db2 create database [dbname] [restrictive] on 'dblocation' dbpath on 'path_location'
这将在文件夹/path_location/dblocation中创建数据库。请注意,最终文件路径与它在语句语法中显示的方式相反。
列出数据库
要查看当前实例中包含的数据库列表,请使用以下命令:
$ db2 list database directory
以下是该命令输出的部分列表示例:
System Database Directory
Number of entries in the directory = 6
Database 1 entry:
Database alias = FOUR
Database name = FOUR
Local database directory = /home/db2inst4/Desktop/dbpath
Database release level = f.00
Comment =
Directory entry type = Indirect
Catalog database partition number = 0
Alternate server hostname =
Alternate server port number =
Database 2 entry:
Database alias = SIX
Local database directory = /home/db2inst4
Database release level = f.00
Comment =
Directory entry type = Indirect
Catalog database partition number = 0
Alternate server hostname =
Alternate server port number =
您应该注意到每个数据库的不同路径。它演示了如何在服务器的文件系统中分布数据库。
激活数据库
要激活数据库,请使用以下语法:
db2 activate db [dbname]
dbname 是要激活的数据库的名称。
你应该注意到这不同于db2start命令。该命令激活一个实例中的所有数据库,而该命令只激活一个数据库。无论使用哪个命令,所有必需的进程和线程都在instance中被激活。
停用数据库
要停用实例中的单个数据库,请使用以下语法:
db2 deactivate dn [dbname]
这将停止一个名为dbname的数据库。如果这是唯一的活动数据库,那么该实例的所有进程和线程也将停止。
连接到数据库
要连接到数据库,请使用以下语法:
db2 connect to [dbname]
这将把您连接到名为dbname的数据库。现在,您可以向数据库发出 SQL 命令。
删除数据库
要删除数据库,请使用以下语法:
Db2 drop database [dbname]
dbname是数据库的名称。注意,即使这是包含在instance中的唯一数据库,也不会删除instance。
桌子
表是所有关系数据库的关键。它们保存着我们想要保存和组织的数据,不管是什么样的数据。表使得关系数据库不同于其他类型的数据库。
当第一次遇到它们时,你可能会认为它们是一种电子表格,这是一种逻辑类比。但是他们有一些基本的区别。首先,它们的列数有限。其次,每一列仅限于一种数据类型。第三,在大多数情况下,一个或多个列与其他表中的列有关系。第三种情况是什么定义了关系数据库。列之间的关系是打开表之间可见和隐藏关系的关键。这是发现以前未知或不明显的有趣数据的关键。
表格由列和行组成。虽然每一行中的列将包含相同数据类型的数据,但一行中每一列的值将大不相同。表中的行数不一定由任何因素决定,除了存储的数据类型和使用该数据的应用所需要的。
数据库中表的数量仅由数据本身的需求决定。数据库管理员有责任确定表的组织和数量。这种分析涉及到许多因素,所有这些因素都会对数据库的设计产生影响。下面列出了其中的一些因素:
-
可用性:数据库必须尽可能容易使用。很多时候,数据用户在如何创建高效查询方面受到的培训有限,这一点必须考虑在内。
-
报告:总是需要根据数据库中包含的数据创建报告。数据的结构应该尽可能的简单高效。
-
安全性:关系数据库有一些非常好的机制来保护它存储的数据。这需要被设计成只有用户需要的数据实际上对他们可用。
-
支持和开发:数据库需要设计成易于用最少的人员维护。这可能意味着在开发期间,需要一些额外的时间和人力资源来完成这个目标。
-
功能性:数据库是用来解决问题和揭示用户群以前不知道的事情的。数据库的设计需要满足这一需求。
-
集成:数据库不仅要适合组织的基础设施,还必须适应基础设施本身可能的创新。它还必须预见到新数据库将集成到基础架构中。
-
成本:数据库的成本效益如何?它会继续具有成本效益吗?这些问题必须回答,有时通过改变数据库的设计。在其他情况下,可能需要不同的更改。
-
可伸缩性:数据库是可伸缩的吗?数据量翻倍或者翻两番会怎么样?当组织发生变化时会发生什么(无论是数据还是组织)?所有这些问题的答案都应该考虑成本、效率和性能。
-
托管:什么样的硬件将托管数据库?可以跨多个系统传播吗?所有这些都会对硬件和支持成本产生直接影响。
-
更新:这经常被忽略,留给支持人员去解决。这是一个严重的错误。关系数据库在系统更新方面各不相同。一些系统需要完全停止才能应用更新,一些系统需要重新启动才能应用更新,而其他系统即使在应用更新时也可以继续运行。在任何情况下,每个更新过程都有“陷阱”。
从任务的角度来看,数据库的设计主要是关于表及其布局的设计。这一过程将在第四章详细讨论。
表格类型
Db2 中有三种主要的表类型。这些是
-
基表:这些表是所有用户都知道并使用的表。它们保存持久的数据。
-
临时表:某些类型的查询需要创建临时表。这些表永远不会出现在系统目录中,并且永远不会有 XML 列。顾名思义,这些表本质上是临时的,在事务结束时被销毁。
-
物化查询表:这些表用于提高查询的性能。它们是由查询定义的,查询也决定了存储在其中的内容。当事务结束时,这些表也会被系统销毁。
基表类型是每个人都使用的类型。其他两种表格类型主要由训练有素的人员使用,以满足特殊的报告需求。这两个表都像基表一样,都使用标准的数据类型,我们接下来将会看到这一点。
内置数据类型
Db2 中有许多非常有用的内置数据类型。除此之外,您还可以定义自己的数据类型,但是我们不会在本书中涉及这些内容。
Db2 内置数据类型及其定义如下所示:
-
TIME:以小时、分钟和秒表示一天中的时间。所有的部分都是数字。
-
TIMESTAMP:通过年、月、日、小时、分钟、秒和微秒形式的七个值来表示特定的日期和时间。所有的部分都是数字。
-
DATE:以年、月和日的形式将日期表示为三部分。所有的部分都是数字。
-
CHAR(固定长度):表示固定长度的字符串。当分配给此数据类型时,短于固定长度的字符串后面会追加空格。已定义字符串的长度必须是 0
-
VARCHAR(max length):表示一个可变长度的字符串。定义的长度必须小于 32763。
-
LONG VARCHAR:表示最长为 500k 的可变长度字符串。
-
CLOB:表示长度高达 500k 的大字符块。
-
GRAPHIC(length):这是一种二进制类型的图形数据,最长可达 500k。
-
VARGRAPHIC(最大长度):这是一种二进制类型的图形数据,最大长度为 500k。
-
DBCLOB:表示最长 500k 的双字节二进制字符串。
-
BLOB(max length):表示长度最大为 500k 的二进制 BLOB 字符串。
-
BOOLEAN:表示布尔值 0 或 1。
-
SMALLINT:表示一个小的有符号整数(一个有符号的 16 位值)。
-
INTEGER:表示有符号整数(有符号的 32 位值)。
-
BIGINT:表示一个大的有符号整数(一个有符号的 64 位值)。
-
DECIMAL(p,s)或 DEC(p,s):由 p 和 s 指定的有符号十进制数。p 是精度,或十进制位数,最多 31 位。s 是数字的小数位数,即小数点右边的位数,范围是 1-1031 到 1031-1。
-
数字(p,s):与十进制(p,s)相同。
-
REAL:表示单精度浮点数。
-
DOUBLE:表示双精度浮点数。
-
XML:表示格式良好的 XML 文档。除了传输到另一个 Db2 服务器时,对文档没有有效的大小限制。这里的大小限制是 2 GB。
创建表格
许多标准用户不使用用于创建表的 SQL 语句,但是数据库管理员使用它。用户可以创建物化查询表(MQT),但是只有管理员可以创建常规表。这两种表的 SQL 语句本质上是相同的,只有很小的不同。
为了开始我们对表的讨论,我们应该看看清单 3-1 中 CREATE TABLE SQL 语句的语法。
CREATE TABLE [Tablename]
( [Column | Unique Constraint | Referential Constraint | Check Constraint],
... )
<LIKE [Source] <CopyOptions>>
<IN [TS_Name]>
<INDEX IN [TS_Name}>
<LONG IN [TS_Name]>
<ORGANIZE BY [ROW | COLUMN]>
<PARTITION BY [PartitionClause]>
<COMPRESS [YES | NO]>
Listing 3-1The CREATE TABLE SQL Statement Syntax
这个语句的难点是列/约束的列表。本质上,这是表中包含的列的列表。列表是从上到下读取的,这在 SELECT *语句中变成了从左到右的列表。每一列总是有一个在前一节中描述的相关内置数据类型,或者一个通常由数据库管理员创建的已定义数据类型。我们将在本节的后面讨论如何定义列。
列列表后面是所有表格选项。除了具有相关表空间名称(TS_Name)的选项之外,我们不会讨论所有这些选项。这在包括 Db2 在内的一些关系数据库系统中是独一无二的。表空间总是定义在特定的磁盘驱动器或一组驱动器上。这允许将一个繁忙的表放在一个或多个磁盘驱动器上,这些磁盘驱动器至少在某种程度上专用于表空间中的表。IN、INDEX IN 和 LONG IN 子句指定放置表和/或索引的表空间。表和索引不必在同一个表空间中。
以下是 CREATE TABLE 语句的几个示例:
create table employee (
id integer,
name varchar(50),
jobrole varchar(30),
joindate date,
salary decimal(10, 2))
in ts1
这是一个非常标准的 CREATE TABLE 语句,用于定义五列的employee。没有为此表定义索引或约束。它被放在一个名为ts1的已定义表空间中。
create table parts (
id integer primary key not null,
name varchar(50) not null,
price (decimal 6, 2),
description char(50) not null)
这里定义的parts表有几个用 NOT NULL 关键字定义的列。这指定该列不允许在其中存储空值。id列被定义为表的主键,因此将创建该列值的索引。您应该注意,该列的索引不是唯一的,因为它没有指定唯一的关键字。最后,由于没有指定表空间,表和索引将放在默认表空间中。
**CREATE TABLE 语句有许多选项,比我们在这里所能记录的要多得多。假设 Db2 语句的选项不同于您可能熟悉的其他关系数据库系统,不要假设您知道语法,因为您知道 system X 的语法。
IBM 网站上提供了 CREATE TABLE 语句的完整语法。只需在您最喜欢的 web 搜索引擎中搜索“db2 create table syntax”。
更改表格
ALTER TABLE 和 ALTER COLUMN 语句能够以某种有限的方式对表进行更改。例如,在某些情况下,ALTER COLUMN 语句只能更改列存储/不存储空值的能力。
这些语句可以做的是添加/删除列、删除表/索引、更改参照完整性约束和一些其他项目。
所有这些限制都适用于普通表和 MQT。然而,ALTER 语句并不局限于 MQT 的管理员。拥有权限的用户可以更改 MQT。
建议您在尝试使用 ALTER 语句之前查找它们的语法。这些语句不经常使用,所以不要假设你记得语法的细节。
其他表 SQL 语句
还有一些其他的 SQL 语句可以为您提供关于表的信息。对于这些语句,您必须至少拥有“读取”权限,这样系统才能为您提供有关表、表空间、索引或您希望查询的任何其他实体的信息。
要查看与模式和表空间相关的表详细信息列表,请使用以下命令:
select tabname, tabschema, tbspace from syscat.tables
这将列出所有数据库表名、表模式(如果有的话)和保存表的表空间名。
要查看表中的列列表,请使用以下命令:
describe table parts
这将显示parts表中的所有列以及每列的数据类型和属性。
放下一张桌子
删除不再使用的表的 SQL 语句是 DROP 语句。应该注意,删除表后,表中包含的所有数据都将丢失。默认情况下,不会删除表的触发器和关系。
以下语句将删除parts表:
drop table parts
要同时删除触发器和关系的层次结构以及表,请使用以下命令:
drop table heirarchy parts
这个 SQL 语句(两个版本)仅限于表的所有者,通常是数据库管理员。
摘要
本章介绍了 Db2 管理员经常使用的各种 Db2 管理命令。这些命令有许多语法变体,可以用来添加、修改或删除数据库中的 Db2 对象,包括数据库本身、表、索引和其他实体。**
四、数据库物理设计
关系数据库的物理设计是一个两阶段的项目。第一阶段涉及表及其关系的设计。第二阶段将表设计布置到系统提供的物理驱动器上。这两个阶段对于数据库信息的适当优化都是必要的。但是这两个阶段之间存在不可避免的冲突,需要在性能测试期间解决。
本章将介绍这些阶段中的每一个,并强调可能的冲突会降低数据库性能的地方。不要被本章的概念所迷惑。这些主题只有在深入理解可能出错的地方时才会涉及。程序员可能认为这一章并不适用于他们,但是他们的假设是错误的。程序员需要理解数据库设计者所做的选择,以便他们能够正确地编写 SQL 语句,从而充分利用设计。
关系数据库物理设计的第 1 阶段包括收集数据和一个称为数据规范化的过程。规范化是以某种方式组织表数据以减少对同一表或其他表中其他数据的依赖性的过程。规范化过程有三个步骤。每一步都将表格转换成第一范式、第二范式或第三范式。所有这些步骤都涉及到创建表,只包含对用户有用的数据。达到的深度决定了标准化的程度。
物理设计的第 2 阶段将第 1 阶段创建的表及其对应的表空间放在一个物理驱动器上。该驱动器可能是新的 SSD 内存设备之一,也可能是旋转磁盘或 RAID 系统。较少使用的数据甚至可以放在更慢的介质或远程系统上。用于存储的介质类型严格取决于所存储数据的性能要求。
数据库设计者的基本问题是平衡用户的需求与数据库可用的系统和介质。这实际上意味着两个阶段应该一起完成。单独执行每个阶段会有一个阶段影响另一个阶段的风险,可能会导致前一个阶段部分重做。
阶段 1:数据收集和标准化
这个阶段收集构成数据库的数据,然后对其进行规范化。这个阶段也称为数据的逻辑模型。
这一阶段需要明确的一点是,数据用户需要大量参与为数据库收集和组织数据。如果没有别的,这将帮助您发现将对数据进行的查询类型。这些查询将帮助您以这样一种方式组织数据,既提高查询性能,又创建对用户既有逻辑又有帮助的设计。
数据采集
这项任务可能涉及许多小组,尤其是当数据来自现有来源时。以下是可能需要参与收集数据的潜在群体列表:
-
要设计的数据库的最终用户
这群人将使用您的数据库中包含的数据。他们还可能从新数据库的任何现有源数据中获得见解。他们可能设计查询,也可能不设计查询,但是他们期望从数据中获得信息。
-
数据库源数据的所有者
该组是将用于新数据库的任何源数据的所有者。如果需要对此数据进行任何更改/添加,您将需要通知该组,并将这些更改的所有权交给该组。
-
维护数据库物理硬件的组
这个组将是数据库所在的物理硬件的维护者。随着新数据库大小的公开,该小组将负责维护该硬件。此外,如果需要购买硬件,该团队很可能会参与硬件的购买和安装。
-
负责数据备份的小组
该组维护新数据库的所有备份和恢复映像。可能需要为该任务购买和安装硬件,因此该组需要参与该任务。
-
数据库的 IT 管理
管理层需要参与批准任何新的硬件采购和/或新的数据库维护人员。
如您所见,有许多人参与设计数据库。在您的组织中,甚至可能有需要在流程的不同点引入的其他组。
数据收集涉及一系列潜在来源,具体取决于数据类型。这些来源可能涉及其他数据库、磁带上的数据、在线来源、自动输入、手动数据输入,甚至来自其他公司或政府机构的来源。所有数据源传递数据的方式会影响数据的及时性,也会影响对数据库的查询。
一旦你对进来的数据有了概念,就该组织它了。您和最终用户将需要定义在数据中容易识别的潜在实体。此外,您可能能够重新组织数据以形成新的实体,从而更好地反映数据的组织。当然,在创建表组织时,您可能需要额外的表和/或索引,用户不会直接使用它们来维护数据/表关系。
此时创建的任何表组织都应被视为临时的。这应该让你的用户非常清楚。设计稳定还有很长的路要走,大家要意识到这一点。
数据标准化
规范化包括从表中删除冗余,并从表中删除实际上不是表实体属性的实体。这非常类似于用设计 OOP 类定义的相同方式来组织一个表。不是主表实体属性的东西应该从表中删除。通常需要创建一个或多个新表来保存这些删除的数据。
标准化包括三种形式或步骤:
-
第一范式:信息存储在一个表中,每一列都包含原子值。没有重复的列组。
-
第二范式:表采用第一范式,所有列都依赖于主键。
-
第三范式:表以第二范式存储,它的所有列都不依赖于主键。
第一范式
第一范式涉及检查任何列是否是任何两个或更多其他列的组合,或者是否存在具有相同或相似信息的重复列。
例如,包含两个相加在一起的其他列的列就属于这一类。事实证明,数据库很容易为一个查询执行这种计算,所以汇总列被认为是多余的。这同样适用于串联在一起形成第三列的字符串数据列。总是让数据库为您执行这种数据转换。这将防止当转换中涉及的列之一用新值更新时潜在的数据损坏。
让我们看一下包含汇总数据的表格:
CREATE TABLE employee (
VARCHAR(50) name NOT NULL,
. . .
DECIMAL(10,2) salary,
DECIMAL(10,2) bonus,
DECIMAL(10,2) total_salary,
. . .
);
创建员工时,已知salary和bonus,并计算出total_salary的输入。这不是第一范式,因为我们有一个包含在表中的字段是从表中的其他两个字段计算出来的。当salary或bonus被更新而total_salary被遗忘时,这很容易出错。这显然会导致引用完整性问题。在这种情况下,应该删除total_salary字段,如果以后需要该值,让数据库在查询过程中一起添加salary和bonus字段。这对查询的影响很小,并且保持了参照完整性。
我们的第二个例子涉及到在一个与主表实体不直接相关的表中保存重复的信息。以下是此类表格的一个示例:
CREATE TABLE customer (
. .
VARCHAR(50) name NOT NULL,
. . .
INT ordernum1,
INT ordernum2,
INT ordernum3,
. . .
)
这里的想法是,为了方便起见,客户的最后三个订单与customer数据一起保存。这很糟糕,因为三个订单号的顺序必须保持不变。事实证明这真的很难做到。数据库可以很容易地从order表中提取最近的订单,所以您应该让它完成自己的工作,并从customer表中删除这些字段。
第二范式
第二范式删除任何不依赖于表主键的列。这将防止表字段之间的任何部分依赖。例如,您不会在存储客户信息的表中存储客户订单。应该创建一个单独的order表来保存这些信息。毕竟,一个客户在任何时候都可能有多个有效订单。
以下是违反第二范式的一个例子:
CREATE TABLE part (
VARCHAR(50) partnum PRIMARY KEY NOT NULL,
VARCHAR(50) warehouse PRIMARY KEY NOT NULL,
INT quantity NOT NULL,
VARCHAR(50) warehouse_addr NOT NULL
)
在这个例子中,warehouse_addr不是part的属性。它是warehouse表的一个属性,所以它不属于part表。
第三范式
第三范式有点难识别。存储患者数据的医院数据库就是一个很好的例子。如果患者表包含医生的电话号码,那么该数据不依赖于患者。该数据属于医生表。该列应该替换为对医生的键引用,而不是医生实体的属性。这就是所谓的删除可传递的依赖关系。
以下示例显示了违反第三范式如何导致引用完整性问题:
CREATE TABLE employee (
INT emp_num PRIMARY KEY NOT NULL,
VARCHAR(20) first_name NOT NULL,
VARCHAR(20) last_name NOT NULL,
CHAR(10) work_dept NOT NULL,
VARCHAR(20) dept_name NOT NULL
)
如果需要更改dept_name(而不是work_dept),那么如果您遗漏了该部门中的一个员工,那么您现在就遇到了引用完整性问题。dept_name属于department表,而不是employee表,因此应该删除该列。
最后要指出的是,第三范式在实现时会导致性能问题。许多数据库设计者已经将他们的设计带到了第三范式,却发现他们不得不退回到第三范式,这样数据就直接在一个不是第三范式的表中,以便为用户提供适当的性能。
第四范式
还有第四种范式,但我们不会讨论它,因为它需要创建新的表来解决问题。这有时会导致用户在创建查询时感到困惑和沮丧。对于您的数据库来说,这种麻烦可能值得,也可能不值得。例外情况可能是空间非常宝贵的大型数据库,因为第四范式可以减少数据库所需的空间。
商业规则
设计好主表后,就该考虑自动保持表同步所需的业务规则了。此活动将简化添加和更新表的过程。几个表可能有需要维护的直接关系。如果一个新行被添加到一个表中,可能需要维护一些依赖关系。业务规则可以通过在相关表更新时自动更新/添加/删除来简化这项任务。
在这些情况下,Db2 有许多工具可以提供帮助。它们太多了,我们用了整整一章来讨论它们。参见第六章。
阶段 2:数据库的物理设计
一旦设计了表、索引和业务规则,就需要在一组磁盘驱动器上布置表。根据您必须使用的硬件和数据库的大小,有许多方法可以做到这一点。
一个高达几千兆字节的小型数据库可以很容易地放在单个磁盘驱动器上,而不会对性能产生重大影响。然而,随着数据库变得越来越大,任务变得越来越困难。如果您从一个小型数据库开始,并且它开始增长,那么性能就成了一个主要问题。因此,您不仅应该考虑当前数据库的大小,还需要了解其潜在增长率,并设计适当的解决方案。
在过去,当磁盘驱动器相当小并且性能受到限制时,即使是小型数据库也需要围绕磁盘驱动器的性能来设计。一个表可能需要跨越几个驱动器,以便它们可以在一个查询中异步地协同工作。如今,驱动器的容量非常大,有些甚至达到了 8tb 和 10 TB,而且还在不断增大。当心这些驱动器。仅仅因为磁盘上的寻道时间,尝试使用这些驱动器上的所有可用空间就可能会导致巨大的性能影响。
通过创建 RAID 阵列,可以在一定程度上解决大磁盘驱动器的许多问题。RAIDs 5 和 6 是相当好的替代方案,其中表中的数据可以分布在阵列中的所有驱动器上。因此,如果阵列由八个驱动器组成,那么这八个驱动器可能都负责从表中检索数据。但是这种解决方案存在潜在的问题。在早期,向阵列写入数据时会出现很大的性能问题。这些惩罚多年来已经部分取消,但在某些方面仍然存在。
RAID 6 试图消除 RAID 5 的写性能损失。在很大程度上,实现是成功的,没有增加新的损失或增加现有特性的损失。RAID 6 的主要缺点是为缓存和临时存储提供存储空间需要更大的开销。事实证明,这在今天的大型磁盘上可以完全忽略。由于其较大的尺寸,今天的磁盘可以很容易地容纳必要的开销。
大型机使用的解决方案是使用一个或一组驱动器来存储一个大的单个表。但对于中小型系统来说,这不是一个好的解决方案,因为对于今天的驱动器,驱动器上会有大量浪费的空间。
一种相对新型的驱动器可以解决这些问题。这些是固态硬盘和其他基于内存的驱动器。这些驱动器在读写方面都有出色的性能。然而,它们的尺寸仍然相对较小,并且大量购买它们的成本可能过高。但在未来,这些可能会成为许多数据库的首选驱动器,因为性能非常好。
备份
这是开始考虑数据库备份的时候了。不要把这个任务推迟到以后,因为那会给你带来麻烦和问题。如果您正在备份到磁盘,您将需要额外的磁盘空间来容纳它们。对于磁带,您可能需要一个专用于数据库的磁带机。数据库的大小可能决定您的备份选项。所有这些都需要在设计过程中预先考虑,而不是以后。
Db2 有一个从db2提示符运行的专用备份命令。如果可能,您应该使用此命令,因为它具有加密和压缩选项,并且能够执行增量备份。它有多个选项,可以在数据库、页面和表空间级别执行各种不同的备份。它可以对任何设备执行备份,只要该设备装载到文件系统中。一个例外是执行到另一个 Db2 远程数据库的备份。这可以通过到本地系统的多个连接来完成。
不要试图在规划过程中最终确定备份解决方案。这个想法是确定你的选择,并尝试选择一个可以在以后最终确定的选项。
摘要
本章高度概括了收集必要数据,然后对数据库执行第一级逻辑和物理设计所需的任务。我们讨论了实现这一目标的所有主要主题,并让合适的人员为此做出贡献。
应该记住,这只是一个简短的回顾。如果你真的要做这项工作,你应该查阅更多的资料。我们在这里提供的只是高级任务的指南。
五、Db2 工具
在本章中,我们将介绍 Db2 工具。这些工具有助于管理 Db2 系统拥有的数据库、表空间、表和索引。这些工具包括backup、i mport、export、load、reorg、runstats、rebind、move以及其他一些命令。我们将在本章中讲述其中的许多命令。
通常,您会登录到想要运行该工具命令的 Db2 实例,然后从该点运行该命令。如果您以 Db2 实例所有者的身份登录,还可以从操作系统命令提示符下运行命令,如下所示:
[db2inst1@centos dashley]$ db2 db2start
这个命令将从 Linux 命令行启动 Db2 的第一个实例。Windows 命令提示符将与此类似。任何工具命令都可以以这种方式运行,这意味着您可以在脚本中编写命令,并从实例所有者的命令行运行它。这样做将确保您不会忘记您可能一直需要的 Db2 工具命令行选项。
Db2 工具不是简单的命令。在某些情况下,他们似乎有几十个选项,乍一看可能非常吓人。我们将只讨论大多数情况下你可能会用到的选项,而不是试图解释每一个选项。这将为您降低命令的复杂性,同时涵盖最常用的选项。如果您需要其他选项,那么您应该查阅 Db2 在线文档以获得您需要的选项。
有关更多信息,请参考位于以下 URL 的适当的在线 PDF 手册:
www.ibm.com/support/pages/node/627743
备份命令
在讨论 Db2 工具时,通常不考虑backup命令。我们认为这是一个非常重要的主题,在讨论完整的 Db2 工具时,它值得成为关注的焦点。如果没有清晰完整的数据库备份和数据恢复计划,您将面临灾难。您需要一个经过充分测试的计划,以确保您的数据完全安全。永远记住,如果您没有验证数据也可以恢复,备份是没有用的。
关于 a backup,的更多信息,请参考 IBM online Db2 手册,网址如下:
www.ibm.com/support/knowledgecenter/SSEPGG_11.5.0/com.ibm.db2.luw.welcome.doc/doc/welcome.html
与backup命令相对应的是restore命令,这将在本章后面介绍。
什么是备份?
典型的备份策略包括两种类型的备份—完整备份和部分备份。无论您选择哪种备份,请注意生成的数据文件都是二进制形式,不适合提取数据用于报告或任何其他人类可读的用途。完整备份和部分备份都是数据库中数据的精确副本,因此消耗的文件空间量与数据库中保存的数据完全相同。
由于从数据库提取数据的方法,该工具不使用或存储 SQL 来执行数据提取。从用户/管理员的角度来看,这意味着完整或部分备份不应被视为“数据库转储”,因为它不包含可用于重新创建表或索引的数据。备份的主要用途是在灾难恢复或磁盘故障时作为restore命令的来源,或者将数据移动到新的数据库。数据库管理员应该负责提供满足数据、应用和业务需求的数据库备份策略。
restore命令是backup命令的书立。一个backup/restore几乎总是将整个数据库从一个地方转移到另一个地方的最快方法,尤其是在没有太多预先计划的情况下。
备份验证
对于生产数据库,验证backup以确保其有效是至关重要的。这个工具叫做db2ckbkp。该工具验证backup图像,确保图像的有效性。其语法如下所示
db2 db2ckbkp image_file_name
其输出示例如下所示:
[db2inst1@centos dashley]$ db2ckbkp SAMPLE.0.DB2.DBPART000.20190318184324.001
[1] Buffers processed: #######
Image Verification Complete - successful.
验证可以运行与原始backup图像一样长的时间。随着数据库备份映像的增长,前面的示例中会产生更多的井号。当你真正依赖一个特定的数据库backup镜像时,你就要运行这个命令。如果数据足够重要,那么您应该考虑将每个经过验证的backup的副本存储在不同于数据库的存储设备上。
高级备份选项
您可以在backup命令中使用更多选项。您应该参考本节前面提到的 IBM 知识中心在线参考资料。
备份语法
本节是关于backup命令的语法。虽然将显示该命令的完整语法,但我们不会详细介绍每个选项。相反,我们将展示一些例子,这些例子将涵盖该命令最可能的用法。
BACKUP DATABASE 命令创建数据库或表空间的备份副本。应该创建数据库和相关存储数据的备份,以防止数据库停机时数据丢失。
当指定执行分区backup时,该命令只能在目录数据库分区上调用。如果该选项指定备份所有数据库分区服务器,则 db2nodes.cfg 文件中列出的所有数据库分区服务器都将被备份。否则,只有在命令中指定的数据库分区服务器会受到影响。
命令语法
BACKUP [DATABASE | DB] [DatabaseAlias]
[USER [Username] [Using [password]]]
[TABLESPACE (TbspNames]) | NO TABLESPACE]
[ONLINE]
[INCREMENTAL [DELTA]]
[TO [Location] | USE TSM [OPTIONS [TSMOptions]]]
[WITH [NumBuffers] BUFFERS]
[BUFFER [BufferSize]]
[PARALLELISM [parallelNum]]
[COMPRESS]
[UTIL_IMPACT_PRIORITY [Priority]]
[INCLUDE LOGS | EXCLUDE LOGS]
[WITHOUT PROMPTING]
backup命令非常灵活。可以备份完整的数据库、一个或多个表空间以及数据库或表空间的增量副本。包含备份数据的备份文件可以保存到 TSM 或文件系统中。您也可以选择将日志与数据一起备份。
它是如何工作的
执行备份时,backup命令不使用 SQL。它在文件系统级别运行。这意味着这是备份数据库或表空间的最快方法。这也意味着在数据库启动并运行时,它对数据库的影响最大。在使用活动数据库进行备份的过程中,很有可能发生冲突;因此,性能不会是最佳的。但是如果数据库的使用量很小,影响会小得多。
使用说明
-
备份应该存储在不直接连接到 Db2 数据库所在的服务器的介质上。这将在硬件或服务器出现故障时保护您的数据。
-
在文件/存储系统出现故障的情况下,应该用常规磁盘备份来补充快照备份。
-
Db2
backup命令的最新版本将 INCLUDE LOGS 选项设置为默认选项。旧版本的backup工具将排除日志选项作为默认选项。 -
始终不仅要测试您所做的备份,还要测试这些备份的恢复。如果备份文件无法恢复,它们就没有任何用处。
导出命令
EXPORT 命令将数据库中的数据导出为几种外部文件格式中的一种,所有这些文件格式都是 ASCII 格式。用户通过提供 SQL SELECT 语句或提供类型化表的分层信息来指定要导出的数据。
有关export的更多信息,请参考 IBM online Db2 手册,网址如下:
www.ibm.com/support/knowledgecenter/SSEPGG_11.5.0/com.ibm.db2.luw.welcome.doc/doc/welcome.html
export命令不如backup命令快,因为它使用 SQL 来选择要导出的数据。当您只希望对部分数据库数据进行选择性的部分备份时,应该使用它。还应该注意到,来自export的数据不能用于将数据库恢复到以前的状态,因为数据中没有导出关于数据库状态的信息。
import命令是export命令的配套命令。这将在本章后面介绍。
命令语法
DB2 EXPORT TO [Filename] OF [DEL | WSK | IXF]
[LOBS TO [LOBPath]]
[LOBFILE [LOBFileName]]
[XML TO [XMLPath]]
[XMLFILE [XMLFileName]]
[MODIFIED BY [Modifiers]]
[METHOD N ([ColumnNames])]
[MESSAGES [MSGFileName]]
[SELECTStatement]
export命令从数据库中删除数据,并将其存储为三种文件格式之一:DEL、WSJ 或 IXF。这种 IXF 格式几乎专门用于在数据库之间移动数据。export命令最常用于从数据库中删除有固定使用期限的数据,如税收数据、一些雇员数据等。
有许多选项支持 lob、XML 和消息。所有这些都可以用来确保数据以您需要的方式导出。
在对数据执行最后的export之前,您应该确保export命令中使用的 SQL 语句是正确的。
使用说明
-
在开始
export操作之前,必须完成所有工作台操作和所有锁的释放。实现这一点的最佳方法是在关闭用 WITH HOLD 选项打开的所有游标后执行 COMMIT,或者发出 ROLLBACK 命令。 -
必须使用 PC/IXF 格式在数据库之间移动数据。如果将包含行分隔符的字符数据导出到带分隔符的 ASCII DEL 中,则可以通过文件传输来修改数据,以便在传输过程中添加行分隔符。
导入命令
import命令将具有受支持文件格式的外部文件中的数据插入到表格、层次结构、视图或昵称中。Load是一个更快的选择,但是load工具不支持在层次级别加载数据。
关于一个import,的更多信息,请参考 IBM online Db2 手册,网址如下:
www.ibm.com/support/knowledgecenter/SSEPGG_11.5.0/com.ibm.db2.luw.welcome.doc/doc/welcome.html
import命令使用 SQL 命令来执行所有操作。因此,它比load命令慢,但是它允许数据库必须执行的所有数据检查。
与import命令相对应的是export命令。本章在这一节的上面介绍了export命令。import命令可以直接使用export命令的输出。
命令语法
IMPORT FROM [Filename] OF [DEL | ASC | WSF | IXF]
[LOBS FROM [LOBPath]]
[XML FROM [XMLPath]]
[MODIFIED BY {Modifiers]]
[Method]
[XML PARSE [STRIP | PRESERVE] WHITESPACE]
[XMLVALIDATION USING [XDS | SCHEMA [SchemaID]]]
[ALLOW NO ACCESS | ALLOW WRITE ACCESS]
[COMMITCOUNT [CommitCount] | COMMITCOUNT AUTOMATIC]
[RESTARTCOUNT | SKIPCOUNT | COMMITCOUNT AUTOMATIC]
[WARNINGCOUNT [WarningCount]]
[NOTIMEOUT]
[MESSAGES [MsgFileName]]
[CREATE | INSERT | INSERT_UPDATE | REPLACE | REPLACE_CREATE]
INTO [TableName] <([ColumnName])]
[IN [TSName] [INDEX IN [TSName]] [LONG IN [TSName]]]
import命令可以从几种不同的文件格式以及importlob 和 XML 加载数据库。您应该注意到,它支持的 SQL 语句非常灵活,但是有特定的格式。有几种机制可以控制如何导入数据,以及在出现错误情况时何时停止工具。
加载命令
load命令有效地将大量数据装载到 Db2 表中。该命令不使用 SQL,因此不执行任何数据检查。它也比import命令快得多。它通常用于向数据库中插入新数据。
因为load命令在数据库页面级别加载数据,所以它绕过了触发器的触发和日志记录。它还会延迟任何约束检查和索引构建,直到所有数据都加载到各自的表中。
load命令的主要用途是将新数据插入数据库。它可以从文件、设备或管道中读取数据。该命令可以设置为每小时、每天甚至连续向数据库馈送数据。由于该工具不使用 SQL,因此在将数据插入数据库之前,应注意消除与数据或现有数据库的任何冲突。
虽然 load 命令不使用 SQL 将数据实际加载到数据库中,但它使用 SQL 语法来指定要加载哪个表/列。
命令语法
LOAD <CLIENT> FROM [Filename OF [DEL | ASC | IXF] | PipeName | Device |
CursorName OF CURSOR]
<LOBS FROM [LOBPath]>
<MODIFIED BY [Modifiers]>
<Method>
<SAVECOUNT [SaveCount]>
<ROWCOUNT [RowCount]>
<WARNINGCOUNT [WarningCount]>
<MESSAGES [MsgFileName]>
<TEMPFILESPATH [TempFilesPath]>
<INSERT | REPLACE | RESTART | TERMINATE]
INTO [TableName] <([ColumnNames])>
<FOR EXCEPTION [ExTableName]>
<STATISTICS [NO | USE PROFILE]>
<NORECOVERABLE | COPY YES TO {CopyLocation | TSM]>
<WITHOUT PROMPTING>
<data buffer [Size]>
<INDEXING MODE {AUTOSELECT | REBUILD | INCREMENTAL | DEFERRED]>
<ALLOW NO ACCESS | ALLOW READ ACCESS <USE [TempTSName]>>
<SET INTEGRITY PENDING CASCADE [IMMEDIATE | DEFERRED]>
命令有许多选项,但大多数时候你只会使用其中的几个。例如,可以选择将被拒绝的数据存储在哪里、执行哪种索引、允许哪种访问等等。
参考 IBM Db2 手册以获得关于load工具的更多信息是非常重要的。这个工具是非常灵活的,有这么多的选项,如果你试图使用它们而不知道它们是什么,那么你可能会很容易感到沮丧。手册中包含了大量关于如何使用 load 工具的示例。
恢复命令
RESTORE DATABASE命令恢复使用 Db2 backup工具备份的数据库。恢复的数据库处于与制作backup副本时相同的状态。RESTORE DATABASE命令也可以用来加密现有的数据库。
恢复工具可以执行许多服务,包括覆盖现有数据库、恢复由backup工具创建的备份映像、在必要时启用数据库的rollforward、恢复增量映像(增量映像)以及其他功能。
如果要从一个环境恢复到另一个环境,在执行完整备份之前,新的增量或差异备份可能会受到限制。同一环境中的恢复操作利用现有的表空间和表空间映射。
针对新数据库运行的restore操作重新获取所有容器(甚至那些尚不存在的容器),并重建优化的表空间映射。在有一个或多个容器丢失的现有数据库上运行的restore操作也会重新获取所有容器,并重建一个优化的表空间映射。
restore命令也是backup命令的书立。它可以还原备份文件,以便数据库返回到以前的已知状态。建议在使用restore命令保护您的生产数据之前,先对其进行测试。
命令语法
RESTORE [DATABASE | DB] [databaseAlias]
[User [username] [Using [password]]]
[REBUILD WITH [TABLESPACE ([Tbspname])] |
[ALL TABLRSPACES IN [DATABASE | IMAGE]]
[EXCEPT TABLESPACE ([TbspName])]]
[TABLESPACE ([TbspName]) [ONLINE] |
HISTORY FILE [ONLINE]] |
LOGS [ONLINE]]
[INCREMENTAL [AUTO | AUTOMATIC | ABORT]]
[FROM [SourceLocation] | USE TSM [OPTIONS [TSMOptions]]]
[TAKEN AT {Timestamp]]
[TO [TargetLocations]]
[DBPATH ON [TargetPath]]
[TRANSPORT [ STAGE IN StagingAlias] [USING STOGROUP StoGroupName]]
[INTO [TargetAlias]] [LOGTARGET [LogLocation]]
[NEWLOGPATH [LogsLocation]]
[WITH [NumBuffers] BUFFERS]
[BUFFER [BufferSize]]
[REPLACE HISTORY FILE]
[REPLACE EXISTING]
[REDIRECT [GENERATE SCRIPT [ScriptFile]]]
[PARALLELISM pParallelNum]]
[WITHOUT ROLLING FORWARD]
[WITHOUT PROMPTING]
restore 命令有许多选项可供选择。为了有效地使用它,您应该参考 IBM Db2 手册,以全面了解这个工具是如何工作的。
摘要
本章只介绍了 Db2 命令工具的一个子集。Db2 在线文档中记录了更多可用的命令。这里介绍的工具是管理员在日常工作中最常用的,但对程序员也很有用。在尝试使用每个命令之前,请务必参考相应的在线文档,以获得完整的说明。
六、业务规则和约束
在 SQL 表上创建业务规则和约束实际上是数据库设计阶段的一部分。我们将它作为一个单独的主题,因为这些规则可以在以后添加,而不是在创建表的同时创建它们。这确实是一件好事,因为我们没有人能在设计阶段考虑到所有的事情。
有两种业务规则和约束——一种执行表的参照完整性,另一种应用实际的业务规则。这两种约束有时会被混淆,但在我们的解释中,我们会尽量将它们分开。
本章将根据约束的类型对其进行分组。这将包括索引、非空属性、主键、外键、表检查和信息约束。在许多情况下,一个约束可以归入多个类别,这就是为什么我们将根据 Db2 类型而不是约束类型对它们进行分组。
非空属性
在 Db2 中创建表时,默认情况下,表中的所有列都允许空值。如果不允许空值,则必须在列定义中显式包含 NOT NULL 短语。考虑下面的表定义:
CREATE TABLE employee (
emp_num CHAR(10) NOT NULL,
name CHAR(50),
location VARCHAR(30),
create_date TIMESTAMP
);
在该表中,emp_num列必须包含数据,即使它只是一个空白。NOT NULL 约束确保列中始终有一些数据。它不一定是有效数据(我们将在后面讨论这个主题),但它必须包含某种类型的数据。
列name、location和create_date可能都被分配了 NULL 类型。对于列定义中不包含 NOT NULL 短语的所有列,这是默认设置。
如果需要向列定义中添加 NOT NULL 短语,可以使用 ALTER TABLE 语句:
ALTER TABLE employee ALTER name CHAR(50) NOT NULL;
这将把 NOT NULL 子句添加到name列,而不修改数据类型。
主关键字
主键标识唯一标识表中单行的列。可以使用主键短语将主键列分配给表。例如,我们可以将这个短语添加到列定义中,使 emp_num 列成为主键:
emp_num CHAR(10) NOT NULL PRIMARY KEY,
您也可以稍后更改该表以添加此约束:
ALTER TABLE employee ADD PRIMARY KEY (emp_num);
如果表中的数据没有可以充当主键的自然列,您可以为表人工创建一个。考虑下面的表定义:
CREATE TABLE employee (
emp_num INTEGER NOT NULL PRIMARY KEY
name CHAR(50),
location VARCHAR(30),
create_date TIMESTAMP
);
这里我们修改了emp_num列,这样 Db2 会在整列中现有的最大整数上加 1,并在新行插入到表中时自动将其分配给新的emp_num列。
您不应该认为主键是唯一的索引,即使它是在下面创建的。虽然它们有许多相同的特征,但它们并不相同。首先,一个表只能有一个主键,但是它可以有许多唯一的和不唯一的索引。
指数
一个表可以有许多索引,但通常只有一两个,因为索引表中的所有内容会适得其反。有两种索引:
-
唯一索引:这种类型的索引防止索引列中的条目在表中出现多次。UNIQUE 关键字指定了这种索引。
-
非惟一索引:当在 SELECT 语句的 WHERE 子句中使用这种类型的索引时,它将提高 Db2 读取表的能力。
执行 CREATE INDEX 语句时可以创建索引。以下示例显示了如何使用 CREATE INDEX 语句在列上创建索引:
CREATE INDEX myindex ON employee (location);
还可以使用 DROP INDEX 语句删除索引。
在这一点上,需要对索引提出警告。创建索引时尽量不要走极端。您可能认为索引一个表中的所有列将为您的所有查询提供最佳的访问时间。这是完全错误的,反而会导致大多数索引被忽略。
外键
外键是引用不同表中另一列的列值。这样做是为了确保所有外键值都存在于主表中。例如,当您将一个人分配到一个部门时,您可能希望在操作成功之前确保该部门确实存在。以下示例将演示这一原理:
CREATE TABLE department (
dept_num INTEGER NOT NULL PRIMARY KEY,
name CHAR(50)
);
CREATE TABLE employee (
emp_num INTEGER NOT NULL PRIMARY KEY
name CHAR(50),
dept INTEGER REFERENCES department (dept_num),
location VARCHAR(30),
create_date TIMESTAMP
);
department表定义了组织内的所有部门。employee表有一个dept列,它引用了department表中的dept_num列。这意味着当您试图在employee表中插入一条新记录时,系统将检查以确保department表的dept_num列中存在dept列值。如果在department表中没有找到相应的部门编号,那么插入将会失败。
外键是一种引用约束,有时也称为外键约束。外键可以指主键或索引。对于索引,索引必须是单列,但不必是唯一索引。
以下示例显示了一个指向索引的外键:
CREATE TABLE department (
dept_num INTEGER NOT NULL,
name CHAR(50)
);
CREATE UNIQUE INDEX ON department (dept_num);
CREATE TABLE employee (
emp_num INTEGER NOT NULL PRIMARY KEY
name CHAR(50),
dept INTEGER REFERENCES department (dept_num),
location VARCHAR(30),
create_date TIMESTAMP
);
在前面的例子中,UNIQUE 关键字确保所有的dept_num数字在department表中是唯一的。试图在表中插入重复的部门编号将被拒绝。
检查和唯一约束
CHECK 约束确保表列中只允许出现特定范围内的值。下面显示了一个检查约束的示例:
CREATE TABLE employee (
empid INTEGER,
name VARCHAR(30),
ssn VARCHAR(11) NOT NULL,
salary INTEGER CHECK (salary >= 5000),
job VARCHAR(10) CHECK (job IN ('Engineer', 'Sales', 'Manager')));
当在表中插入或更新一行时,salary列的值必须大于或等于 5000。job列必须具有指定的三个值之一(区分大小写)。约束表达式可以具有前面指定的值,也可以是返回一组指定有效值的子选择语句。
默认约束
DEFAULT 约束允许您在向表中写入行时为列指定默认值。您可以指定默认约束,如下例所示:
CREATE TABLE employee (
empid INTEGER,
name VARCHAR(30),
ssn VARCHAR(11) WITH DEFAULT '999-99-999');
以下示例 INSERT 语句将显示此约束的工作方式:
INSERT INTO employee (empid, name)
VALUES (005, 'Smith, James');
请注意,ssn字段未被引用。通常这可能会导致 INSERT 语句出现问题。但是在这种情况下,已经为ssn指定了一个默认值,所以不会出现错误。
扳机
触发器是响应特定表或视图上的插入、更新或删除等事件的数据库对象。除了所有其他引用约束或检查约束之外,还可以使用触发器来实施数据完整性业务规则。任何触发器都有五个相关组件:
-
定义触发器的主题-表或视图
-
启动触发器的事件–插入、更新或删除操作
-
触发器的激活时间–事件之前或之后
-
指定触发器的操作是对表执行一次还是对每个受影响的行执行一次的粒度-对每个语句或每个行操作执行一次
-
触发器执行的操作–以下一个或多个元素:
-
呼叫语句
-
声明和/或设置语句
-
WHILE 和/或 FOR 循环
-
IF、SIGNAL、ITERATE、LEAVE 和 GET 诊断语句
-
选择指令
-
插入、更新、删除和合并 SQL 语句(仅适用于 AFTER 和 INSTEAD OF 触发器)
-
触发器分为之前、之后和替代:
-
BEFORE 触发器在更新或插入操作之前被激活,被更新或插入的值可以在数据库被修改之前被更改。
-
AFTER 触发器在插入、更新或删除操作后被激活,用于维护数据之间的关系或保存审计跟踪信息。
-
而不是触发器定义如何在视图上执行插入、更新或删除操作,否则这些操作是不允许的。
创建触发器的语法如下:
CREATE or REPLACE TRIGGER [Triggername]
<NO CASCADE> | <AFTER | BEFORE | INSTEAD OF> [TriggerEvent]
ON [Tablename | ViewName]
REFERENCING <OLD AS | NEW AS | OLD TABLE AS | NEW TABLE AS>
[CorrelationName | Identifier]
<FOR EACH ROW | FOR EACH STATEMENT>
<Action>
在哪里
-
TriggerName:标识分配给要创建的触发器的名称。 -
TriggerEvent:指定当其中一个事件应用于主题表或主题视图时,与要执行的触发器相关联的触发动作。 -
TableName:标识定义触发器的表(主题)的名称。 -
ViewName:标识定义触发器的视图(主题)的名称。 -
CorrelationName:在触发 SQL 操作之前标识行状态的临时表名。 -
Identifier:临时表名,在触发 SQL 操作之前标识受影响的行集。 -
Action:指定触发器激活时要执行的动作。触发器操作由 SQL 过程语句和执行 SQL 过程语句的可选条件组成。
虽然使用触发器的方式有无数种,但我们将只展示四个简单的例子。这些示例可以用作您创建的其他触发器的基础。
以下示例说明了在对employee表执行 INSERT 语句时激活的 BEFORE 触发器。当触发器检测到一个空值被插入到employee表的empstart列时,它会分配第二天的值:
CREATE OR REPLACE TRIGGER employeeJoinDate
NO CASCADE BEFORE INSERT ON enployee
REFERENCING NEW AS N
FOR EACH ROW
MODE DB2SQL
WHEN (N.empstartdate IS NULL)
SET N.empstartdate = CURRENT DATE + 1 DAY;
在下面的例子中,当在employee表上执行 INSERT 语句时,AFTER 触发器被激活。如果插入到employee表中的行是由于一个新雇员,触发器语句将更新公司统计表company_stats中的雇员人数:
CREATE OR REPLACE TRIGGER employeeNewHire
NO CASCADE AFTER INSERT ON employee
FOR EACH ROW
MODE DB2SQL
UPDATE company_stats SET emp_tcount = emp_tcount + 1;
如果人力资源部门希望在employee表中的工资变动之前检查员工的加薪情况,该怎么办?对于任何员工,如果加薪是当前工资的两倍,则必须将其记录在salary_audit表中以供分析。可以创建一个 AFTER 触发器,将适当的数据插入到salary_audit表中:
CREATE OR REPLACE TRIGGER employeeSalaryUpdate
AFTER UPDATE OF salary ON employee
REFERENCING NEW AS N OLD AS O
FOR EACH ROW
MODE DB2SQL
WHEN (N.salary > O.salary * 2)
INSERT INTO salary_audit
(empno, old_salary, New_salary, rating)
VALUES (N.empid, O.salary, N.salary, N.rating);
触发器还可以用于通过 SIGNAL 语句引发错误,并阻止对表的特定操作。如果存在加薪不能超过 300%的规则,您可以创建一个触发器来检查该条件:
CREATE TRIGGER salaryRaiseLimit
AFTER UPDATE OF salary ON employee
REFERENCING NEW AS N OLD AS O
FOR EACH ROW
WHEN (N.salary > O.salary * 3)
SIGNAL SQLSTATE '75000' SET MESSAGE_TEXT = 'Salary increase > 300%';
摘要
在本章中,我们已经介绍了业务规则和约束的概念。这些规则和约束可以帮助您实现您的组织为维护其数据而建立的规则。他们还可以帮助实现组织的组织特征,以及建立公司不同部门之间的关系。
七、为 Db2 编写优秀的 SQL
编写好的 SQL 实际上是关于数据库性能的。尽管数据库调优有助于提高性能,但糟糕的 SQL 会以调优数据库无法解决的方式影响性能。当数据库管理员完成他们的工作后,就该开始培训用户了。所有关系数据库都有自己的怪癖,对于用户来说,意识到这些怪癖并了解如何绕过它们或创建利用数据库设计和特性的 SQL 是至关重要的。
Db2 被设计用来保存和处理大量数据。因此,许多设计目标和特性与其他关系数据库有很大不同。SQL 优化器是业内最复杂的优化器之一。数据库数据可以分布在各种设备和一系列计算实体上。所有这些能力的获得都是有代价的,本章关注 SQL 的设计和使用。
本章将介绍一些简单的方法来构建 SQL,以利用 Db2 设计的优势。它将涵盖多个领域,以便您能够很好地理解 Db2 是如何工作的,以及如何让您的 SQL 性能更好。
关系理论
关系数据库是围绕集合的数学理论设计的。集合论是一个非常古老的、被理解的关于实体或数据之间关系的理论。所有关系数据库的核心都是集合论。
由于这种设计,数据库中的数据存储实际上与传统的文件系统没有任何相似之处。这使得程序员和用户都很难理解为什么不应该用传统的方式做事。教给新用户的第一件事是,以传统方式做事总是会对数据库性能造成不利影响。在某些情况下,性能会比顺序访问相同数据的文件差。
关系数据库内置了巨大的能力。当使用 Db2 时,您应该总是尝试利用这种能力,主要是因为这几乎总是比尝试设计自己的解决方案要好。
新用户犯的一个错误是将数据库表视为一个文件。它们不是文件,甚至一个表行中的数据也不存储在一起。原因可以追溯到设计的集合论。动态结构化数据总是比分解结构化数据并将其重组为结果集更容易。
新用户犯的下一个错误是认为结果集的构造一次只发生一点点。这是一个大错误。当第一个结果行呈现给用户时,系统已经完成了完整的结果集。因此,一次处理一行结果集会延迟结果集的销毁,并会耗尽宝贵的系统暂存空间。
对于新用户和程序员来说,这是两个非常大的问题,但是程序员特别容易犯的另一个错误是在获取数据后处理数据以产生新的结果。新程序员有时会认为数据库系统在动态修改结果方面效率低下——比如向结果列添加固定的数值。事实并非如此。数据库系统在这种计算上比任何程序都要快得多。
新用户有时也害怕创建复杂的 SQL,认为系统会发现很难解释有效的结果。Db2 的 SQL optimizer 非常有效地分解输入 SQL,并高效地解释和执行它。
用户犯的最后一个错误是编写 SQL 来请求您已经知道的数据。例如,如果您需要 Dept. 4 的数据,就不要在结果集中包含部门号。这只是浪费时间和资源。
最后要介绍的是 Db2 中使用的数据类型,并将它们与硬件系统的本地数据类型进行对比。例如,很少有较小的系统本身支持十进制数据。有时有一些库可以处理这些数据,但即使这样,也需要一些编程来适应十进制数据。
减少数据传递
减少 SQL 查询处理的最简单方法之一是消除对同一数据的多次传递。例如,如果您正在寻找工资超出范围的雇员,您可以创建两个查询来返回数据:
SELECT firstnme, midinit, empno
FROM emp
WHERE salary > 75000;
SELECT firstnme, midinit, empno
FROM emp
WHERE salary < 10000;
这两个查询将返回工资高于或低于该工资范围的雇员。如果员工数量非常大,每个查询都需要一些时间来处理。这里的问题是我们真正想要回答的问题是什么“员工超出范围”这个基本问题可以用一个简单的查询来回答:
SELECT firstnme, midinit, empno
FROM emp
WHERE salary > 75000 OR salary < 10000;
该查询将对数据进行一次遍历,以列出高于或低于该范围的所有雇员。
当需要两次查询来获得数据时,总是从一个问题后退是非常重要的。尝试找出你问的基本问题,因为这通常会导致一个真正回答你的问题的查询。
下面是另一个例子,说明多次传递如何渗透到 SQL 代码中:
SELECT creator, name, 'Table '
FROM sysibm.systables
WHERE type = 'T'
UNION
SELECT creator, name, 'View '
FROM sysibm.systables
WHERE type = 'V'
UNION
SELECT creator, name, 'Alias '
FROM sysibm.systables
WHERE type = 'A'
UNION
SELECT creator, name, 'Global'
FROM sysibm.systables
WHERE type = 'G'
ORDER BY creator, name;
这个简单的语句导致四次通过sysibm.systables表。结果输出显示了数据库目录中所有类似表格的对象。但是,该语句可以简化,只需要对sysibm.systables表进行一次遍历。
当联接两个或多个表时,WHERE 子句引用所引用的每个表中的索引列将非常有用。所有这些都是为了防止在每个表中查找正确行的完整表扫描:
SELECT creator, name,
CASE type
WHEN 'T' THEN 'Table '
WHEN 'V' THEN 'View '
WHEN 'A' THEN "Alias '
WHEN 'G' THEN 'Global'
END
FROM sysibm.systables
ORDER BY creator, name;
这个新查询提供了与前一个相同的结果,但是只需要一次通过sysibm.systables表。它也更容易阅读和理解。
下面显示了如何只通过一次表就可以进行批量更新:
UPDATE emp
SET salary = CASE workdept
WHEN 'A01' THEN salary * .04
WHEN 'C01' THEN salary * .02
WHEN 'D11' THEN salary * .10
WHEN 'D21' THEN salary * .50
ELSE salary * .01
WHERE edlevel > 15;
使用索引提高性能
当从数据库中查询数据时,尽可能利用索引非常重要。性能的提高可能非常显著。您不一定要获取有索引的列,而是尽可能在 SQL WHERE 子句中利用它们。例如,如果empno列上有一个索引,您应该尝试在查询中使用该索引:
SELECT firstnme, midinit, lastnme, dept
FROM emp
WHERE empno > 10000
前面的语句现在将利用索引来定位雇员行,而不需要全表扫描。列empno有一个索引;因此,Db2 可以使用它来定位要返回的行。请注意,没有必要检索索引列来利用它的索引。WHERE 子句中的引用足以防止全表扫描。
另外,您应该知道 Db2 可以动态地创建索引来帮助查询一个或多个表。例如,下面的 WHERE 子句将导致在salary、bonus和comm列上创建动态索引,以帮助减少数据的访问时间:
SELECT empno, firstnme, lastnme
FROM emp
WHERE salary + bonus + comm > 100000.00;
创建的索引只是临时的,在查询完成后将被删除。
排序和分组
对于新的 SQL 用户来说,排序有时是一个令人困惑的话题,因为有一些基本的规则来管理它。此外,排序可以由 ORDER BY 子句或 SELECT 子句上的 DISTINCT 关键字调用。
ORDER BY 子句的一个特点是,不必在输出中检索该子句中指定的列(出现在 SELECT 关键字之后)。许多新用户总是对输出数据的排序感到惊讶。这里显示了一个示例:
SELECT empno, lastnme, firstnme
FROM emp
ORDER BY workdept;
如您所见,workdept没有出现在输出中,但是数据将按该值排序。
您还应该限制要排序的列数。包含的列越多,排序所需的时间就越长。
DISTINCT 关键字总是导致排序,即使没有重复的行。使用 DISTINCT 时应该非常小心,因为开销可能会很高。但是,这并不是建议永远不要使用它,因为这样做可能有非常正当的理由。
当您想要返回聚合数据时,使用 SQL 进行分组非常有用。考虑以下查询:
SELECT workdept, SUM(salary)
FROM emp
GROUP BY workdept;
这导致输出包含每个workdept的单行以及workdept中所有雇员的工资总和。由于没有使用 ORDER BY 子句,workdept的顺序将是随机的。此外,HAVING 子句可以消除对您无用的部门:
SELECT workdept, SUM(salary)
FROM emp
GROUP BY workdept
HAVING AVG(salary) < 15000;
包含 SQL 的程序
包含 SQL 的程序有时被称为“黑盒代码”,因为运行程序的用户看不到 SQL。这些程序是由程序员创建的,他们根据用户创建的一组需求工作。当程序员在为程序编写 SQL 时走捷径时,问题就出现了。让我们来看一个例子,它会使 SQL 效率低下,从而导致程序效率低下。
如果程序需要来自customer表的三种不同类型的数据,最好的方法是针对customer表编写三个不同的查询:
SELECT firstnme, lastnme, address, city, state, zipcode
FROM customer
WHERE areacode = :HV-AC;
SELECT custid, firstnme, lastnme, phoneno
FROM customer
WHERE areacode = :HV-AC;
SELECT custid, firstnme, lastnme, custtype
FROM customer
WHERE areacode = :HV-AC;
前面所有的查询都非常高效,只需要很少的数据库资源。但是,以减少编程工作量的名义,程序员可能会决定对它们进行如下编码,以减少程序中 SQL 语句的数量:
SELECT custid, firstnme, lastnme, address, city, state, zipcode, phonno, custtype
FROM customer
WHERE areacode = :HV-AC;
这远不如前三个查询有效。这也是不好的形式,因为它总是返回用户不感兴趣的列。底线是这是一个可怕的想法。虽然它看起来节省了程序员的时间,但每次运行查询时,它都会浪费用户的时间和数据库资源。
尽可能使用 Db2 工具
程序员和用户都应该研究使用 Db2 工具代替定制程序的可能性。在大多数情况下,Db2 工具比程序员创建的程序要高效得多。Db2 中的绝大多数工具已经存在了几十年,并且已经消除了 99%的错误。此外,这些公用事业的效率已经磨砺了多年,远远超过任何其他本地创建的程序。
load和import工具可用于添加或替换现有表格中的数据。这些工具非常高效,尤其是load工具。它们还可以处理非常大的输入数据集。这些工具支持许多选项,可以修改数据加载到表中的方式,包括跳过一些输入数据。
unload工具可以非常快速地从一个表中大量提取数据。它使用 SQL 语句来确定要卸载的数据,因此用户可以很容易地修改它来满足他们的需求。
MERGE SQL 语句虽然不是 Db2 工具,但它是从两个表中获取数据并将其合并到一个表中的另一种方式。这是一个非常强大的 SQL 语句,有许多选项用于匹配要合并的数据。
TRUNCATE SQL 语句是另一个不是 Db2 工具的工具。这一条语句可以删除表中的所有数据,并选择性地回收表所占用的存储空间。
Db2 函数
Db2 系统提供了大量的内置函数(BIFs ),可以在 SQL 语句中使用这些函数,从而改进对您正在寻找的确切数据的检索。这些函数和 SQL 语句本身一样,都是 SQL 和 Db2 优化器的一部分。在 SQL 语句中使用它们通常不会导致性能问题。
从广义的角度来看,Db2 提供了两种类型的功能。有由数据库提供的内置函数,也有由用户和程序员提供的用户开发函数。内置函数非常高效,任何为 Db2 开发 SQL 语句的人都可以使用。用户定义的函数必须由程序员开发,以便可用于 SQL 语句。
BIF 有多种类型,下面列出了其中几种:
-
聚合函数:这些函数用于计算包含在一行或多行中的一列的汇总信息,并以某种形式进行汇总。
-
标量函数:这些函数在单行中返回列数据的子集。
-
表函数:这些函数从 WHERE 子句中返回一组列。
-
行函数:这些在 LUW 版 Db2 中还不可用。
下面列出了当前的 Db2 BIFs:
ABS:从一组行中返回一列的绝对值。
AVG:计算一组列值的平均值。
CEILING:返回大于或等于参数的最小整数值。
CHAR:返回参数的固定长度字符串。
COALESCE:返回第一个非空表达式的值。
CONCAT:返回两个兼容字符串参数的串联。
CORRELATION:返回任何内置数值数据类型的值的表达式。
COUNT:计算列结果中的行数。
协方差:返回一组数字对的总体(协方差)。
DATE:返回从表达式中导出的 Db2 日期。
IDENTITY_VAL_LOCAL:返回 IDENTITY 元素的最近赋值。
INT:返回数字的整数或整数的字符串表示形式。
LENGTH:返回值的长度。
LOCATE:返回一个参数在另一个参数中第一次出现的位置。
LOWER:以小写形式返回参数字符串。
LPAD:返回一个左边带空格的填充字符串参数(或另一个字符串)。
LTRIM:返回一个从字符串左边移除了字节的字符串。
MAX:返回一组行中某列的最大值。
MIN:返回一组行中某列的最小值。
POSITION:返回一个参数在另一个参数中第一次出现的位置。
RAND:返回一个介于 0 和 1 之间的随机浮点数。
REPLACE:返回一个字符串,其中包含该字符串与另一个字符串的所有匹配项。
ROUND:返回舍入到小数点右边或左边指定位数的数字。
RPAD:返回右边带空格的填充字符串参数(或另一个字符串)。
RTRIM:返回一个从字符串右侧移除了字节的字符串。
SQRT:返回参数的平方根。
STDDEV:返回一列中一组行的标准偏差。
SUBSTR:返回字符串参数的子字符串。
SUM:返回一组行中所有列的总和。
UPPER:以大写形式返回参数字符串。
UNNEST:返回一个结果表,其中包含数组中每个元素的一行。
VARIANCE:返回一组行中所有列的方差。
编写 SQL 的多种方法
使用 SQL,就像任何其他语言一样,有多种方法可以从查询中获得想要的结果。检查以下语句:
SELECT empno, lastname
FROM emp
WHERE workdept LIKE 'A%';
虽然这可能会得到您想要的结果,但它可能会返回比您预期的多得多的一组行。前面的陈述假设您知道只有三个部门以字母“a”开头,但是将来,以该字母开头的部门可能会多得多。一个更好的替代方法是编写您所知道的代码,以防止将来出现问题并提高效率:
SELECT empno, lastname
FROM emp
WHERE workdept IN('A00', 'A01', 'A02');
这至少会给你正确的目标结果,但也会提高性能。
摘要
本章介绍了几个单独的主题,都是为了帮助您为 Db2 数据库编写好的 SQL。好的 SQL 被定义为高效、合理、易读。编写好的 SQL 的结果是容易理解和维护。
八、Python 和ibm_db
为了从 Python 访问 Db2,您需要下载并设置ibm_db模块。目前,这个模块在任何 Linux、Unix 或 Windows 代码库中都不可用。但是,它可以通过 Python pip系统安装,或者作为源代码下载并在您的系统上编译。我们强烈建议您使用pip来安装ibm_db模块。但是在安装之前,需要满足一些先决条件,然后才能成功执行安装:
-
您的系统必须能够编译源代码。为了创建模块,必须安装 C 编译器和链接器。在 Linux 上,您可以通过运行以下命令来检查是否安装了编译器:
-
如果没有安装
pip,那么使用您的发行版的软件包安装程序来安装它。不要担心 pip 的 Python 版本,因为安装会为您的 Python 版本创建一个到正确版本pip的链接:sudo dnf install pip或
sudo yum install pip -
如果在 Linux 或 Unix 上运行,必须安装
python-devel包。使用您的发行版的安装包来安装python-devel (or python3-devel)包:sudo dnf install python-devel或
sudo yum install python-devel
gcc -v
一旦安装了额外的组件,就可以安装ibm_db包了:
sudo pip install ibm_db
或(适用于 RHEL 或 CentOS)
sudo python3 pip3 install ibm_db
(for RHEL or CentOS)
这将安装 Db2 包,以便 Python 脚本可以使用它。除非出于其他目的,否则不需要安装 Db2 客户机软件。ibm_db模块将允许 Python 脚本/程序在本地或远程访问 Db2,而无需任何其他软件包。
ibm_db包包含两个可以导入到脚本中的模块:ibm_db模块,这是从 Python 访问 Db2 的标准方式,以及ibm_db_dbi模块,这符合一般访问数据库系统的 Python DB-API 2.0 标准。在本书中,我们将集中讨论ibm_db模块,因为该模块是访问 Db2 的基础模块,并且实际上被ibm_db_dbi模块用来执行访问 Db2 的所有工作。
ibm_db模块是一个基于 C 源代码的模块。它是开源的,可以在 http://github.com/ibmdb/python-ibmdb/ 找到。此外,模块的 Python 测试和ibm_db_dbi模块的 Python 源代码都在同一个位置。
如果您在尝试安装ibm_db包时收到错误消息,那么一个或多个先决条件没有安装。如果消息声称找不到Python.h文件,那么您没有从 Linux/Unix 发行版的代码库中安装python-devel包,或者它没有正确安装。
一旦成功安装了 ibm_db 包,就可以编写第一个 Python 脚本来访问 Db2 了。
下面是一些使用ibm_db模块从sample数据库中检索数据的例子。所有这些例子都提示您输入用户名和密码。这些值总是 Db2 服务器上的值,而不是本地机器上的值(除非 Db2 系统驻留在本地机器上)。如果使用默认选项将sample数据库安装在服务器上,那么用户名将是db2inst1,,密码将是数据库管理员设置的任何值。
您的第一个 Python ibm_db 程序
本章中的示例 Python 代码访问 Db2 附带的sample数据库。您需要安装sample数据库,或者让您的 Db2 管理员为您安装。如果您自己在自己的系统上安装它,只需要运行一个命令就可以安装它。确保以db2inst1帐户运行该命令:
db2sampl -force -sql
这需要一点时间来安装,所以请耐心等待。安装后,您可以运行以下命令来测试数据库是否已成功安装:
db2 connect to sample
db2 "select * from department"
这将显示department表,它应该包含 14 条记录。
一旦成功执行,现在就可以编写访问 Db2 sample数据库的第一个 Python 程序了。
我们的第一个 Python 程序/脚本实际上非常简单。它模拟了我们之前用来测试数据库sample的 SQL select命令。我们希望表格内容有一个很好的格式显示,并且我们希望执行所有需要的错误检查,以确保我们可以跟踪程序可能遇到的任何错误。这将需要一个比第一个例子通常需要的更长的程序,但是它也给了我们一个机会来描述一些你将在你所有的 Python 程序中使用的ibm_dbAPI。
第一个 Python 示例程序如清单 8-1 所示。
#!/usr/bin/python
import sys, getpass
import ibm_db
def getColNamesWidths(results):
# get the width of each column
columns = list()
col = 0
numColumns = 0
try:
numColumns = ibm_db.num_fields(results)
except Exception:
pass
# If information about the number columns returned could not be obtained,
# display an error message and exit .
if numColumns is False:
print("\nERROR: Unable to obtain information about the result set produced.")
conn.closeConnection()
exit(-1)
while col < numColumns:
col_name = ibm_db.field_name(results, col)
col_width = ibm_db.field_width(results, col)
# the field name can be bigger than the display width
col_width = max(len(col_name), col_width)
columns.append((col_name, col_width))
col += 1
return columns # return a list of tuples (name, size)
def populateColTitleLines(columns):
# populate the two title lines for the results
col = 0
line = ''
lines = []
# do the title line
while col < len(columns):
(col_name, col_width) = columns[col]
title = col_name + ((col_width - len(col_name)) * ' ')
line += ' ' + title
col += 1
lines.append(line)
# do the underlines
col = 0
line = ''
while col < len(columns):
(col_name, col_width) = columns[col]
line += ' ' + (col_width * '-')
col += 1
lines.append(line)
return lines # return the two title lines
def populateLines(results, headerLines):
# print the data records
lines = []
record = ibm_db.fetch_tuple(results)
while record is not False:
line = ''
col = 0
numColumns = 0
try:
numColumns = ibm_db.num_fields(results)
except Exception:
pass
# If information about the number columns returned could not be obtained,
# display an error message and exit .
if numColumns is False:
print("\nERROR: Unable to obtain information about the result set produced.")
conn.closeConnection()
exit(-1)
while col < numColumns:
colstr = record[col]
(name, col_width) = headerLines[col]
coltype = ibm_db.field_type(results, col)
if record[col] is None:
line += ' -' + ((col_width - 1) * ' ')
elif coltype in ("clob", "dbclob", "blob", "xml", "string"):
# these are the string types
line += ' ' + str(colstr) + ((col_width - len(colstr)) * ' ')
else:
# these are the numeric types, or at least close enough
colstr = str(colstr)
line += ' ' + ((col_width - len(colstr)) * ' ') + colstr
col += 1
lines.append(line)
record = ibm_db.fetch_tuple(results)
return lines
# main program
driver = "{IBM DB2 ODBC DRIVER}" # Db2 driver information
host = '192.168.1.201' # database host IP or dns address
port = "50000" # host port
db = "sample" # database registered name
uid = None # userid on the database host
pw = None # password of the uid
autocommit = '' # autocommit default override
connecttype = '' # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
print("The password you entered is incorrect.")
exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host + \
';PORT=' + port + ';DATABASE=' + db + \
';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, autocommit, connecttype)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# get the records from the database
sqlstmt = 'select * from department'
try:
results = ibm_db.exec_immediate(connID, sqlstmt)
except Exception:
pass
# If the sql statement could not be executed, display an error message and exit
if results is False:
print("\nERROR: Unable to execute the SQL statement specified.")
ibm_db.close(connID)
exit(-1)
# fetch SQL results and format lines
headerLines = getColNamesWidths(results)
titleLines = populateColTitleLines(headerLines)
dataLines = populateLines(results, headerLines)
selrecords = len(dataLines)
#print the result lines
for line in titleLines:
print(line)
for line in dataLines:
print(line)
# print the number of records returned
print('\n ' + str(selrecords) + ' record(s) selected.')
ibm_db.close(connID)
exit(0)
Listing 8-1Display the department Table
Python 脚本从通常的第一行开始,通知系统这是一个 Python 脚本,然后指定要执行的 Python 解释器。
接下来的两行是脚本所需的 Python 导入语句:
import sys, getpass
import ibm_db
这里唯一不同寻常的是ibm_db模块的导入。
第一个函数getColNamesWidths()从结果表中获得每列所需的列名和显示宽度。对ibm_db.num_fields()函数的调用获得结果集包含的列数。一旦我们有了这个数字,我们就循环遍历每一列并调用ibm_db.field_name()和ibm_db.field_width()来获得列名和列显示宽度。这些将用于为每列添加标题,并设置列显示宽度。这些值随后被返回给调用者。
下一个函数populateColTitleLines()创建两行,它们最终将被打印到屏幕上。这些行是列标题行和分隔符虚线。它利用上一个函数getColNamesWidths()的值来计算如何格式化这些行。一旦行被创建,它们将作为 Python list返回给调用者。
下一个函数populateLines()创建从department表中获取的行,并使用传递给该函数的信息将每个记录格式化为一个显示行。创建的每一行都被添加到 Python list中,然后返回给调用者。每个结果集记录都通过ibm_db.fetch_tuple()函数获取,然后根据传递给该函数的信息进行格式化。
主程序代码紧跟在populateLines()函数之后。主程序代码的第一部分设置了一些值,这些值将在程序连接到 Db2 数据库时使用。在所有使用ibm_db模块的 Python 脚本中,driver变量总是相同的。host变量是保存sample数据库的服务器的 IP 地址或 DNS 名称。如果数据库在本地,那么它也可以是 IP 地址127.0.0.1。db变量具有您想要连接的数据库的名称;在我们的例子中,这是sample。我们没有初始化uid和pw变量,所以我们没有将用户 id 和密码信息编码到程序中。autocommit和connecttype变量是我们在这个脚本中没有使用的选项。
接下来的几行从用户那里获得了uid和pw变量信息。然后,我们调用 ibm_db.connect()函数实际连接到 Db2 和前面变量指定的数据库。如果此函数因任何原因失败,Python 脚本将退出并显示错误消息。这是一个深思熟虑的决定,这样我们就不会在程序中嵌入大量代码来纠正用户提供的信息或错误输入到脚本中的信息。
接下来的两段代码是执行实际工作的地方。第一部分设置要执行的 SQL 语句。在这种情况下,我们希望获取关于 department 表的所有信息。然后我们调用ibm_db.exec_immediate()来执行 SQL 语句。如果ibm_db.exec_immediate()失败,将显示错误信息,程序将退出并显示错误信息。如果成功了,我们继续下一段代码。
下一部分代码调用程序中定义的三个函数来获取并显示 SQL 语句的结果。这些功能已在前面描述过。
最后一个主要部分实际上将列标题和获取的数据打印到标准输出中。
最后一部分调用ibm_db.close()来关闭我们与 Db2 数据库的会话并退出脚本。
咻!这是很多代码,但重要的是要注意,我们包括了所有必要的错误检查,并生成了一个格式良好的报告返回给我们的程序用户。此外,这是可以在你编写的几乎任何使用select语句的程序中反复使用的代码,或者作为一个更复杂更大的程序的基础。这正是我们在后面的例子中要做的。程序的大部分将被重用,无需任何更改,只有 SQL 语句将被更改或代码将被添加到该部分以支持扩展功能。
清单 8-1 的输出在清单 8-2 中提供。
$ ./example8-1.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
DEPTNO DEPTNAME MGRNO ADMRDEPT LOCATION
------ ------------------------------------ ------ -------- ---------
A00 SPIFFY COMPUTER SERVICE DIV. 000010 A00 -
B01 PLANNING 000020 A00 -
C01 INFORMATION CENTER 000030 A00 -
D01 DEVELOPMENT CENTER - A00 -
D11 MANUFACTURING SYSTEMS 000060 D01 -
D21 ADMINISTRATION SYSTEMS 000070 D01 -
E01 SUPPORT SERVICES 000050 A00 -
E11 OPERATIONS 000090 E01 -
E21 SOFTWARE SUPPORT 000100 E01 -
F22 BRANCH OFFICE F2 - E01 -
G22 BRANCH OFFICE G2 - E01 -
H22 BRANCH OFFICE H2 - E01 -
I22 BRANCH OFFICE I2 - E01 -
J22 BRANCH OFFICE J2 - E01 -
14 record(s) selected.
Listing 8-2Output of Listing 8-1
尽管我们的输出和本章开始时测试 Db2 命令的显示之间有一些小的差异,但本质上是相同的。我们特意用两个空格隔开这些列,以便在视觉上更好地分隔这些列。包含NULL数据的记录有一个破折号来表示列开始字符中的NULL值。
在这一点上,我们应该指出这本书有一个附录专门用来记录ibm_dbAPI。尽管这些信息取自于ibm_db文档,我们还是添加了一些注释,希望能使文档更加清晰,并增强文档,使您的程序更具可读性。
使用参数标记
我们的下一个例子稍微复杂一些。它使用所谓的参数标记来允许将 Python 变量信息动态替换到 SQL 语句中。这意味着参数标记的值来自 SQL 语句之外。为了实现这一点,我们必须使用一些不同的 ibm_db API 调用来准备 SQL 语句,替换变量,然后执行语句。
我们将基本上使用与上一个例子相同的代码,但是代码的一个部分会有所不同。修改后的代码如清单 8-3 所示。
.
.
.
# get the records from the database
sqlstmt = 'SELECT projname, deptno FROM project WHERE deptno = ?'
prepstmt = ibm_db.prepare(connID, sqlstmt)
if prepstmt is False:
print("Unable to prepare the SQL statement.")
exit(-1)
deptid = 'B01'
retcode = ibm_db.bind_param(prepstmt, 1, deptid, ibm_db.SQL_PARAM_INPUT, \
ibm_db.SQL_CHAR)
try:
results = ibm_db.execute(prepstmt)
except Exception:
pass
# If the sql statement could not be executed, display an error message and exit
if results is False:
print("\nERROR: Unable to execute the SQL statement specified.")
ibm_db.close(connID)
exit(-1)
.
.
.
Listing 8-3The Modified Code
请记住,除了两个 Python 语句之外,这一部分之前和之后的代码与清单 8-1 中的代码是相同的,因此,如果您没有使用来自 press 的打包代码,那么在重新创建这些代码时要小心。
除了语句中的最后一个字符,SQL 语句非常正常。问号(?)标识参数标记。这是我们将在代码中替换 Python 变量的地方。一条 SQL 语句可以有任意多个参数标记,并且可以包含数据甚至 SQL 关键字。在我们的例子中,它将包含我们正在寻找的特定部门号。
下一行是对ibm_db.prepare()的调用。这将解析 SQL 语句并记下任何参数标记。这必须在将数据代入 SQL 语句之前完成。然后,我们检查来自ibm_db.prepare()的返回代码,以确保在我们继续之前成功。
下一条语句设置我们将代入 SQL 语句的 Python 变量。这个价值从何而来,完全取决于你。它可能是 Python 程序的输入参数,也可能来自文件,或者您甚至可以提示用户输入值。只要确保在使用它之前对值进行适当的错误检查。
下一条语句调用ibm_db.bind_param()将 Python 变量绑定到 SQL 语句。该函数的第一个参数标识前一次调用ibm_db.prepare()时输出的准备好的语句。第二个参数是要在 SQL 语句中替换(或绑定)的 Python 变量。在这种情况下,使用的是部门编号。如果您在 SQL 语句中编码了多个参数标记,我们就需要这样做。对于每个参数标记,您需要单独调用 ibm_db.bind_param()。第三个参数指定标记是输入、输出还是输入/输出变量。第四个参数指定正在传递的变量的 SQL 类型。
下一组语句围绕着我们对ibm_db.execute()的调用,用于错误检查。这个函数实际上执行准备好的 SQL 语句。下面的代码检查以确保执行成功。
在执行 SQL 语句后,之后的代码是我们对三个函数的调用,然后打印 SQL 语句的结果。这段代码与我们之前的例子没有变化。
强烈建议您阅读ibm_db.prepare()功能的文档。有大量的参数类型来标识您应该熟悉的 SQL 数据类型。
带有参数标记的 SQL 语句可能是程序中最常用的语句类型。它们非常灵活,只需要多一点代码就可以容纳在您的 Python 代码中。
清单 8-4 提供了这个例子的输出。
$ ./example8-2.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
PROJNAME DEPTNO
------------------------ ------
WELD LINE PLANNING B01
Listing 8-4Output from Listing 8-3
我们的 SQL 语句只指定了两列的检索,并且只有一个部门被指定为B01。该报告很小,因为只有一个指定了编号的部门,但是它指出了参数标记非常有用的地方。
关于参数标记的更多信息
下一个示例使用多个参数标记来创建用于查询project表的值的选择。基本上,我们想列出所有指定了department号码的project名字。
同样,我们将使用本项目第一个例子中的基本代码来生成报告,同时只显示修改后的代码(清单 8-5 )。
.
.
.
# get the records from the database
sqlstmt = 'SELECT projname, deptno FROM project WHERE deptno = ? OR deptno = ?'
prepstmt = ibm_db.prepare(connID, sqlstmt)
if prepstmt is False:
print("Unable to prepare the statement.")
exit(-1)
deptid1 = 'B01'
deptid2 = 'D01'
retcode = ibm_db.bind_param(prepstmt, 1, deptid1, ibm_db.SQL_PARAM_INPUT, \
ibm_db.SQL_CHAR)
retcode = ibm_db.bind_param(prepstmt, 2, deptid2, ibm_db.SQL_PARAM_INPUT, \
ibm_db.SQL_CHAR)
results = ibm_db.execute(prepstmt)
.
.
.
Listing 8-5Using Multiple Parameter Values
请记住,除了两个 Python 语句之外,这一部分之前和之后的代码与清单 8-1 中的代码是相同的,因此,如果您没有使用来自 press 的打包代码,那么在重新创建这些代码时要小心。
该查询的select语句包含两个参数值。第一个标记将标记为1;,第二个标记为2。在 SQL 语句中,参数标记总是从左向右标记。
接下来,我们准备调用ibm_db.prepare()的 SQL 语句,就像所有带参数标记的 SQL 语句一样。如果调用返回False,,那么我们产生一个错误消息并退出 Python 程序。
清单中接下来是将被替换到参数标记中的 Python 变量的赋值。同样,这些可以来自任何地方。我们在这里给它们赋值只是为了尽可能清楚地说明发生了什么。
现在我们调用ibm_db.bind_param()两次,每个参数标记一次。一旦值被绑定到 SQL 预准备语句,它将保持绑定,直到再次调用ibm_cb.bind_param()。
最后,我们调用ibm_db.execute()来获得查询结果。
清单 8-6 显示了这个查询的输出。
$ ../examples/example8-3.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
PROJNAME DEPTNO
------------------------ ------
ADMIN SERVICES D01
WELD LINE AUTOMATION D01
WELD LINE PLANNING B01
3 record(s) selected.
Listing 8-6Output from Listing 8-5
该报告显示,我们有三个项目将指定的部门编号用作参数标记,两个项目用于department 'D01',一个项目用于部门“B01”。这是参数标记在程序中非常有用的另一个好例子。您可以将所需的数据存储在文件中,或者将其作为参数输入到 Python 程序中,并在不修改 Python 脚本的情况下更改报告。
使用参数标记生成多个报告
清单 8-7 展示了如何用一个带参数标记的 SQL 语句创建多个报告。正如您将看到的,这实际上比听起来容易。
同样,我们将使用本项目第一个示例中的基本代码来生成报告,同时只显示修改后的代码。
.
.
.
# get the records from the database
sqlstmt = """SELECT empno, firstnme, midinit, lastname, salary
FROM employee WHERE salary < ? and salary > ?
ORDER BY salary"""
prepstmt = ibm_db.prepare(connID, sqlstmt)
if prepstmt is False:
print("Unable to prepare the statement.")
exit(-1)
salaries = (('35000.00', '0.00'), ('50000.00', '40000.00'))
for i in (salaries):
(salary1, salary2) = i
retcode = ibm_db.bind_param(prepstmt, 1, salary1, ibm_db.SQL_PARAM_INPUT, \
ibm_db.SQL_CHAR)
retcode = ibm_db.bind_param(prepstmt, 2, salary2, ibm_db.SQL_PARAM_INPUT, \
ibm_db.SQL_CHAR)
results = ibm_db.execute(prepstmt)
headerLines = getColNamesWidths(prepstmt)
titleLines = populateColTitleLines(headerLines)
dataLines = populateLines(prepstmt, headerLines)
selrecords = len(dataLines)
#print the result lines
for line in titleLines:
print(line)
for line in dataLines:
print(line)
# print the number of records returned
print('\n ' + str(selrecords) + ' record(s) selected.\n')
.
.
.
Listing 8-7Producing Multiple Reports with One SQL Statement
乍一看,这个清单与我们的其他例子有些不同。主要原因是我们将报告打印代码移到了循环中。我们这样做是因为我们需要为每次调用ibm_db.execute()函数打印一份报告。
我们的 SQL 语句稍微复杂一些,因为我们在末尾添加了一个ORDER BY子句,以便输出是有序的。它仍然有两个参数标记来指定薪水的范围。
接下来,我们准备 SQL 语句并测试结果中的错误。
下一条语句建立了一个 Python 列表数组,指定了我们想要的工资范围。指定了两个范围,每个范围将在单独调用ibm_db.execute()函数时使用。
剩下的代码是一个循环,它采用一组salary值来查询employee表。代码的工作方式与前面的例子一样,只是报告打印代码被移到了循环内部,这样两个查询都能够打印它们的报告。
本例中需要注意的一点是,我们不必多次准备 SQL 语句。这是特意设计的,以便准备好的 SQL 语句可以多次使用,就像我们的示例一样。要生成可能不同的报告,只需将新值重新绑定到参数标记,然后执行语句。
清单 8-8 提供了这个例子的输出。
$ ../examples/example8-4.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
EMPNO FIRSTNME MIDINIT LASTNAME SALARY
------ ------------ ------- --------------- -----------
200340 ROY R ALONZO 31840.00
1 record(s) selected.
EMPNO FIRSTNME MIDINIT LASTNAME SALARY
------ ------------ ------- --------------- -----------
000230 JAMES J JEFFERSON 42180.00
000340 JASON R GOUNOT 43840.00
000170 MASATOSHI J YOSHIMURA 44680.00
000330 WING LEE 45370.00
200280 EILEEN R SCHWARTZ 46250.00
200010 DIAN J HEMMINGER 46500.00
000260 SYBIL P JOHNSON 47250.00
000240 SALVATORE M MARINO 48760.00
000250 DANIEL S SMITH 49180.00
000120 SEAN O'CONNELL 49250.00
000220 JENNIFER K LUTZ 49840.00
11 record(s) selected.
Listing 8-8Multiple Reports from a Single SQL Statement
我们已经成功地在两个不同的salary范围内生成了两个报告,并按salary字段对记录进行了排序。我们对报告打印代码所做的惟一更改是在报告的末尾添加了一个换行符,这样两个报告之间就有了一些间隔。
使用没有绑定变量的参数标记
当有大量数据要加载或更新到表中时,最好使用这个例子。ibm_db.execute()函数有一个额外的可选参数,可以用来传递参数标记值,而不是为每个参数标记调用ibm_db.bind_param()函数。
当要插入/更新的行来自一个文件或另一个外部资源时,使用ibm_db.execute()效果最好。在我们的例子中,我们使用一个文件,该文件包含要插入到employee表中的行。该文件被构造成一个逗号分隔值(CSV)文件,每行数据代表一个要添加到employee表中的新行。
本例中要解决的额外问题是将输入文件中的每一行数据从单个字符串转换为 Python tuple的一组值,这是ibm_db.execute()函数所需要的。在清单 8-9 显示后,我们将对此进行更详细的讨论。
#!/usr/bin/python
import sys, getpass
import ibm_db
# main program
driver = "{IBM DB2 ODBC DRIVER}" # Db2 driver information
host = '192.168.1.201' # database host IP or dns address
port = "50000" # host port
db = "sample" # database registered name
uid = None # userid on the database host
pw = None # password of the uid
autocommit = '' # autocommit default override
connecttype = '' # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
print("The password you entered is incorrect.")
exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host + \
';PORT=' + port + ';DATABASE=' + db + \
';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, autocommit, connecttype)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# Add new designer employees to the employee table
sql = """INSERT INTO employee (empno, firstnme, midinit, lastname,
workdept, phoneno, hiredate, job, edlevel, sex, birthdate,
salary, bonus, comm) VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"""
stmt = ibm_db.prepare(connID, sql)
if stmt:
inserts = 0
with open('./example8-5.csv') as f:
line = f.readline()
while len(line) > 0:
emp_list = line.split(',')
for i in range(0, len(emp_list)):
emp_list[i] = emp_list[i].rstrip("' \n")
emp_list[i] = emp_list[i].lstrip("' ")
emp = tuple(emp_list)
result = ibm_db.execute(stmt, emp)
if result is False:
print("Unable to execute the SQL statement.")
exit(-1)
inserts += 1
line = f.readline()
print(str(inserts) + ' employees inserted successfully.')
# Now delete those new employees
ibm_db.exec_immediate(connID, "delete from employee where empno = '000350'")
ibm_db.exec_immediate(connID, "delete from employee where empno = '000360'")
ibm_db.exec_immediate(connID, "delete from employee where empno = '000370'")
ibm_db.exec_immediate(connID, "delete from employee where empno = '000380'")
print('4 employees deleted successfully.')
ibm_db.close(connID)
exit(0)
Listing 8-9Parameter Markers Without Binding Variables
第一个语句是我们程序所需的标准 Python import语句。
下一部分代码是我们访问 Db2 数据库的标准设置,提示用户输入用户 id 和密码,并连接到 Db2 数据库。
下一段代码不同于我们前面的例子。我们为将要插入的每一列数据设置了一个带有参数标记的 SQL INSERT语句。在本例中,这恰好是employee表中的所有列。接下来,我们准备最终执行的语句。
下一个代码块是所有工作发生的地方。我们首先打开包含要插入到表中的信息的文件。然后我们一次读一行。接下来,由于该文件是一个 CSV 结构,我们在每个逗号处拆分该行。分割的片段在每个片段中都有我们不需要的数据,所以我们删除了前导和尾随空格以及单引号。最后,我们将列值的 Python list改为 Python tuple。
现在我们可以用列值的tuple作为函数调用中的第二个参数来调用ibm_db.execute()。然后,我们检查以确保insert正常工作,然后读取下一行。
我们做的最后一件事是删除我们添加到employee表中的行。这将使数据库保持其原始状态。
最后,我们关闭数据库连接。
清单 8-10 显示了程序的所有输出。这里没什么可看的,但这是程序成功执行的结果。
$ ./example8-5.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
4 employees inserted successfully.
4 employees deleted successfully.
Listing 8-10Output from Listing 8-9
这个例子表明,只需在一个循环中做一点工作,就可以消除对ibm_db.bind_param()函数的 14 次调用。它还消除了使用 14 个不同的变量来保存信息,并用一个简单的 Python list和一个单独的tuple来代替它们。
我们应该在这里添加另一个注释,以便可能清理一个项目。Python 中的元组是不可变的。这意味着一旦创建了它们,就不能用其他成员来修改或扩展它们。这就是为什么我们必须在一次调用中将 Python list转换成tuple。
连接表格
Db2 支持查询中的表连接。有两种类型的联接–内部联接和外部联接。自从 Db2 出现以来就支持内部连接,但是外部连接是一个相对较新的概念。我们将在本节讨论这两种类型。
内部联接
在 SQL 中,内部联接有两种形式。内部连接的第一种形式可以追溯到 Db2 的初期,并且编码起来相当简单。第二种形式的内部联接同样易于编码,并且更容易准确地确定要联接什么以及如何联接。
下面的清单 8-11 显示了旧形式的内部连接。这只是整个程序的摘录,只是为了展示 SQL 语句及其处理方式。
# get the records from the database
sqlstmt = """SELECT e.empno, e.lastname, d.deptname FROM emp e, dept d
WHERE e.workdept = d.deptno AND d.deptname = ?"""
prepstmt = ibm_db.prepare(connID, sqlstmt)
if prepstmt is False:
print("Unable to prepare the statement.")
exit(-1)
deptnme = 'SOFTWARE SUPPORT'
retcode = ibm_db.bind_param(prepstmt, 1, deptnme, ibm_db.SQL_PARAM_INPUT, \
ibm_db.SQL_CHAR)
results = ibm_db.execute(prepstmt)
headerLines = getColNamesWidths(prepstmt)
titleLines = populateColTitleLines(headerLines)
dataLines = populateLines(prepstmt, headerLines)
selrecords = len(dataLines)
Listing 8-11The Old Form of an Inner Join
这个程序的输出将只显示在被称为“软件支持”的部门工作的雇员那个部门总共应该只有六名员工。这两个表在employee表中的workdept列和department表中的deptno列连接在一起。一个额外的约束要求只查看deptname等于“软件支持”的行
如您所见,这个例子非常简单。当 SQL 语句有许多约束,并且 SQL 语句中的连接列不容易被发现时,就会出现问题。为了解决这个问题并方便地支持外部联接,在 SQL 语句中添加了一个新的子句来显式指定联接的类型。
下面的清单 8-12 展示了一个内部连接的新语法,它获得了与前一个例子相同的数据。
# get the records from the database
sqlstmt = """SELECT e.empno, e.lastname, d.deptname FROM emp e
INNER JOIN dept d ON e.workdept = d.deptno AND d.deptname = ?"""
prepstmt = ibm_db.prepare(connID, sqlstmt)
if prepstmt is False:
print("Unable to prepare the statement.")
exit(-1)
deptnme = 'SOFTWARE SUPPORT'
retcode = ibm_db.bind_param(prepstmt, 1, deptnme, ibm_db.SQL_PARAM_INPUT, \
ibm_db.SQL_CHAR)
results = ibm_db.execute(prepstmt)
headerLines = getColNamesWidths(prepstmt)
titleLines = populateColTitleLines(headerLines)
dataLines = populateLines(prepstmt, headerLines)
selrecords = len(dataLines)
Listing 8-12The New Form of an Inner Join
本例中唯一修改的是 SQL 语句。否则,程序不变。SQL 语句有一个新子句 INNER JOIN 子句和。这些显式声明了正在对指定的两个表进行内部连接。在这种情况下,没有 WHERE 子句,因为 ON 子句替换了它。
这个程序的输出和前面的程序完全一样。这里给出这两个例子的原因是仍然有大量的程序仍然使用旧形式的内部连接,根据需要识别和处理这些程序是很重要的。
外部连接
外部联接与内部联接的不同之处在于,它不仅能够显示与内部联接相似的结果行,还能够显示两个表的联接列都设置为 NULL 的行。因此,它会显示由于其中一列的值无效而无法连接在一起的行。
这实际上是 Db2 的一个很少使用的特性,因此我们不打算在这里展示一个例子。有关更多信息,请参考 Web 上的 IBM Db2 知识中心。
插入、更新和删除
在这一节中,我们将看看 SQL insert、update和delete语句,以及它们与 Python 和参数标记的关系。实际上,我们在前一节的示例中看到了delete语句,但是这里我们将更详细地讨论所有这些语句。
清单 8-13 是使用所有四个 SQL 数据操作语句的例子。它将一个新条目插入到employee表中,更新它,获取它进行显示,然后删除该条目。这将使employee表保持其原始状态。
#!/usr/bin/python
import sys, getpass
from decimal import *
import ibm_db
# main program
driver = "{IBM DB2 ODBC DRIVER}" # Db2 driver information
host = '192.168.1.201' # database host IP or dns address
port = "50000" # host port
db = "sample" # database registered name
uid = None # userid on the database host
pw = None # password of the uid
autocommit = '' # autocommit default override
connecttype = '' # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
print("The password you entered is incorrect.")
exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host + \
';PORT=' + port + ';DATABASE=' + db + \
';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, autocommit, connecttype)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# Add new designer employees to the employee table
sql = """INSERT INTO employee (empno, firstnme, midinit, lastname,
workdept, phoneno, hiredate, job, edlevel, sex, birthdate,
salary, bonus, comm) VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"""
stmt = ibm_db.prepare(connID, sql)
if stmt is False:
print("Unable to prepare the SQL statement.")
exit(-1)
emp = ('000350', 'DAVID', 'W', 'ANDREWS', 'D11', '3634', '1969-03-20', \
'DESIGNER', 20, 'M', '1951-06-14', 40160.00, 500, 2220)
result = ibm_db.execute(stmt, emp)
if result is False:
print("Unable to execute the SQL statement.")
ibm_db.close(connID)
exit(-1)
# Now update the salary
sql = "UPDATE employee SET salary = ? where empno = '000350'"
stmt = ibm_db.prepare(connID, sql)
if stmt is False:
print("Unable to prepare the SQL statement.")
exit(-1)
salary = str(Decimal('40160.00') * Decimal('1.1'))
retcode = ibm_db.bind_param(stmt, 1, salary, ibm_db.SQL_PARAM_INPUT, \
ibm_db.SQL_CHAR)
result = ibm_db.execute(stmt)
if result is False:
print("Unable to execute the SQL statement.")
ibm_db.close(connID)
exit(-1)
# Ensure the salary is updated
sql = "select empno, salary from employee where empno = '000350'"
results = ibm_db.exec_immediate(connID, sql)
if results is False:
print("\nERROR: Unable to execute the SQL statement specified.")
ibm_db.close(connID)
exit(-1)
(empno, salary) = ibm_db.fetch_tuple(results)
print('empno: ', str(empno), ' old salary: 40160.00 new salary: ', str(salary))
# Now delete the employee we added
ibm_db.exec_immediate(connID, "delete from employee where empno = '000350'")
ibm_db.close(connID)
exit(0)
Listing 8-13Insert, Update, and Delete Example
示例清单与我们所有的示例一样,只有一个例外。为了更新salary,它在内部是一个decimal字段,我们需要导入decimal模块。这样,我们可以保持调整与 SQL 字段一致。
第一个任务是向employee表中插入一条新记录。我们在 INSERT 语句中使用参数字段。这就是 SQL 语句中所有的问号。然后我们调用ibm_db.prepare()来准备语句。
接下来,我们为要插入的数据创建tuple。然后,我们调用ibm_db.execute()将新行插入数据库。
下一个任务是update我们刚刚插入的记录。我们为新的salary创建带有单个参数标记的 SQL UPDATE 语句,然后准备该语句。下一条语句将当前薪金转换为十进制字段,并将其增加 10%。这样做的结果是一个新字段,也是一个十进制字段。然后,我们将新的薪水绑定到 UPDATE SQL 语句和execute语句。
为了确保update成功,并向我们自己证明它是有效的,我们用 SELECT 语句从数据库中获取数据并显示结果。结果显示员工编号、旧工资和新工资。
最后,我们delete新的行,让数据库回到它最初的开始状态。
这个例子有点复杂,但是它是所有 SQL 数据操作语句的一个例子。这个例子也可以作为你几乎每天都会遇到的许多活动的起点。
清单 8-14 具有清单 8-13 的输出。它显示了新员工在更新前后的工资。
$ ./example8-6.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
empno: 000350 old salary: 40160.00 new salary: 44176.00
Listing 8-14Output from Listing 8-13
值得注意的是,10%增长的计算是完全正确的,不会受到浮点计算的不准确性的影响。通过使用decimal包来执行计算,我们已经确保应用了正确的增量。
其他一些 ibm _ db APIs
在这一节中,我们将看看您可能会用到的其他一些ibm_db包 API。这些不是数据操作语句,而是可以提供关于数据库和 Python 程序处理环境各个方面的信息。
需要注意的是,这并没有涵盖ibm_db包中所有剩余的 API。其余的 API 很少使用,并且有充分的文档记录,因此它们可以很容易地集成到您的程序中。
清单 8-15 显示了这个例子的代码。我们将在上市后对其进行审查。
#!/usr/bin/python
import sys, getpass
import ibm_db
# main program
driver = "{IBM DB2 ODBC DRIVER}" # Db2 driver information
host = '192.168.1.201' # database host IP or dns address
port = "50000" # host port
db = "sample" # database registered name
uid = None # userid on the database host
pw = None # password of the uid
autocommit = '' # autocommit default override
connecttype = '' # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
print("The password you entered is incorrect.")
exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host + \
';PORT=' + port + ';DATABASE=' + db + \
';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, autocommit, connecttype)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# Test if the connection is active
active = ibm_db.active(connID)
if active:
print('The currect connection is active.')
else:
print('*The current connection is not active.')
# Test autocommit
commit = ibm_db.autocommit(connID)
if active:
print('Autocommit is active.')
else:
print('*Autocommit is not active.')
# Get the client info
clientinfo = ibm_db.client_info(connID)
if clientinfo:
print('Client info:')
print(' APPL_CODEPAGE: ', clientinfo.APPL_CODEPAGE)
print(' CONN_CODEPAGE: ', clientinfo.CONN_CODEPAGE)
print(' DATA_SOURCE_NAME: ', clientinfo.DATA_SOURCE_NAME)
print(' DRIVER_NAME: ', clientinfo.DRIVER_NAME)
print(' DRIVER_ODBC_VER: ', clientinfo.DRIVER_ODBC_VER)
print(' DRIVER_VER: ', clientinfo.DRIVER_VER)
print(' ODBC_SQL_CONFORMANCE: ', clientinfo.ODBC_SQL_CONFORMANCE)
print(' ODBC_VER: ', clientinfo.ODBC_VER)
else:
print('Could not obtain client info.')
# Get column priviliges, if they exist
priv = ibm_db.column_privileges(connID, None, uid.upper(), 'employee', 'workdept')
row = ibm_db.fetch_assoc(priv)
if row:
print('Sample database, table department, column priviliges:')
print(" Schema name : {}" .format(row['TABLE_SCHEM']))
print(" Table name : {}" .format(row['TABLE_NAME']))
print(" Column name : {}" .format(row['COLUMN_NAME']))
print(" Privilege grantor : {}" .format(row['GRANTOR']))
print(" Privilege recipient : {}" .format(row['GRANTEE']))
print(" Privilege : {}" .format(row['PRIVILEGE']))
print(" Privilege is grantable : {}" .format(row['IS_GRANTABLE']))
else:
print('No column privileges to retrieve.')
# Get column metadata, if it exists
coldata = ibm_db.columns(connID, None, None, 'employee', 'empno')
row = ibm_db.fetch_assoc(coldata)
if row:
print('Sample database, table department, columns metadata:')
table_name = row['TABLE_NAME']
column_name = row['COLUMN_NAME']
print(" Table name : {}" .format(table_name))
print(" Column name : {}" .format(column_name))
else:
print('No column metadata to retrieve.')
# Test SQL commit
rc = ibm_db.commit(connID)
if rc:
print('Commit succeeded.')
else:
print('*Commit did not succeed.')
ibm_db.close(connID)
exit(0)
Listing 8-15Some Miscellaneous ibm_db APIs
与往常一样,该程序的起始代码与我们的其他示例相同。第一个测试是ibm_db.active() API。这个 API 确定作为参数传递的连接是否仍然处于活动状态。虽然在大多数程序中并不重要,但在使用ibm_db.pconnect() API 的程序中会用到它。
下一个使用的 API 是ibm_db.autocommit()。这个 API 可以获取和设置指定连接的自动提交标志。
下一个 API 是ibm_db.client_info()。这个 API 返回关于连接的客户端的信息。它列出了使用的代码页、驱动程序名称和版本以及 SQL 标准符合性。
接下来,ibm_db.column_priviliges()查询表中分配给特定列的特权。这些特权可能存在,也可能不存在。示例数据库中的表通常没有被分配特殊权限,因此 API 不会从这些表返回任何数据。
ibm_db.columns() API 用于查询分配给表中某一列的元数据。示例数据库没有为我们能够确定的列分配元数据。
我们测试的最后一个 API 是ibm_db.commit() API。这个 API 在调用时强制提交数据库。无论自动提交标志是否打开,这都应该有效。
清单 8-16 显示了清单 8-15 在示例客户机上的输出。
$ ./example8-7.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
The currect connection is active.
Autocommit is active.
Client info:
APPL_CODEPAGE: 1208
CONN_CODEPAGE: 1208
DATA_SOURCE_NAME: SAMPLE
DRIVER_NAME: libdb2.a
DRIVER_ODBC_VER: 03.51
DRIVER_VER: 11.01.0405
ODBC_SQL_CONFORMANCE: EXTENDED
ODBC_VER: 03.01.0000
No column privileges to retrieve.
No column metadata to retrieve.
Commit succeeded.
Listing 8-16Output from the Listing 8-15 Program
的输出非常简单。大多数情况下,只有一行结果来自被测试的 API。客户机信息 API 有更多的数据,所有这些都是需要这些信息的程序员感兴趣的。
创建数据库对象
ibm_db库还可以帮助您创建数据库对象,比如表、索引和表空间。必要时,它还可以删除现有的数据库对象。这些操作是通过 CREATE 和 DROP SQL 语句完成的。
此外,本节还将介绍另外两个ibm_dbAPI,即stmt_error和stmt_errormsg。这些 API 用于报告错误情况和解释。它们可以在prepare()、exec_immediate()和callproc()API 之后使用,包含有助于您诊断 SQL 语句问题的信息。
清单 8-17 展示了如何使用 Python 模块创建一个表。需要注意的一点是:下面的例子没有真正的意义,只是严格地用于展示表列定义的例子。
#!/usr/bin/python
import sys, getpass
import ibm_db
# main program
driver = "{IBM DB2 ODBC DRIVER}" # Db2 driver information
host = '192.168.1.201' # database host IP or dns address
port = "50000" # host port
db = "sample" # database registered name
uid = None # userid on the database host
pw = None # password of the uid
autocommit = '' # autocommit default override
connecttype = '' # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
print("The password you entered is incorrect.")
exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host + \
';PORT=' + port + ';DATABASE=' + db + \
';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, autocommit, connecttype)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# create the sample table
sqlstmt = """CREATE TABLE myexampletable (
C01 INTEGER NOT NULL
GENERATED ALWAYS AS IDENTITY
(START WITH 1, INCREMENT BY 1),
C02 CHAR(50),
C03 VARCHAR(70),
C04 DEC(15,2),
C05 FLOAT(21),
C06 CLOB(1K),
C07 VARGRAPHIC(2000),
C08 BLOB(1M),
C09 DATE,
C10 TIME,
C11 TIMESTAMP,
C12 XML,
C13 BOOLEAN,
PRIMARY KEY(C01))"""
try:
rc = ibm_db.exec_immediate(connID, sqlstmt)
except:
print("Create ' {} ' failed with ".format(sqlstmt))
print("Error : {}".format(ibm_db.stmt_errormsg()))
exit(-1)
print('\n The CREATE statement executed successfully.')
ibm_db.close(connID)
exit(0)
Listing 8-17Creating a Sample Table
关于这个例子,有一些事情需要注意。首先,除了作为例子,表格没有任何意义。它不依赖于示例数据库中的任何其他表。这只是 Db2 提供给用户的一些列数据类型的一个例子。
其次,try / except块增加了一些不错的异常处理。它从exec_immidiate() API 中寻找非零返回代码,如果没有找到,它就执行except代码块。
第三,except 块调用了stmt_errormsg() API,它将打印出与 SQL 语句中的错误相关的错误消息。
第四,这个例子中没有格式化代码,因为它不返回结果表,只是一个简单的返回代码。
C01 列是递增列的一个例子。每次插入或更改新行时,数据库都会生成一个值放在该列中。按照 SQL 语句中最后一个子句的定义,该列也是表的主键。
C02 是一个字符串,总是有 50 个字符与之相关联。当您将少于 50 个字符的字符串分配给此列时,系统会用空格填充右侧的字符串,直到其长度达到 50 个字符。如果您尝试添加超过 50 个字符的字符串,它将被截断为 50 个字符。
C03 列是可变字符串。这种类型的字符串不会在右边填充空格。相反,会记录字符串的实际长度。但是如果字符串长度超过 70 个字符,它将被截断为 70 个字符。
C04 列是一个固定的十进制数,长度为 15 个数值,有 2 个小数位。
C05 列是一个浮点字段,总共可以容纳 21 个数字字符。
C06 列是一个可变图形字段,最大值为 1,000,000 字节。
C07 列是可变图形字段,最大 200 字节。
C08 列是长度为 1,000,000 字节的二进制 LOB。
C09 列是包含数据值的字段。
C10 列是包含时间值的字段。
C11 列是包含时间戳值的字段。该字段包含日期和时间值,它们连接在一起形成时间和日期的实例。
C12 列是一个 XML 字段。这个字段实际上是指向包含 XML 数据的文件系统的指针。
C13 列是一个布尔型字段,用于简单的是/否值。
现在我们已经查看了该表,您可能希望将其从数据库中删除。清单 8-18 会帮你做到。
#!/usr/bin/python
import sys, getpass
import ibm_db
# main program
driver = "{IBM DB2 ODBC DRIVER}" # Db2 driver information
host = '192.168.1.201' # database host IP or dns address
port = "50000" # host port
db = "sample" # database registered name
uid = None # userid on the database host
pw = None # password of the uid
autocommit = '' # autocommit default override
connecttype = '' # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
print("The password you entered is incorrect.")
exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host + \
';PORT=' + port + ';DATABASE=' + db + \
';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, autocommit, connecttype)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# create the sample table
sqlstmt = """DROP TABLE myexampletable"""
try:
rc = ibm_db.exec_immediate(connID, sqlstmt)
except:
print("Drop ' {} ' failed with ".format(sqlstmt))
print("Error : {}".format(ibm_db.stmt_errormsg()))
exit(-1)
print('\n The DROP statement executed successfully.')
ibm_db.close(connID)
exit(0)
Listing 8-18Removing a Table
这个程序与前一个程序非常相似,只是它使用 DROP SQL 语句来删除我们在本节开始时创建的表。
获取现有表的属性
有时,您可能需要知道现有 Db2 表中每一列的属性。有了ibm_db库,你可以很容易地获得这些属性。清单 8-19 展示了如何实现这一点。
#!/usr/bin/python
import sys, getpass
import ibm_db
# main program
resultSet = False
dataRecord = False
tableName = "EMP"
sqlDataTypes = {0 : "SQL_UNKNOWN_TYPE", 1 : "SQL_CHAR", 2 : "SQL_NUMERIC", 3 : "SQL_DECIMAL",
4 : "SQL_INTEGER", 5 : "SQL_SMALLINT", 6 : "SQL_FLOAT", 7 : "SQL_REAL", 8 : "SQL_DOUBLE",
9 : "SQL_DATETIME", 12 : "SQL_VARCHAR", 16 : "SQL_BOOLEAN", 19 : "SQL_ROW",
91 : "SQL_TYPE_DATE", 92 : "SQL_TYPE_TIME", 93 : "SQL_TYPE_TIMESTAMP",
95 : "SQL_TYPE_TIMESTAMP_WITH_TIMEZONE", -8 : "SQL_WCHAR", -9 : "SQL_WVARCHAR",
-10 : "SQL_WLONGVARCHAR", -95 : "SQL_GRAPHIC", -96 : "SQL_VARGRAPHIC",
-97 : "SQL_LONGVARGRAPHIC", -98 : "SQL_BLOB", -99 : "SQL_CLOB", -350 : "SQL_DBCLOB",
-360 : "SQL_DECFLOAT", -370 : "SQL_XML", -380 : "SQL_CURSORHANDLE", -400 : "SQL_DATALINK",
-450 : "SQL_USER_DEFINED_TYPE"}
sqlDateTimeSubtypes = {1 : "SQL_CODE_DATE", 2 : "SQL_CODE_TIME", 3 : "SQL_CODE_TIMESTAMP",
4 : "SQL_CODE_TIMESTAMP_WITH_TIMEZONE"}
driver = "{IBM DB2 ODBC DRIVER}" # Db2 driver information
host = '192.168.1.201' # database host IP or dns address
port = "50000" # host port
db = "sample" # database registered name
uid = None # userid on the database host
pw = None # password of the uid
autocommit = '' # autocommit default override
connecttype = '' # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
print("The password you entered is incorrect.")
exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host + \
';PORT=' + port + ';DATABASE=' + db + \
';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, '', '')
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# Attempt to retrieve information about all columns of a table
resultSet = ibm_db.columns(connID, None, None, tableName, None)
# If The Information Desired Could Not Be Retrieved, Display An Error Message And Exit
if resultSet is False:
print("\nERROR: Unable to obtain the information desired\n.")
conn.closeConnection()
exit(-1)
noData = False
loopCounter = 1
while noData is False:
dataRecord = ibm_db.fetch_assoc(resultSet)
if dataRecord is False:
noData = True
else:
# Display Record Header Information
print("Column " + str(loopCounter) + " details:")
print("_________________________________________")
# Display The Information Stored In The Data Record Retrieved
print("Table schema : {}" .format(dataRecord['TABLE_SCHEM']))
print("Table name : {}" .format(dataRecord['TABLE_NAME']))
print("Column name : {}" .format(dataRecord['COLUMN_NAME']))
print("Data type : {}" .format(dataRecord['TYPE_NAME']))
print("Size : {}" .format(dataRecord['COLUMN_SIZE']))
print("Buffer size : {}" .format(dataRecord['BUFFER_LENGTH']))
print("Scale (decimal digits) : ", end="")
if dataRecord['DECIMAL_DIGITS'] == None:
print("Not applicable")
else:
print("{}" .format(dataRecord['DECIMAL_DIGITS']))
print("Precision radix : ", end="")
if dataRecord['NUM_PREC_RADIX'] == 10:
print("Exact numeric data type")
elif dataRecord['NUM_PREC_RADIX'] == 2:
print("Approximate numeric data type")
elif dataRecord['NUM_PREC_RADIX'] == None:
print("Not applicable")
print("Can accept NULL values : ", end="")
if dataRecord['NULLABLE'] == ibm_db.SQL_FALSE:
print("NO")
elif dataRecord['NULLABLE'] == ibm_db.SQL_TRUE:
print("YES")
print("Remarks : {}" .format(dataRecord['REMARKS']))
print("Default value : {}" .format(dataRecord['COLUMN_DEF']))
print("SQL data type : ", end="")
print(sqlDataTypes.get(dataRecord['SQL_DATA_TYPE']))
print("SQL data/time subtype : ", end="")
print(sqlDateTimeSubtypes.get(dataRecord['SQL_DATETIME_SUB']))
print("Data type : {}" .format(dataRecord['DATA_TYPE']))
print("Length in octets : ", end="")
if dataRecord['CHAR_OCTET_LENGTH'] == None
:
print("Not applicable")
else:
print("{}" .format(dataRecord['CHAR_OCTET_LENGTH']))
print("Ordinal position : {}" .format(dataRecord['ORDINAL_POSITION']))
print("Can contain NULL values : {}" .format(dataRecord['IS_NULLABLE']))
# Increment The loopCounter Variable And Print A Blank Line To Separate The
# Records From Each Other
loopCounter += 1
print()
ibm_db.close(connID)
exit(0)
Listing 8-19Obtaining the Column Attributes of an Existing Table
列有许多可用的属性。根据列上定义的数据类型或未定义的数据类型,其中一些将不适用。您感兴趣的主要属性是数据类型或 SQL 数据类型,它决定了存储在列中的数据类型。有些特定的属性仅适用于某些类型的数据,例如,SCALE 属性仅适用于 DECIMAL 数据类型。
清单 8-20 列出了表格中的所有列。在下面的输出中,为了节省空间,我们删除了一些列。
$ ./example8-14.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
Column 1 details:
_________________________________________
Table schema : DB2INST1
Table name : EMP
Column name : EMPNO
Data type : CHAR
Size : 6
Buffer size : 6
Scale (decimal digits) : Not applicable
Precision radix : Not applicable
Can accept NULL values : NO
Remarks : None
Default value : None
SQL data type : SQL_CHAR
SQL data/time subtype : None
Data type : 1
Length in octets : 6
Ordinal position : 1
Can contain NULL values : NO
Column 2 details:
_________________________________________
Table schema : DB2INST1
Table name : EMP
Column name : FIRSTNME
Data type : VARCHAR
Size : 12
Buffer size : 12
Scale (decimal digits) : Not applicable
Precision radix : Not applicable
Can accept NULL values : NO
Remarks : None
Default value : None
SQL data type : SQL_VARCHAR
SQL data/time subtype : None
Data type : 12
Length in octets : 12
Ordinal position : 2
Can contain NULL values : NO
.
.
.
Column 14 details:
_________________________________________
Table schema : DB2INST1
Table name : EMP
Column name : COMM
Data type : DECIMAL
Size : 9
Buffer size : 11
Scale (decimal digits) : 2
Precision radix : Exact numeric data type
Can accept NULL values : YES
Remarks : None
Default value : None
SQL data type : SQL_DECIMAL
SQL data/time subtype : None
Data type : 3
Length in octets : Not applicable
Ordinal position : 14
Can contain NULL values : YES
Listing 8-20Output of Listing 8-19
每个列以及所有属性都显示在完整的输出文件中。清单 8-20 只是完整清单的一小部分摘录。
清单 8-14 Python 程序在许多情况下非常有用,比如发现列中是否允许空值,是否为列指定了默认值,可以提供列的最大大小线索的 SIZE 属性,以及许多其他属性。
获取结果集的属性
与获取现有 Db2 表的属性不同,结果集是保存查询结果的临时表。我们使用这个 API 的原因是,一个结果集可能是两个或多个表的连接,一些列可能在连接中被修改,其属性被连接过程修改。因此,除非通过反复试验,否则很难确定连接列的实际属性。
清单 8-21 展示了如何发现结果集的属性,以便它们可以用来帮助您确定一个列应该如何显示。
#!/usr/bin/python
import sys, getpass
import ibm_db
# main program
resultSet = False
dataRecord = False
tableName = "EMP"
sqlDataTypes = {0 : "SQL_UNKNOWN_TYPE", 1 : "SQL_CHAR", 2 : "SQL_NUMERIC", 3 : "SQL_DECIMAL",
4 : "SQL_INTEGER", 5 : "SQL_SMALLINT", 6 : "SQL_FLOAT", 7 : "SQL_REAL", 8 : "SQL_DOUBLE",
9 : "SQL_DATETIME", 12 : "SQL_VARCHAR", 16 : "SQL_BOOLEAN", 19 : "SQL_ROW",
91 : "SQL_TYPE_DATE", 92 : "SQL_TYPE_TIME", 93 : "SQL_TYPE_TIMESTAMP",
95 : "SQL_TYPE_TIMESTAMP_WITH_TIMEZONE", -8 : "SQL_WCHAR", -9 : "SQL_WVARCHAR",
-10 : "SQL_WLONGVARCHAR", -95 : "SQL_GRAPHIC", -96 : "SQL_VARGRAPHIC",
-97 : "SQL_LONGVARGRAPHIC", -98 : "SQL_BLOB", -99 : "SQL_CLOB", -350 : "SQL_DBCLOB",
-360 : "SQL_DECFLOAT", -370 : "SQL_XML", -380 : "SQL_CURSORHANDLE", -400 : "SQL_DATALINK",
-450 : "SQL_USER_DEFINED_TYPE"}
sqlDateTimeSubtypes = {1 : "SQL_CODE_DATE", 2 : "SQL_CODE_TIME", 3 : "SQL_CODE_TIMESTAMP",
4 : "SQL_CODE_TIMESTAMP_WITH_TIMEZONE"}
driver = "{IBM DB2 ODBC DRIVER}" # Db2 driver information
host = '192.168.1.201' # database host IP or dns address
port = "50000" # host port
db = "sample" # database registered name
uid = None # userid on the database host
pw = None # password of the uid
autocommit = '' # autocommit default override
connecttype = '' # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
print("The password you entered is incorrect.")
exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host + \
';PORT=' + port + ';DATABASE=' + db + \
';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, '', '')
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# Attempt to retrieve information about all columns of a table
sqlstmt = """SELECT e.empno, e.lastname, d.deptname FROM emp e, dept d
WHERE e.workdept = d.deptno AND d.deptname = ?"""
prepstmt = ibm_db.prepare(connID, sqlstmt)
if prepstmt is False:
print("Unable to prepare the statement.")
exit(-1)
deptnme = 'SOFTWARE SUPPORT'
retcode = ibm_db.bind_param(prepstmt, 1, deptnme, ibm_db.SQL_PARAM_INPUT, \
ibm_db.SQL_CHAR)
results = ibm_db.execute(prepstmt)
# If The Information Desired Could Not Be Retrieved, Display An Error Message And Exit
if results is False:
print("\nERROR: Unable to obtain the information desired\n.")
ibm_db.close(connID)
exit(-1)
loopCounter = 1
cols = ibm_db.num_fields(prepstmt)
while loopCounter <= cols:
# Display Record Header Information
print("Column " + str(loopCounter) + " details:")
print("_________________________________________")
# Display The Information Stored In The Data Record Retrieved
print("Column name : {}" .format(ibm_db.field_name(prepstmt, loopCounter)))
print("Data type : {}" .format(ibm_db.field_type(prepstmt, loopCounter)))
print("Size : {}" .format(ibm_db.field_display_size(prepstmt, loopCounter)))
print("Scale (decimal digits) : ", end="")
if ibm_db.field_scale(prepstmt, loopCounter) == None:
print("Not applicable")
else
:
print("{}" .format(ibm_db.field_scale(prepstmt, loopCounter)))
print("Precision : {}" .format(ibm_db.field_precision(prepstmt, loopCounter)))
print("Display size : ", end="")
if ibm_db.field_display_size(prepstmt,loopCounter) == None:
print("Not applicable")
else:
print("{}" .format(ibm_db.field_display_size(prepstmt,loopCounter)))
# Increment The loopCounter Variable And Print A Blank Line To Separate The
# Records From Each Other
loopCounter += 1
print()
ibm_db.close(connID)
exit(0)
Listing 8-21Obtaining the Attributes of a Result Set
这个例子有点类似于清单 8-19 程序,除了可用的属性更少。这主要是由于结果集的性质,它在 Python 程序中主要用于显示目的。
清单 8-22 是清单 8-21 程序的输出。
$ ./example8-15.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
Column 1 details:
_________________________________________
Column name : LASTNAME
Data type : string
Size : 15
Scale (decimal digits) : 0
Precision : 15
Display size : 15
Column 2 details:
_________________________________________
Column name : DEPTNAME
Data type : string
Size : 36
Scale (decimal digits) : 0
Precision : 36
Display size : 36
Column 3 details:
_________________________________________
Column name : False
Data type : False
Size : False
Scale (decimal digits) : False
Precision : False
Display size : False
Listing 8-22Output from Listing 8-21
这里的输出类似于清单 8-14 。添加显示大小属性非常有价值,因为这是正确显示列数据所需的字符数。
ibm_db_dbi 和 Python
ibm_db_dbi模块实际上是一个 Python 脚本,但是它可以像 Python 模块一样被导入。该模块遵循 PEP 249 Python 数据库 API 规范 v2.0,这使得您的程序可以跨不同的数据库移植——至少这是总体思路。这个模块的规范有些松散,因此留下了很大的添加空间,这可能无法移植到其他数据库。ibm_db_dbi就是这种情况。
清单 8-23 是一个使用ibm_db_dbi模块的 Python 程序的例子。
#!/usr/bin/python
import sys, getpass
import ibm_db_dbi
# main program
driver = "{IBM DB2 ODBC DRIVER}" # Db2 driver information
host = '192.168.1.201' # database host IP or dns address
port = "50000" # host port
db = "sample" # database registered name
uid = None # userid on the database host
pw = None # password of the uid
autocommit = '' # autocommit default override
connecttype = '' # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
print("The password you entered is incorrect.")
exit(-1)
#host = host + ':' + port
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host + \
';PORT=' + port + ';DATABASE=' + db + \
';UID=' + uid + ';PWD=' + pw
connID = ibm_db_dbi.connect(dsn=conn_str, conn_options=None)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# get a cursor
cursor = connID.cursor()
sqlstmt = 'select * from department'
cursor.execute(sqlstmt)
# build/print header lines and fetch/print all rows
row = cursor.fetchone()
if row:
rows = 0
cols = len(cursor.description)
col = 0
typecode = []
collen = []
tline1 = ''
tline2 = ''
i = 0
# print the report header lines
while i < cols:
(name,type_code,display_size,internal_size,precision,scale,null_ok) = \
cursor.description[i]
typecode.append(type_code)
collen.append(max(display_size, len(name)))
tline1 = tline1 + ' ' + name + (collen[i]-len(name))*' '
tline2 = tline2 + ' ' + (collen[i]*'-')
i += 1
print(tline1 + '\n' + tline2)
# print each fetched row
while row:
rows += 1
colvals = list(row)
i = 0
line = ''
while i < cols:
(name,type_code,display_size,internal_size,precision,scale,null_ok) = \
cursor.description[i]
if colvals[i] is None:
line = line + ' -' + (collen[i]-1)*' '
elif typecode[i] in ibm_db_dbi.DECIMAL:
line = line + ' ' + (collen[i]-len(colvals[i]))*' ' + colvals[i]
else:
line = line + ' ' + colvals[i] + (collen[i]-len(colvals[i]))*' '
i += 1
print(line)
row = cursor.fetchone()
# print summary
print('\n ' + str(rows) + ' record(s) selected.')
connID.close()
exit(0)
Listing 8-23An ibm_db_dbi Example Python Program
这个例子中的代码很容易理解。真正值得注意的是,生成与图 1-1 相同的输出所需的 Python 代码数量减少了。这有点误导,因为同样是 Python 代码的ibm_db_dbi模块正在做我们在清单 8-1 中为自己做的大量工作。因此,当模块和我们的程序代码放在一起时,大约有相同数量的 Python 代码被执行。
清单 8-24 显示您仍然可以在我们的 SQL 语句中使用参数标记。
# get a cursor
cursor = connID.cursor()
sqlstmt = 'SELECT projname, deptno FROM project WHERE deptno = ? OR deptno = ?'
cursor.execute(sqlstmt, ('B01', 'D01'))
# build/print header lines and fetch/print all rows
Listing 8-24Using Parameter Markers with ibm_db_dbi
我们只是展示了清单 8-9 中使用参数标记的修改后的语句。这将改变程序以匹配清单 8-3 的输出。注意,参数是作为元组在execute函数上传递的。
同样,大部分代码与上一个示例相同。这再次表明,使用ibm_db_dbi模块,我们可以利用它来减少 Python 程序中的代码。请注意,您应该尽可能不使用无法移植的代码。
ibm_db 模块何去何从?
在这一章中,我们已经深入讨论了ibm_db模块,但是它将走向何方?对模块源代码的仔细分析揭示了与 Db2 APIs 的一些不兼容性。开发人员承认这些问题应该得到解决,并且正在想办法解决。他们也承认,对于许多 Python 开发者来说,该模块的文档缺乏足够的例子。
这些和其他模块问题肯定会在未来版本中得到解决,但目前它们不会造成太多问题。正如我们在本书中展示的那样,该模块当然是可用的。但是,为了让 Db2 拥有更多基于 Windows、Linux 和 Unix 的用户,可以做些什么呢?目前,IBM 开发人员正在创建 Python 模块,通过创建从程序的通用接口到ibm_db模块的接口,允许使用通用数据库 API 的程序使用 Db2 作为主数据库。这将允许 Db2 存储程序所需的对象。
到目前为止,开发人员已经开发了四个这样的接口,我们将在下面讨论。
ibm_db_dbi 模块
在上一节中已经讨论了ibm_db_dbi接口,但是在这里值得一提。安装ibm_db模块时会包含该接口。它基于 PEP 248 规范。PEP 249 描述了一个通用接口,它应该支持几乎所有的本地数据库查询接口。ibm_db_dbi接口完全支持这个规范。
上一节有一个如何使用ibm_db_dbi接口的例子。这个接口调用ibm_db接口中的 API 来访问 Db2 数据库。但是需要注意的是,如果您的数据库在未来发生变化,您的 Python 程序需要的唯一变化是修改import语句,使其指向新数据库使用的接口文件。
通过使用一个通用的数据库接口,你的程序变得更容易移植,也更容易维护。使用通用数据库接口的缺点是程序员会失去一些本地数据库接口的高级特性。因此,在选择程序的数据库接口时,要仔细权衡这些选择。
Django 数据库接口
Django 系统是构建基于 web 的页面的一个非常强大的机制。它有许多有用的特性,并且被世界上大多数的网络程序员所使用。Django 还有一个通用的数据库接口,数据库供应商可以用它来翻译数据库支持的 API。这与 PEP 248 接口的工作方式非常相似。Db2 提供了ibm_db_django模块,该模块执行从 Django 访问 Db2 所必需的翻译。
要安装ibm_db_django模块,执行以下命令:
sudo pip install ibm_db
sudo pip install ibm_db_django
这将在您的机器上安装最新版本的ibm_db_django和ibm_db模块。剩下要做的就是在 Django 配置文件中配置模块,这样就可以使用 Db2 作为 Django 应用的存储设备了。清单 8-25 显示了需要添加到 Django settings.py文件中的内容。
DATABASES = {
'default': {
'ENGINE' : 'ibm_db_django',
'NAME' : 'mydjangodb',
'USER' : 'db2inst1',
'PASSWORD' : 'xxxxxx2',
'HOST' : 'localhost',
'PORT' : '50000',
'PCONNECT' : True, #Optional property, default is false
}
}
Listing 8-25Django settings.py File Extract
下面几行还需要添加到settings.py文件中的 tuple INSTALLED_APPS 中。
'django.contrib.flatpages',
'django.contrib.redirects',
'django.contrib.comments',
'django.contrib.admin',
通用的 Django 数据库 API 完全受ibm_db_django模块支持,所以在 Python 应用中不需要特殊的编码。但是,如果您没有权限,可能需要 Db2 管理员在 Db2 服务器上建立数据库。
Django 默认执行时没有事务,即处于自动提交模式。这种默认设置通常不是您在 web 应用中想要的。您应该记得在 Django 中打开事务支持。
要了解更多关于 Django 的信息,请访问 www.djangoproject.com/ 网站。
SQLAlchemy 适配器
SQLAlchemy 是一个对象关系适配器。它转换 Python 程序中的对象信息,并将其映射到关系数据库,以允许对象从程序的一次执行保存到下一次执行。转换是通过一组众所周知的模式执行的,因此存储对象的可靠性是持久的。
SQLAlchemy 工具包在 Python 领域得到了广泛使用,因为它在转换过程中使用了成熟的规则。这些工具很容易理解,在其他面向对象语言中也有很长的使用历史。
要安装the ibm_db_sa模块,执行以下命令:
sudo pip install ibm_db
sudo pip install ibm_db_sa
这将在您的机器上安装最新版本的 ibm_db_sa 和ibm_db模块。剩下要做的就是在 CLI 配置文件中配置该模块,这样就可以使用 Db2 作为 SQLAlchemy 应用的存储设备了。清单 8-26 显示了需要添加到 CLI 配置文件中的内容。
[pydb]
Database=pydev
Protocol=tcpip
Hostname=host.name.com
Servicename=50000
uid=db2inst1
pwd=secret
Listing 8-26CLI Configuration File Extract
一旦 CLI 配置文件有了正确的设置,就可以开始在程序中使用 SQLAlchemy 模块来创建 Python 程序对象的持久副本,并存储在 Db2 中。
要了解更多关于 SQLAlchemy 的信息,请访问 www.sqlalchemy.org/ 网站。
Alembic 适配器
Alembic 项目是 SQLAlchemy 项目的子项目。它是一个迁移工具,允许将存储在 SQLAlchemy 关系数据库中的对象迁移到另一个关系数据库中。无论使用不同于项目当前使用的关系数据库的原因是什么,Alembic 将允许您将所有数据迁移到新的关系数据库,也就是说,将 SQLite SQLAlchemy 数据库迁移到 Db2。
虽然这个工具在大多数环境中并不是日常使用的,但是在您需要的时候它是可用的。
要安装the ibm_db_alembic模块,使用以下命令:
sudo pip install ibm_db
sudo pip install ibm_db_alembic
这将在您的机器上安装最新版本的ibm_db_alembic和ibm_db模块。
要了解更多关于 Alembic 的信息,请访问 https://alembic.sqlalchemy.org/ 网站。
未来
ibm_db模块及其相关的子项目在允许 Python 访问 Db2 环境方面有了一个很好的开端。增加这个覆盖率有很多可能性,但是在写这本书的时候,有一件事阻碍了新项目的发展——缺乏开发人员。这也是阻碍许多开源项目发挥其全部潜能的问题。然而,ibm_db项目有一个小优势,那就是有一小部分来自 IBM 的开发人员正在努力推进这个项目。但是他们需要你的帮助和支持。所以,如果你有时间和兴趣,请自愿参加这个项目。
摘要
在本章中,我们已经介绍了ibm_db包和 API。我们已经展示了所有的数据操作 SQL 语句以及许多杂项 API。我们还介绍了如何使用参数标记,以及将 Python 数据绑定到使用它的语句的不同方式。
希望本章包含的信息和示例能够让您牢牢掌握如何使用和操作 Db2 数据库。您还应该能够更好地利用附录信息。


浙公网安备 33010602011771号