这是本节的多页打印视图。
点击此处打印.
返回本页常规视图.
客户端库
Vertica 客户端驱动程序库提供用于将客户端应用程序(或诸如 Cognos 和 MicroStrategy 等第三方应用程序)连接到 Vertica 数据库的接口。这些驱动程序简化了执行加载、报告生成及其他常见数据库任务时的数据交换操作。
以下是三种不同的客户端驱动程序:
-
开放式数据库连接 (Open Database Connectivity, ODBC) — 最常用的接口,适用于采用 C、Python、PHP、Perl 和大多数其他语言编写的第三方应用程序和客户端。
-
Java 数据库连接 (Java Database Connectivity, JDBC) — 由采用 Java 编程语言编写的客户端使用。
-
适用于 .NET 的 ActiveX 数据对象 (ActiveX Data Objects for .NET, ADO.NET) — 由采用 Microsoft .NET Framework 开发并采用 C#、Visual Basic .NET 和其他 .NET 语言编写的客户端使用。
客户端驱动程序标准
Vertica 客户端驱动程序与以下驱动程序标准兼容:
-
ODBC 驱动程序符合 3.5.1 版本的 ODBC 标准。
-
Vertica 的 JDBC 驱动程序是第 4 类驱动程序,符合 JDBC 3.0 标准。此驱动程序是使用 JDK 版本 1.5 编译的,并与使用 JDK 版本 1.5 和 1.6 编译的客户端应用程序兼容。
-
ADO.NET 驱动程序符合 .NET framework 3.0 规范。
驱动程序不支持这些标准中的部分可选功能。有关详细信息,请参阅 ODBC 功能支持 和 JDBC 功能支持 以及使用 ADO.NET。
1 - 客户端驱动程序和服务器版本兼容性
Vertica 服务器与客户端驱动程序之间的向后兼容性可双向工作;Vertica 服务器与所有先前版本的客户端驱动程序兼容,所有新的客户端驱动程序都与大多数版本的 Vertica 服务器兼容。这种兼容性使您无需立即升级客户端软件即可升级 Vertica 服务器,并且可以将新客户端软件与旧版本的 Vertica 一起使用。但有时,新服务器版本中的个别功能可能在旧版驱动程序中不可用。
2 - 客户端驱动程序
必须安装 Vertica 客户端驱动程序才能从客户端应用程序访问 Vertica。驱动程序将创建并维护与数据库的连接,而且为应用程序提供用于访问数据的 API。客户端驱动程序支持使用 JDBC、ODBC 和 ADO.NET 的连接。
客户端驱动程序标准
客户端驱动程序支持以下标准:
2.1 - 安装和配置客户端驱动程序
通过安装适当的客户端驱动程序,您可以使用各种编程语言和工具访问 Vertica 数据库。下表列出了每种访问方法所需的客户端驱动程序:
2.1.1 - Windows 客户端驱动程序安装程序
Vertica 客户端驱动程序和工具安装程序中包含所有可用的 Windows 客户端驱动程序。这会在满足先决条件的系统上安装以下组件。个别组件在使用前可能需要额外配置,因此请导航至下方链接的页面以获取更多信息:
2.1.1.1 - 系统先决条件
适用于 Windows 的 Vertica 客户端驱动程序和工具要求系统满足基本的先决条件。该包还要求安装特定的 Microsoft 组件以实现完全集成。
有关所有先决条件的列表,请参阅支持的平台文档中的客户端驱动程序支持。
完全更新系统
在安装 Vertica 驱动程序包之前,请确认系统已使用所有 Windows 更新和修补程序进行了完全更新。有关如何运行 Windows 更新的说明,请参阅您的 Windows 版本的文档。Vertica 客户端库和 vsql 可执行文件安装依赖于 Windows Service Pack 的更新的 Windows 库。确保解决阻止安装 Windows 更新的任何问题。
如果您的系统不是完全最新,您可能会在启动 vsql 时收到有关缺少库(例如 api-ms-win-crt-runtime-l1-1-0.dll
)的错误消息。
2.1.1.1.1 - .NET Framework
.NET Framework 未捆绑到适用于 Windows 的 Vertica 客户端驱动程序和工具中。但是,在安装期间,如果在系统上检测不到 Microsoft .NET 3.5 SP1,则 Web 安装程序会启动。然后,您可以下载该框架。此外,如果操作系统版本包含 .NET 3.5 SP1,但未打开此功能,则安装程序会打开此功能。
如果已安装 Visual Studio 2010 或 2012,则您的系统已包含 Microsoft .NET Framework 4.0 或 4.5。您还需要安装 Microsoft .NET 3.5 SP1,才能使用 Vertica 客户端驱动程序和工具来实现 Windows 集成功能。
可以使用以下链接直接从 Microsoft 下载相应的 .NET Framework 版本:
2.1.1.1.2 - Microsoft visual studio
适用于 Windows 安装程序的 Vertica 客户端驱动程序和工具提供一个 Visual Studio 插件,通过该插件,您可以使用 Vertica 作为 Visual Studio 2008、Visual Studio 2010、Visual Studio 2012、Visual Studio 2013 或 Visual Studio 2015 的 Visual Studio 数据源。该插件的连接属性与 ADO.NET 连接属性 相同。
重要
重要说明: 您必须已安装 Visual Studio 和匹配的 SDK 才能使用 Visual Studio 插件。
安装该插件之后,您可以使用该插件从 Visual Studio 中访问 Vertica 数据库。如果尚未安装 SDK,请下载特定于您的 Visual Studio 版本的 SDK。
注意
对于 Visual Studio 2015,您无需单独下载 SDK,因为它作为安装选项包含在 Visual Studio 安装中。有关详细信息,请参考
Microsoft 文档。
如果开始安装时缺少 Microsoft Visual Studio SDK,将打开一个对话框进行安装。您可以选择忽略此对话框。
配置 BIDS 集成或 SSDT-BI 集成
适用于 Windows 安装程序的 Vertica 客户端驱动程序和工具提供 BIDS (Visual Studio 2008) 集成或 SSDT-BI(Visual Studio 2010、Visual Studio 2012、Visual Studio 2013 或 Visual Studio 2015)集成。若要使用 BIDS 或 SSDT-BI,请遵循以下过程:
-
安装适用于 Visual Studio 的 BIDS 或 SSDT-BI 开发工具加载项。
-
验证 SQL Server 是安装在同一台还是不同计算机上。
-
验证是否已激活适用于 IDS 或 SSDT-BI 的 SQL Server 共享功能。
然后,您可以使用 BIDS 或 SSDT-BI 开发程序包,还可以使用 SQL Server 的 SSIS、SSAS 和 SSRS 功能创建项目。若要使用这些功能,您必须通过 Vertica ADO.NET 驱动程序(适用于 SSIS 和 SSRS)或 OLE DB 驱动程序(适用于 SSAS)连接到 Vertica。
有关详细信息,请参阅Microsoft 组件。
2.1.1.1.3 - Microsoft SQL server
使用 SQL Server 2012、SQL Server 2014 或 SQL Server 2016。适用于 Windows 安装程序的 Vertica 客户端驱动程序和工具支持以下功能:
-
SQL Server 2012、SQL Server 2014 和 SQL Server 2016:
-
SQL Server Integration Services (SSIS)
-
SQL Server Reporting Services (SSRS)
-
SQL Server Analysis Services (SSAS)
-
使用 Visual Studio 2010、Visual Studio 2012、Visual Studio 2013 和 Visual Studio 2015 的 SQL Server — SQL Server Data Tools - Business Intelligence (SSDT-BI)
注意
对于 SQL Server 2012,您可以使用 SQL Server 2012 或 SQL Server 2012 SP1。
若要使用增强的 Vertica .NET 支持,您必须先安装 SQL Server。然后,您可以安装适用于 Windows 的客户端驱动程序和工具。必须在 SQL Server 上安装以下组件:
2.1.1.2 - 适用于 Windows 的 Visual Studio 插件
适用于 Windows 的 Visual Studio 插件作为适用于 Windows 的客户端驱动程序和工具的一部分安装。
有关 Visual Studio 插件如何与先前已安装在系统上的 Microsoft 组件集成的信息,请参阅 Microsoft 组件。
2.1.1.2.1 - Visual Studio 限制
Visual Studio 2012 可能需要 Update 3
在下列情况下,您可能需要安装用于 Visual Studio 2012 的 Update 3:
对于其他受 Vertica 支持版本的 Visual Studio,此问题不会出现。
结果查看器限制为 655 列
Visual Studio 结果查看器无法执行包含超过 655 列的查询。如果表包含超过 655 列,请选择特定列(总数最多为 655),而不应选择所有列。
手动刷新 Visual Studio 的设置
安装 Visual Studio 插件后,如果 Vertica 未作为数据提供程序列出,请手动刷新。
若要执行此操作,请运行 devenv.exe/setup
,您可以在 Visual Studio 的安装文件夹中找到该文件。
SQL 窗格问题
-
ALTER TABLE 或 CREATE TABLE
您使用 Visual Studio 2008、Visual Studio 2010、Visual Studio 2012、Visual Studio 2013 或 Visual Studio 2015,并在 SQL 窗格中发出 ALTER TABLE 或 CREATE TABLE 语句。但显示一条消息,指明该语句不受支持。若要解决该错误,请单击继续,查询将会执行。
-
带有分号的查询
您使用 Visual Studio 2008、Visual Studio 2010、Visual Studio 2012、Visual Studio 2013 或 Visual Studio 2015,并在 SQL 窗格中执行 SQL 查询。如果在查询中包含分号 (;),该查询将会执行,但您无法编辑返回的结果。若要避免此问题,请在 SQL 窗格中输入不带分号的相同查询。
-
引用布尔值
您使用 Visual Studio 2008、Visual Studio 2010、Visual Studio 2012、Visual Studio 2013 或 Visual Studio 2015 连接到 Vertica 数据库,并在 SQL 窗格中执行 SQL 查询。当尝试将值插入布尔列而不在值两端加上引号时,SQL 语句的后续执行将返回错误。要解决此问题,请加上引号。
卸载 适用于 Windows 的客户端驱动程序和工具错误
在某些情况下,Windows 软件包的客户端驱动程序和工具的卸载失败并显示需要 .NET Framework的消息。以下是导致此问题的场景。
- 安装 Windows 的客户端驱动程序和工具
- 然后安装 Visual Studio 2010 或 2012,其中包括安装 .NET Framework 4.0 或 4.5。
- 使用 Windows 控制面板卸载 .NET Framework。
- 然后,您尝试卸载 Windows 的客户端驱动程序和工具。卸载将会失败并显示需要 .NET Framework的消息。
要更正此问题,请执行以下操作:
- 使用 Windows 控制面板手动重新安装 .NET Framework 4.0 或 4.5。
- 卸载 Windows 的客户端驱动程序和工具
2.1.1.3 - 卸载、修改或修复客户端驱动程序和工具
要卸载、修改或修复客户端驱动程序和工具,请运行适用于 Windows 的客户端驱动程序和工具安装程序。
该安装程序提供以下三个选项:
在静默模式下卸载客户端驱动程序和工具
-
以 Windows 管理员身份,打开命令行会话,然后将目录更改为包含安装程序的文件夹。
-
运行命令:
VerticaSetup.exe -q -uninstall
将在静默模式下卸载客户端驱动程序和工具。
2.1.2 - FIPS 客户端驱动程序
Vertica 提供符合 FIPS 的版本的 ODBC 和 JDBC 客户端驱动程序。
2.1.2.1 - 为 JDBC 安装 FIPS 客户端驱动程序
Vertica 提供符合联邦信息处理标准 (FIPS) 的 JDBC 客户端驱动程序。使用此 JDBC 客户端驱动程序访问与 FIPS 兼容的系统。有关 FIPS 的详细信息,请参阅美国联邦信息处理标准。
在 JDBC 客户端上实施 FIPS 需要一个名为 BouncyCastle 的第三方 JRE 扩展,它是一组用于加密的 API。将 BouncyCastle API 与 JDK 1.7 和 1.8 以及支持的符合 FIPS 的操作系统一起使用。
重要
使用符合 FIPS 的 JDBC 客户端时,客户端与数据库建立安全连接预计会稍有延迟。如有必要,增加系统的熵以确保快速安全的连接。
以下过程将 FIPS BouncyCastle .jar
添加为 JVM JSSE 提供程序:
-
下载 BouncyCastle FIPS .jar
文件 bc-fips-1.0.0.jar
。
-
将 bc-fips-1.0.0.jar
添加为 JRE 库扩展:
path/to/jre/lib/ext/bc-fips-1.0.0.jar
-
在 <path to jre>/lib/security/java.security
中将 BouncyCastle 添加为 SSL 安全提供程序:
security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastle FipsProvider
security.provider.2=com.sun.net.ssl.internal.ssl.Provider BCFIPS
security.provider.3=sun.security.provider.Sun
-
使用以下 JVM java -D 系统属性命令实参将 KeyStore 和 TrustStore 文件设置为 BCFIPS:
export JAVA_OPTS="$JAVA_OPTS -Djavax.net.ssl.keyStoreProvider=BCFIPS
export JAVA_OPTS="$JAVA_OPTS -Djavax.net.ssl.trustStoreProvider=BCFIPS
-
在 path/to/jre/lib/security/java.security 中将 KeyStore 实施的默认类型设置为 BCFKS:
keystore type=BCFKS
ssl.keystore.type=BCFKS
注意
如果您将 FIPS 与 BouncyCastle 一起使用,则必须创建具有 BCFKS 存储类型的所有客户端密钥和证书(包括 Vertica 到 Kafka 连接的密钥和证书)。
-
创建 BCFKS 类型的密钥库和信任库:
cd path/to/jre
-storetype BCFKS
-providername BCFIPS
-providerclass org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
-provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
-providerpath bc-fips-1.0.0.jar
-alias CARoot
-import -file path/to/server.crt.der
-
出现提示时,输入密钥库密码。系统将显示以下消息以确认已将证书添加到密钥库:
"Certificate was added to the keystore"
-
使用 SSL DB 运行 Java 程序:
-
将 vertica.kafka.keystore.bcfks 密钥库从 path/to/jre/lib/ext/
复制到 Java 程序文件夹。
-
将 Vertica 服务器证书转换为能够由 Java 理解的形式:
$ path/to/java/bin/keytool -keystore verticastore -keypasswd -storepass password
-importkeystore -noprompt -alias verticasql -import -file server.crt.der
-
安装 JDBC。
-
对实施进行测试:
$ java -Djavax.net.debug=ssl -Djavax.net.ssl.keyStore='vertica.kafka.keystore.bcfks'
-Djavax.net.ssl.keyStorePassword='password'
-Djavax.net.ssl.trustStore='path/to/verticastore'
-Djavax.net.ssl.trustStorePassword='password'
-cp .:vertica-jdbc-12.0.0-0.jar FIPSTest
2.1.2.2 - 为 ODBC 和 vsql 安装 FIPS 客户端驱动程序
Vertica 为与 FIPS 兼容的系统提供 FIPS 客户端。与 FIPS 兼容的系统启用了 FIPS,其中包含 OpenSSL 库。
FIPS 客户端支持 ODBC 和 vsql,仅提供 64 位版本。
先决条件
确认主机系统正在运行受 Vertica 支持且符合 FIPS 的操作系统。
FIPS 客户端安装程序会检查您的主机系统以获取 sysctl 参数 crypto.fips_enabled 的值。您必须将此参数设置为 1(启用)。如果未启用您的主机,则客户端不会安装。
安装 FIPS 客户端
要安装 FIPS 客户端驱动程序包:
-
从 Vertica 驱动程序下载页面 下载 FIPS 客户端包。
-
以 root 身份登录客户端系统。
-
安装已下载的 RPM 包:
# rpm -Uvh package_name.rpm
对于 ODBC,在您安装了客户端包之后,创建 DSN 并设置一些额外的配置参数。有关详细信息,请参阅:
您可以选择将 vsql 客户端添加到 PATH 环境变量,以便不需要输入完整路径即可运行该客户端。为此,将下行添加到主目录中的 .profile
文件或全局 /etc/profile
文件:
export PATH=$PATH:/opt/vertica/bin
客户端如何搜索 OpenSSL 库
当您启动客户端应用程序以连接到服务器时,客户端会为支持的 OpenSSL 版本搜索并加载 OpenSSL 库 libcrypto.so.10
和 libssl.so.10
:
重要
LD_LIBRARY_PATH
(如果已设置)指示 OpenSSL 库的搜索路径。客户端从所设置或预设的任何 LD_LIBRARY_PATH
位置加载库。
下图描述了 OpenSSL 库的搜索过程:
2.1.3 - JDBC 客户端驱动程序
Vertica JDBC 客户端驱动程序符合 JDK 5 规范,它提供了一个接口,通过该接口可以使用 Java 与 Vertica 数据库进行通信。有关此 API 和其他 API 的详细信息,请参阅 API 参考。
要安装 JDBC 客户端驱动程序,请参阅安装 JDBC 客户端驱动程序。
2.1.3.1 - 安装 JDBC 客户端驱动程序
JDBC 客户端驱动程序符合 JDK 5 规范。根据您的环境和要求下载 JDBC 客户端驱动程序。如果您需要使用符合 FIPS 的驱动程序,请参阅为 JDBC 安装 FIPS 客户端驱动程序。
从 RPM 安装 Vertica 会自动安装 JDBC 客户端驱动程序。要使用 JDBC 客户端驱动程序,您只需将 Vertica JDBC .jar
添加到您的 CLASSPATH。
要手动安装 JDBC 客户端驱动程序:
-
从客户端驱动程序下载页面,下载与您的 Vertica 版本兼容的 JDBC 客户端驱动程序版本。
-
在希望从中访问 Vertica 的每个客户端系统上将 .jar
文件复制到 Java CLASSPATH 中的目录。您可以执行以下操作之一:
-
将 .jar
文件复制到其自己的目录(例如
/opt/vertica/java/lib
),然后将该目录添加到 CLASSPATH(建议采用此方法)。有关详细信息,请参阅修改 Java CLASSPATH。
-
将 .jar
文件复制到已存在于 CLASSPATH 中的目录(例如,已将应用程序所依赖的其他 .jar
文件放置到的目录)。
-
将 .jar
文件复制到系统范围的 Java 扩展目录。确切的位置因操作系统不同而异。一些示例包括:
-
创建连接以测试您的配置。
2.1.3.2 - 修改 Java CLASSPATH
CLASSPATH 环境变量包含目录的列表,Java 运行时将从该列表的目录查找库类文件。要让 Java 客户端代码访问 Vertica,您必须将包含 Vertica JDBC .jar
的目录添加到 CLASSPATH。
将符号链接用于 CLASSPATH
您可以选择将符号链接 vertica-jdbc-x.x.x.jar
(其中 x.x.x 是版本号,此符号链接指向 JDBC 库 .jar
文件而非 .jar
文件本身)添加到 CLASSPATH。
使用符号链接可确保对 JDBC 库 .jar
文件(此文件使用其他文件名)进行的任何更新不会使 CLASSPATH 设置失效,因为符号链接的文件名将保持不变。您只需要更新符号链接,使其指向新的 .jar
文件。
Linux 和 OS X
以下示例使用符合 POSIX 的 shell。
要为当前会话设置 CLASSPATH:
$ export CLASSPATH=$CLASSPATH:/opt/vertica/java/lib/vertica-jdbc-x.x.x.jar
要为每个会话设置 CLASSPATH,请将以下内容添加到启动文件(例如 ~/.profile
或 /etc/profile
):
$ export CLASSPATH=$CLASSPATH:/opt/vertica/java/lib/vertica-jdbc-x.x.x.jar
Windows
提供指向 .jar
、.zip
或 .class
文件的类路径。
C:> SET CLASSPATH=classpath1;classpath2...
例如:
C:> SET CLASSPATH=C:\java\MyClasses\vertica-jdbc-x.x.x.jar
与 Linux/UNIX 设置一样,此设置仅持续到当前会话关闭。若要永久设置 CLASSPATH,请设置环境变量:
-
在 Windows 控制面板中,单击系统。
-
单击高级 或高级系统设置。
-
单击环境变量。
-
在“用户变量”下,单击新建。
-
在“变量名”框中,键入 CLASSPATH
。
-
在“变量值 (Variable value)”框中,键入指向系统上的 Vertica JDBC .jar
文件的路径(例如,
C:\Program Files (x86)\Vertica\JDBC\vertica-jdbc-x.x.x.jar
)
在 Java 命令中指定库目录
另一种与操作系统无关的、告知 Java 运行时在何处可以找到 Vertica JDBC 驱动程序的方法是:使用 -cp
或 -classpath
实参将包含 .jar
文件的目录显式添加到 Java 命令行。例如,可以使用以下命令启动客户端应用程序:
java -classpath /opt/vertica/java/lib/vertica-jdbc-x.x.x.jar myapplication.class
Java IDE 还可让您将目录添加到 CLASSPATH,或者可让您将 Vertica JDBC 驱动程序导入到项目中。有关详细信息,请参阅 IDE 文档。
2.1.4 - ODBC 客户端驱动程序
Vertica ODBC 客户端驱动程序提供了一个接口,通过该接口可以使用多种语言创建客户端应用程序:
要安装 ODBC,请参阅安装 ODBC 客户端驱动程序。
2.1.4.1 - 安装 ODBC 客户端驱动程序
要安装 ODBC,请按照您的平台说明进行操作。有关受支持平台的列表,请参阅客户端驱动程序支持。
本页介绍非 FIPS 安装。要在符合 FIPS 的系统上安装 ODBC,请参阅为 ODBC 和 vsql 安装 FIPS 客户端驱动程序。
在 Linux 上安装
从 RPM 安装 Vertica 会自动安装 ODBC 客户端驱动程序,因此您无需在运行 Vertica 的计算机上再次安装它们。要在这种情况下使用 ODBC 客户端驱动程序,请创建 DSN。
要在其他计算机上手动安装 ODBC 客户端驱动程序:
-
以 root 身份登录客户端系统。
-
验证您的系统是否装有受支持的 ODBC 驱动程序管理器。
-
以适合您的发行版的格式,下载适用于 Linux 的 ODBC 客户端驱动程序。
-
安装或解压缩驱动程序:
-
如果您下载了 .rpm
,请安装该驱动程序:
注意
如果客户端驱动程序已安装在您的系统上(通过手动安装或通过 Vertica RPM 自动安装),则在您尝试手动重新安装,会收到错误消息。要跳过这些错误并覆盖现有的驱动程序安装,请使用 --force
标志。
$ rpm -Uvh driver_name.rpm
-
如果您下载了 .tar
,则创建 /opt/vertica/
目录(如果该目录不存在),将 .tar
复制到其中,导航到它,然后解压缩 .tar
:
$ mkdir -p /opt/vertica/
$ cp driver_name.tar.gz /opt/vertica/
$ tar vzxf driver_name.tar.gz
这将创建两个目录:
-
在 vertica.ini
中设置以下 ODBC 驱动程序设置。有关每个设置的详细信息,请参阅 ODBC 驱动程序设置:
-
ErrorMessagesPath
:必需,包含 ODBC 驱动程序的错误消息文件的目录路径。
-
ODBCInstLib
:ODBC 安装程序库的路径。仅当驱动管理器的安装库不在环境变量 LD_LIBRARY_PATH
或 LIB_PATH
中时才需要。
-
DriverManagerEncoding
:驱动程序管理器使用的 UTF 编码标准。仅当您的驱动程序管理器不使用 UTF-8 时才需要。
下面是 vertica.ini
中的示例配置:
-
使用 64 位 UNIXODBC 驱动程序管理器的编码。
-
使用标准 Vertica 64 位 ODBC 驱动程序安装目录中定义的错误消息。
-
将所有警告和严重性更高的消息记录到日志文件 /tmp/
[Driver]
DriverManagerEncoding=UTF-16
ODBCInstLib=/usr/lib64/libodbcinst.so
ErrorMessagesPath=/opt/vertica
LogLevel=4
LogPath=/tmp
-
创建 DSN。
在 macOS 上安装
注意
在 macOS 系统上,每个版本的 ODBC 驱动程序只能安装一个。这是因为每个安装都由包 ID 和版本号标识,并且包 ID 在驱动程序的版本之间不会更改。
要在 macOS 上安装 ODBC 客户端驱动程序:
-
验证您的系统是否装有兼容的驱动程序管理器。该驱动程序旨在与 macOS 附带的标准 iODBC 驱动程序管理器一起使用。您也可以使用 unixODBC。
-
下载 ODBC 客户端驱动程序。
-
如果已安装以前版本的 ODBC 驱动程序,则系统可能已注册了名为“Vertica”的驱动程序。在从 .pkg
安装程序安装新版本之前,您必须移除或重命名旧版本的驱动程序。重命名旧版本可让您在安装新版本后保留旧版本。
-
运行安装程序。
-
创建 DSN。
静默安装
-
通过以下两种方式之一登录客户端 macOS:
-
打开终端。
-
使用以下命令安装包含 ODBC 驱动程序的 .pkg
文件:
sudo installer -pkg path/to/client/driver/vertica-odbc-xx.x.x-x.pkg -target /
在 Windows 上安装
要在 Windows 上安装 ODBC 客户端驱动程序:
-
下载适用于 Windows 的客户端驱动程序安装程序。
-
运行安装程序。
-
创建 DSN。
静默安装
-
以管理员身份打开终端。
-
运行以下命令将驱动程序静默安装到 C:\Program Files\Vertica Systems
:
VerticaSetup.exe -q -install InstallFolder="C:\Program Files\Vertica Systems"
2.1.4.2 - 升级和降级 ODBC
Linux
要升级 ODBC:
-
卸载当前版本的驱动程序。
-
安装新版本的驱动程序。
macOS
要升级或降级 ODBC:
Windows
-
下载 Windows 客户端驱动安装程序。
-
运行安装程序并按照提示升级驱动程序。安装程序会升级现有的驱动程序。
-
重新启动系统。
2.1.4.3 - 卸载 ODBC
Linux
如果您使用 .rpm
安装了 ODBC:
$ rpm -e package_name
如果您使用 .tar
安装了 ODBC,请手动删除该目录。
macOS
卸载 macOS ODBC 客户端驱动程序不会移除与该驱动程序关联的任何现有 DSN。
若要卸载,请执行下列操作:
-
打开终端窗口。
-
运行命令:
sudo /Library/Vertica/ODBC/bin/Uninstall
Windows
-
打开添加或删除程序 (Add or Remove Programs) 菜单。
-
或者卸载 Vertica Client Installer 以从系统中移除所有客户端驱动程序,或者仅卸载 ODBC 以卸载以下应用程序:
-
Vertica ODBC 驱动程序(32 位)
-
Vertica ODBC 驱动程序(64 位)
2.1.4.4 - 创建 ODBC 数据源名称 (DSN)
数据源名称 (DSN) 是由开放式数据库连接 (ODBC) 使用的逻辑名称,用于引用从数据源访问数据而所需的驱动程序和其他信息。无论您是开发自己的 ODBC 客户端代码,还是使用需要通过 ODBC 访问 Vertica 的第三方工具,都需要配置和测试 DSN。您使用的方法取决于所用的客户端操作系统。
有关特定于您的客户端操作系统的信息,请参阅以下部分。
2.1.4.4.1 - 为 Linux 创建 ODBC DSN
在 Linux 和其他类 UNIX 平台上,您可以在文本文件中定义 DSN。客户端的驱动程序管理器会读取该文件,以确定如何连接到 Vertica 数据库。驱动程序管理器通常在以下两个位置查找 DSN 定义:
用户必须能够读取 odbc.ini
文件,才能使用它连接到数据库。如果使用全局 odbc.ini
文件,请考虑创建一个对该文件具有读取权限的 UNIX 组。然后,将需要使用 DSN 的用户添加到该组。
这些文件的结构是相同的,只是所在位置不同而已。如果这两个文件都存在,则 ~/.odbc.ini
文件通常会替代系统范围 /etc/odbc.ini
文件。
注意
有关应将这两个文件存储到的位置和任何其他要求,请参阅 ODBC 驱动程序管理器的文档。
odbc.ini 文件结构
odbc.ini
是一个包含以下两种类型的行的文本文件:
当心
unixODBC 驱动程序管理器支持
odbc.ini
中最多 1000 个字符的参数值。如果您的参数值大于 1000 个字符(例如 OAuthAccessToken),则必须通过
连接字符串 传递它,而不是在
odbc.ini
中指定它。
文件的第一节始终名为 [ODBC Data Sources],其中包含 odbc.ini
文件所定义的所有 DSN 的列表。此节中的参数是 DSN 的名称,这些参数稍后在文件中显示为节定义。值是 DSN 的文本描述,并不起任何作用。例如,定义了名为 Vertica DSN 的单个 DSN 的 odbc.ini
文件可能包含以下 ODBC 数据源节:
[ODBC Data Sources]
VerticaDSN = "vmartdb"
显示在 ODBC 数据源节之后的节定义了每个 DSN。DSN 节的名称必须与 ODBC 数据源节中定义的名称之一匹配。
配置 odbc.ini 文件:
若要创建或编辑 DSN 定义文件,请执行下列操作:
-
使用所选文本编辑器打开 odbc.ini
或 ~/.odbc.ini
。
-
创建 ODBC 数据源节并定义参数:
-
其名称是要创建的 DSN 的名称
-
其值是 DSN 的描述
例如,若要创建名为 VMart 的 DSN,您应输入以下命令:
[ODBC Data Sources]
VMart = "VMart database on Vertica"
-
创建名称与您在步骤 2 中定义的 DSN 名称匹配的节。在此节中,您应添加定义了 DSN 的设置的参数。最常定义的参数如下:
-
Description – 有关数据源的附加信息。
-
Driver – Vertica ODBC 驱动程序的位置和指定,或 odbcinst.ini
文件中定义的驱动程序的名称(请参阅下文)。为了确保将来的兼容性,请使用库目录(而非库文件)中的符号链接的名称:
例如,64 位 ODBC 驱动程序库的符号链接是:
/opt/vertica/lib64/libverticaodbc.so
符号链接始终指向最新版本的 Vertica 客户端 ODBC 库。请使用此链接,以便不需要在更新客户端驱动程序后更新所有 DSN。
-
Database – 在服务器中运行的数据库的名称。此示例使用 vmartdb 来表示 vmartdb。
-
ServerName — 安装了 Vertica 的服务器的名称。如果 Vertica 已安装在同一台计算机上,则使用 localhost。
您可以提供 IPv4 地址、IPv6 地址或主机名。
在 IPv4/IPv6 混合网络中,DNS 服务器配置决定了哪个 IP 版本地址最先发送。可使用 PreferredAddressFamily
选项来强制连接使用 IPv4 或 IPv6。
-
UID — 数据库超级用户(名称与数据库管理员帐户相同)或超级用户已创建并向其授予了权限的用户。此示例使用用户名 dbadmin。
-
PWD — 指定的用户名的密码。此示例将密码字段留空。
-
Port — Vertica 用来侦听 ODBC 连接的端口号。例如,5433。
-
ConnSettings — 可以包含用分号分隔的 SQL 命令。这些命令可以在连接到服务器之后立即运行。
-
SSLKeyFile — 客户端私钥的文件路径和名称。此文件可以驻留在系统上的任意位置。
-
SSLCertFile — 客户端公用证书的文件路径和名称。此文件可以驻留在系统上的任意位置。
-
Locale — 用于会话的默认区域设置。默认情况下,数据库的区域设置是:en_US@collation=binary (English as in the United States of America)。将区域设置指定为 ICU 区域设置。有关可用于指定区域设置的完整参数列表,请参阅 ICU 用户指南 (http://userguide.icu-project.org/locale)。
-
PreferredAddressFamily:
如果客户端和服务器都有 IPv4 和 IPv6 地址而且您已经提供了主机名时要使用的 IP 版本,则使用的 IP 版本是以下之一:
例如:
[VMart]
Description = Vmart Database
Driver = /opt/vertica/lib64/libverticaodbc.so
Database = vmartdb
Servername = host01
UID = dbadmin
PWD =
Port = 5433
ConnSettings =
AutoCommit = 0
SSLKeyFile = /home/dbadmin/client.key
SSLCertFile = /home/dbadmin/client.crt
Locale = en_US@collation=binary
有关包括 Vertica 特定参数的完整参数列表,请参阅 ODBC DSN 连接属性。
使用 odbcinst.ini 文件
您可以使用在 odbcinst.ini
文件中定义的驱动程序的名称,而不必在 DSN 定义中指定 ODBC 驱动程序库的路径。如果您有许多 DSN 并且经常需要更新这些 DSN 以指向新的驱动程序库,则此方法很有用。使用此方法,您还可以设置一些其他 ODBC 参数,例如线程模型。
与 odbc.ini
文件一样,odbcinst.ini
文件中也包含一些节。每个节定义了可在 odbc.ini
文件中引用的 ODBC 驱动程序。
在一个节中,您可以定义以下参数:
例如:
[Vertica]
Description = Vertica ODBC Driver
Driver = /opt/vertica/lib64/libverticaodbc.so
然后,在 odbc.ini
文件中,您可以使用已在 odbcinst.ini
文件中创建的节(描述了要使用的驱动程序)的名称。例如:
[VMart]
Description = Vertica Vmart database
Driver = Vertica
如果使用的是 unixODBC 驱动程序管理器,您还应添加一个 ODBC 节以替代其标准线程设置。默认情况下,unixODBC 通过 ODBC 将所有 SQL 调用序列化,这样可以阻止多个并行加载。若要更改其默认行为,请将以下行添加到 odbcinst.ini
文件中:
[ODBC]
Threading = 1
配置其他 ODBC 设置
在 Linux 和 UNIX 系统上,您需要先配置一些其他驱动程序设置,然后才能使用 DSN。有关详细信息,请参阅ODBC 驱动程序设置。
2.1.4.4.1.1 - 使用 isql 测试 ODBC DSN
unixODBC 驱动程序管理器包含一个名为 isql 的实用程序,此实用程序是一个简单的 ODBC 命令行客户端。使用此实用程序,您可以连接到 DSN 以发送命令和接收结果,类似于 vsql。
若要使用 isql 测试 DSN 连接,请执行下列操作:
-
运行以下命令:
$ isql –v DSNname
其中 DSNname 是已创建的 DSN 的名称。
此时将显示连接消息和 SQL 提示。如果它们未显示,则可能表明存在配置问题,或者可能表明所使用的用户名或密码不正确。
-
尝试执行简单 SQL 语句。例如:
SQL> SELECT table_name FROM tables;
isql 工具将返回 SQL 语句的结果。
注意
如果未在其他驱动程序配置设置中设置 ErrorMessagesPath,在测试期间发生的错误将导致错误消息文件缺失(“The error message NoSQLGetPrivateProfileString could not be found in the en-US locale”(错误消息 NoSQLGetPrivateProfileString 不可能出现在 en-US 区域设置中))。有关详细信息,请参阅
ODBC 驱动程序设置。
2.1.4.4.2 - 为 Windows 客户端创建 ODBC DSN
若要为 Microsoft Windows 客户端创建 DSN,您必须执行以下任务:
2.1.4.4.2.1 - 设置 ODBC DSN
数据源名称 (DSN) 是 ODBC 逻辑名称,用来表示驱动程序和访问数据源中的数据所需的其他信息。此名称由 Internet 信息服务 (Internet Information Services, IIS) 用于与 ODBC 数据源的连接。
此部分介绍如何使用 Vertica ODBC 驱动程序设置 ODBC DSN。本主题假设您已按照在 Windows 中安装客户端驱动程序中所述安装了驱动程序。
设置 DSN
-
打开 ODBC 管理器。例如,可以导航到“开始 > 控制面板 > 管理工具 (Administration Tools) > 数据源 (ODBC)”。
注意
用于打开 ODBC 管理器的方法取决于 Windows 版本。Windows 版本之间的差异和开始菜单 自定义可能要求执行不同的操作以打开 ODBC 管理器。
-
确定是否希望客户端系统上的所有用户都能访问 Vertica 数据库的 DSN。
-
单击添加 (Add) 以创建用于连接到 Vertica 数据库的新的 DSN。
-
滚动浏览“创建新数据源 (Create a New Data Source)”对话框中的驱动程序列表以查找 Vertica 驱动程序。选择该驱动程序,然后单击完成。
注意
如果在 Windows 客户端系统上安装了多个版本的 Vertica 客户端驱动程序,您可能会在此列表中看到该驱动程序的多个版本。选择您知道与客户端应用程序和 Vertica 分析数据库服务器兼容的版本。如果不确定,可以使用最新版本的驱动程序。
此时将显示 Vertica ODBC DSN 配置对话框。
-
单击更多 >>> (More >>>) 按钮,以查看正在编辑的字段和由 DSN 定义的连接字符串的描述。
-
输入 DSN 的信息。以下字段是必填的:
-
DSN 名称 — DSN 的名称。客户端使用此名称来标识要连接到的 DSN。DSN 名称必须满足以下要求:
-
服务器 — 要连接到的 Vertica 服务器的主机名或 IP 地址。如果 Vertica 已安装在同一台计算机上,则使用 localhost。
您可以提供 IPv4 地址、IPv6 地址或主机名。
在 IPv4/IPv6 混合网络中,DNS 服务器配置决定了哪个 IP 版本地址最先发送。可使用 PreferredAddressFamily
选项来强制连接使用 IPv4 或 IPv6。
PreferredAddressFamily
选项在“客户端设置”选项卡上可用。
-
备份服务器 — 用于在“服务器”字段中指定的服务器已关闭时连接到的主机名或 IP 地址的逗号分隔列表。可选。
-
数据库 — Vertica 数据库的名称。
-
用户名 — 在连接到数据库时使用的用户帐户的名称。在连接到 DSN 时,如果应用程序不提供自己的用户名,则会使用此帐户名称登录到数据库。
其余字段是可选的。有关可以定义的 DSN 参数的详细信息,请参阅 DSN 参数。
-
如果要测试连接,请执行下列操作:
-
至少输入有效的 DSN 名称、服务器名称、数据库,并输入用户名 或选择 Windows 身份验证。
-
如果未选择 Windows 身份验证,您可以在密码 框中输入密码。或者,您也可以选择密码提示,以让驱动程序在连接时提示您输入密码。
-
单击测试连接。
-
完成编辑和测试 DSN 后,单击确定。Vertica ODBC DSN 配置窗口将关闭,并且新的 DSN 会在“ODBC 数据源管理器”窗口中列出。
-
单击确定 (OK) 以关闭 ODBC 数据源管理器。
创建 DSN 后,您可以使用 Microsoft Excel 2007 进行测试。
在 64 位版本的 Microsoft Windows 上设置 32 位 DSN
在 64 位版本的 Windows 上,ODBC 数据源管理器在默认情况下会创建并编辑与 64 位 Vertica ODBC 库关联的 DSN。
尝试将这些 64 位 DSN 与 32 位客户端应用程序结合使用会生成架构不匹配错误。相反,您必须通过运行 32 位 ODBC 管理器为 32 位客户端创建特定的 32 位 DSN,该管理器通常位于以下位置:
c:\Windows\SysWOW64\odbcad32.exe
此管理器窗口可用于编辑与 32 位 ODBC 库关联的一组 DSN。使用此版本的 ODBC 管理器创建的 DSN 可与 32 位客户端应用程序结合使用。
2.1.4.4.2.2 - ODBC DSN 的密码加密
当您安装 ODBC 驱动程序并创建数据源名称 (DSN) 时,DSN 设置(其中包括)将存储在注册表中。ODBC DSN 的密码加密仅适用于 Windows 系统。
对 ODBC 数据源名称 (DSN) 的密码进行加密,可防止未经授权的数据库访问。密码默认不加密,以纯文本形式存储。
注意
在 Vertica ≤8.0.x 中创建的 ODBC DSN 密码升级到更高版本时不会加密,无论加密设置如何均是如此。
启用密码加密
使用 EncryptPassword 参数,可为 ODBC DSN 启用或禁用密码加密:
在 Windows 注册表中设置 EncryptPassword
- HKEY_LOCAL_MACHINE > Software > Vertica > ODBC > Driver EncryptPassword=<true/false>
。
注意
对于在 64 位 windows 上运行的 32 位驱动程序,请在此处验证密码加密:
HKEY_LOCAL_MACHINE > Software > Wow6432Node > Vertica > ODBC >
Driver > EncryptPassword=<true/false>
加密密码在以下注册表位置更新:
对于用户 DSN:
HKEY_CURRENT_USER-> Software -> ODBC -> ODBC.INI -> DSNNAME -> PWD
对于系统 DSN:
HKEY_LOCAL_MACHINE-> Software -> ODBC -> ODBC.INI -> DSNNAME -> PWD
验证密码加密
使用 Windows 注册表编辑器,根据 EncryptPassword 的值确定是否已启用密码加密。根据您安装的 DSN 类型,检查以下内容:
对于用户 DSN: HKEY_CURRENT_USER > Software > ODBC > ODBC.INI > dsn name > isPasswordEncrypted=<1/0>
对于系统 DSN: HKEY_LOCAL_MACHINE > Software > ODBC > ODBC.INI > dsn name > isPasswordEncrypted=<1/0>
对于每个 DSN,isPasswordEncrypted
参数值表示密码加密的状态。其中,1
表示密码已加密,0
表示密码未加密。
2.1.4.4.2.3 - 使用 Excel 测试 ODBC DSN
可以使用 Microsoft Excel 验证应用程序是否能够连接到 ODBC 数据源或其他 ODBC 应用程序。
-
打开 Microsoft Excel,然后选择数据 (Data) > 获取外部数据 (Get External Data) > 来自其他来源 (From Other Sources) > 来自 Microsoft Query (From Microsoft Query)。
-
当“选择数据源 (Choose Data Source)”对话框打开之后:
-
请选择新建数据源 (New Data Source),然后单击确定 (OK)。
-
输入数据源的名称。
-
选择 Vertica 驱动程序。
-
单击连接。
-
当“Vertica 连接对话框 (Vertica Connection Dialog)”打开之后,输入 DSN 的连接信息,然后单击确定 (OK)。
-
在“创建新数据源”对话框上单击确定,以返回到“选择数据源”对话框。
-
选择“VMart_Schema*”,并验证是否已取消选中“使用查询向导 (Use the Query Wizard)”复选框。单击确定 (OK)。
-
当“添加表”对话框打开后,单击关闭。
-
当“Microsoft 查询 (Microsoft Query)”窗口打开之后,单击 SQL 按钮。
-
在“SQL”窗口中,编写任意简单查询以测试连接。例如:
SELECT DISTINCT calendar_year FROM date_dimension;
-
*如果您看到提醒“SQL Query 无法以图形表示。是否继续?(SQL Query can't be represented graphically. Continue anyway?)”,请单击**确定 (OK)**。 *数据值 2003、2004、2005、2006、2007 表示已成功连接到 ODBC,并通过 ODBC 执行了查询。
-
选择文件 (File) > 将数据返回到 Microsoft Office Excel (Return Data to Microsoft Office Excel)。
-
在“导入数据”对话框中,单击确定。
数据当前在 Excel 工作表中可用。
2.1.4.4.3 - 为 macOS 客户端创建 ODBC DSN
可以使用 Vertica ODBC 驱动程序设置 ODBC DSN。此过程假设您已按照安装 ODBC 客户端驱动程序中所述安装了驱动程序。
设置 DSN
-
使用 Web 浏览器下载并安装 Apple ODBC Administrator Tool。
-
安装之后,找到并打开 ODBC 管理员工具:
-
导航至 Finder (查找器) > 应用程序 (Applications) > 实用程序 (Utilities)。
-
打开 ODBC 管理员工具。
-
单击驱动程序 (Drivers) 选项卡,然后验证是否已安装 Vertica 驱动程序。
-
指定是否希望客户端系统上的所有用户都能访问 Vertica 数据库的 DSN:
-
单击添加... (Add...) 创建用于连接到 Vertica 数据库的新的 DSN。
-
滚动浏览“选择驱动程序 (Choose A Driver)”对话框中的驱动程序列表以查找 Vertica 驱动程序。选择该驱动程序,然后单击确定 (OK)。此时将打开一个对话框,请求提供 DSN 参数信息。
-
在对话框中,输入数据源名称 (DSN) 和可选的 描述 (Description)。为此,请单击 添加 (Add) 以插入关键字(参数)和定义连接到数据库所需设置的值,其中包括数据库名称、服务器主机、数据库用户名(例如 dbaadmin)、数据库密码和端口。然后单击确定 (OK).
-
在“ODBC Administrator”对话框中,单击应用 (Apply)。
有关包括 Vertica 特定参数的完整参数列表,请参阅 ODBC DSN 连接属性。
配置 ODBC 管理员工具之后,您可能需要配置其他驱动程序设置才能使用 DSN,具体取决于环境。有关详细信息,请参阅其他 ODBC 驱动程序配置设置。
2.1.4.4.3.1 - 使用 iodbctest 测试 ODBC DSN
Mac OS X 上的标准 iODBC 驱动程序管理器包含一个名为 iodbctest 的实用程序,您可以使用此实用程序测试 DSN,以验证该 DSN 是否配置正确。应向此命令传递与用于打开 ODBC 数据库连接的连接字符串格式相同的连接字符串。配置 DSN 连接之后,您可以运行查询以验证该连接是否正常工作。
例如:
# iodbctest "DSN=VerticaDSN;UID=dbadmin;PWD=password"
iODBC Demonstration program
This program shows an interactive SQL processor
Driver Manager: 03.52.0607.1008
Driver: 07.01.0200 (verticaodbcw.so)
SQL> SELECT table_name FROM tables;
table_name
--------------------------------------------------------------------------------------------------------------------------------
customer_dimension
product_dimension
promotion_dimension
date_dimension
vendor_dimension
employee_dimension
shipping_dimension
warehouse_dimension
inventory_fact
store_dimension
store_sales_fact
store_orders_fact
online_page_dimension
call_center_dimension
online_sales_fact
numbers
result set 1 returned 16 rows.
2.1.4.4.4 - ODBC DSN 连接属性
下表列出了可以在 DSN 中设置以与 Vertica ODBC 驱动程序结合使用的连接属性。要设置这些参数,请参阅 设置 DSN 连接属性。
所需的连接属性
若要创建正常工作的 DSN,您至少需要设置这些连接属性。
注意
如果所使用的主机名 (Servername) 的 DNS 条目解析为多个 IP 地址,客户端将尝试连接到由 DNS 返回的第一个 IP 地址。如果无法与第一个地址,客户端将依次尝试连接到第二个和第三个地址,直至成功连接或用完地址为止。
可选属性
高级设置
标识
OAuth 连接属性
以下连接属性与 ODBC 中的 OAuth 相关。
当心
unixODBC 驱动程序管理器支持
odbc.ini
中最多 1000 个字符的参数值。如果您的参数值大于 1000 个字符(例如 OAuthAccessToken),则必须通过
连接字符串 传递它,而不是在
odbc.ini
中指定它。
加密
第三方兼容性
Kerberos 连接属性
可以将以下属性用于使用 Kerberos 的客户端身份验证。
另请参阅
ODBC 驱动程序设置
2.1.4.4.5 - 设置 DSN 连接属性
以下表中的属性是所有用户 DSN 条目和所有系统 DSN 条目共用的。所提供的示例适用于 Windows 客户端。
要编辑 DSN 属性:
-
在 UNIX 和 Linux 客户端平台上,您可以编辑 odbc.ini
文件。此文件的位置特定于驱动程序管理器。请参阅为 Linux 创建 ODBC DSN。
-
在 Windows 客户端平台上,您可以使用 Vertica ODBC 客户端驱动程序界面编辑部分 DSN 属性。请参阅为 Windows 客户端创建 ODBC DSN。
-
您也可以直接编辑 DSN 属性,方法是在 Windows 注册表中(例如在 HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBC.INI\\
DSNname 中)打开 DSN 条目。直接编辑注册表会带来风险,因此,只应对无法通过 ODBC 驱动程序用户界面或客户端代码进行设置的属性使用此方法。
-
当使用 SQLDriverConnect()
函数打开连接时,您可以在连接字符串中设置属性:
sqlRet = SQLDriverConnect(sql_hDBC, 0, (SQLCHAR*)"DSN=DSNName;Locale=en_GB@collation=binary", SQL_NTS, szDNS, 1024,&nSize, SQL_DRIVER_NOPROMPT);
注意
在连接字符串中,“;”是预留符号。如果需要设置多个属性作为 ConnSettings 属性的一部分,请使用“%3B”来替代“;”,并使用“+”来替代空格。
例如:
sqlRet = SQLDriverConnect(sql_hDBC, 0, (SQLCHAR*)"DSN=Vertica SQL;ConnSettings=set+search_path+to+a,b,c%3Bset+locale=ch;SSLMode=prefer", SQL_NTS,
szDNS, 1024,&nSize, SQL_DRIVER_NOPROMPT);
-
已使用 SQLGetConnectAttr()
和 SQLGetStmtAttr()
API 调用建立与 Vertica 的连接后,客户端代码可以检索 DSN 属性值。可以使用 SQLSetConnectAttr()
和 SQLSetStmtAttr()
设置部分属性。
有关特定于 Vertica 的属性的列表,请参阅特定于 Vertica 的 ODBC 头文件。
2.1.4.5 - ODBC 驱动程序设置
注意
尽管所有平台都需要必需的设置,但这些设置由 Windows 和 macOS
安装程序自动设置,因此所有用来更改这些设置的指令都适用于 Linux 用户。
-
DriverManagerEncoding
:驱动程序管理器使用的 UTF 编码标准。可以是以下内容之一:
ODBC 驱动程序编码必须与驱动程序管理器的编码相匹配。下表列出了不设置该参数时生效的各种平台的默认编码。如果默认值与驱动程序管理器使用的编码不匹配,则必须进行手动设置。有关驱动程序管理器编码的详细信息,请参阅驱动程序管理器文档。
注意
虽然 UTF-16 和 UTF-8 都是 DataDirect 驱动程序管理器的有效设置,但建议使用 UTF-16。
-
ErrorMessagesPath
:必需,包含 ODBC 驱动程序的错误消息文件的目录路径。这些文件(ODBCMessages.xml
和 VerticaMessages.xml
)存储在 Vertica ODBC 驱动程序文件所在的目录(例如,已下载的 .tar
所在的 opt/vertica/en-US
)中。
-
ODBCInstLib
:ODBC 安装程序库的路径。只有当未在 LD_LIBRARY_PATH
或 LIB_PATH
环境变量中设置包含该库的目录时,才需要此设置。主要驱动程序管理器的库文件如下:
您还可以控制 ODBC 和 ADO.NET 的客户端-服务器消息日志记录。有关详细信息,请参阅配置 ODBC 日志。
Linux 和 macOS
要在 Linux 或 macOS 上设置这些参数:
-
在客户端系统的任何位置创建文件 vertica.ini
。公共位置在共享配置的 /etc/
中,或者在每个用户配置的主目录中。
-
验证 ODBC 驱动程序的用户是否具有文件的读取权限。
-
将 VERTICAINI
环境变量设置为 vertica.ini
的路径。例如:
$ export VERTICAINI=/etc/vertica.ini
-
在 vertica.ini
中创建名为 [Driver]
的部分:
[Driver]
-
在 [Driver]
下,按如下格式设置参数。每个参数必须有其自己的行:
[Driver]
DriverManagerEncoding=UTF-16
ODBCInstLib=/usr/lib64/libodbcinst.so
Windows
Windows 客户端驱动程序安装程序自动为 ODBC 驱动程序配置所有必要的设置。设置存储在注册表中的 HKEY_LOCAL_MACHINE\SOFTWARE\Vertica\ODBC\Driver
下。
如果要进一步配置 ODBC,请使用 ODBC 数据源 程序。
2.1.4.6 - 配置 ODBC 日志
以下参数控制 ODBC 客户端驱动程序是否以及如何记录客户端和服务器之间的消息。
设置这些参数的方式因操作系统不同而异:
-
在 Linux 和 macOS 上,编辑您在安装期间创建的 vertica.ini
。例如,要将所有警告和更严重的消息记录到 /tmp/
中的日志文件:
[Driver]
LogLevel=4
LogPath=/tmp
-
在 Windows 上的 Windows 注册表中,编辑 HKEY_LOCAL_MACHINE\SOFTWARE\Vertica\ODBC\Driver
下面的键。
参数
-
LogLevel:在客户端和服务器之间记录的消息的严重性。有效值包括:
-
0:无日志记录
-
1:严重错误
-
2:错误
-
3:警告
-
4:信息
-
5:Debug
-
6:跟踪(所有消息)
为此设置指定的值设置了要记录的消息的最低严重性。例如,如果将 LogLevel 设置为 3,则表示客户端驱动程序将记录所有警告、错误和严重错误。
-
LogPath:用于存储日志文件的目录的绝对路径。例如: /var/log/verticaodbc
将日志条目转移到 ETW (Windows)
在 Windows 客户端上,可以将 ODBC 日志条目发送到 Windows 事件跟踪 (ETW),以便它们显示在 Windows 事件查看器中:
-
将驱动程序注册为 Windows 事件日志提供程序,并启用日志。
-
通过将带有数据 ETW 的字符串值 LogType 添加到 Windows 注册表来激活 ETW。
-
了解 Vertica 如何压缩 Windows 事件查看器的日志级别。
-
了解在事件查看器中的何处可以找到日志。
-
了解日志条目中事件 ID 的含义。
将 ODBC 驱动程序注册为 Windows 事件日志提供程序
要使用 ETW 日志记录,必须将 ODBC 驱动程序注册为 Windows 事件日志提供程序。您可以选择注册 32 位或 64 位驱动程序。注册驱动程序后,必须启用日志。
重要
如果您既未注册驱动程序也未启用日志,则输出将定向到 stdout。
-
以管理员身份打开命令提示符窗口,或使用“以管理员身份运行 (Run as Administrator)”选项启动命令提示符。
重要
您必须具有管理员权限才能成功完成下一步。
-
运行命令 wevtutil im
以注册 32 位或 64 位版本的驱动程序。
-
对于 64 位 ODBC 驱动程序,运行:
wevtutil im "c:\Program Files\Vertica Systems\ODBC64\lib\VerticaODBC64.man"
/resourceFilePath:"c:\Program Files\Vertica Systems\ODBC64\lib\vertica_9.1_odbc_3.5.dll"
/messageFilePath:"c:\Program Files\Vertica Systems\ODBC64\lib\vertica_9.1_odbc_3.5.dll"
-
对于 32 位 ODBC 驱动程序,运行:
wevtutil im "c:\Program Files (x86)\Vertica Systems\ODBC32\lib\VerticaODBC32.man"
/resourceFilePath:"c:\Program Files (x86)\Vertica Systems\ODBC32\lib\vertica_9.1_odbc_3.5.dll"
/messageFilePath:"c:\Program Files (x86)\Vertica Systems\ODBC32\lib\vertica_9.1_odbc_3.5.dll"
-
运行命令 wevtutil sl
以启用日志。
注意
如果您想稍后禁用日志,则可以使用相同的 wevtutil sl
命令,在发出语句时用 /e:false
代替 /e:true
。或者,您可以在 Windows 事件查看器本身中启用或禁用日志。
添加字符串值 LogType
默认情况下,Vertica 不会将 ODBC 日志条目发送到 ETW。要激活 ETW,请将字符串 LogType
添加到 Windows 注册表,并将其值设置为 ETW
。
-
通过在 Windows“运行 (Run)”命令框中键入 regedit.exe
来启动注册表编辑器。
-
导航到注册表中的 HKEY_LOCAL_MACHINE\SOFTWARE\Vertica\ODBC\Driver
。
-
在注册表编辑器 (Registry Editor) 窗口的右窗格中右键单击。
-
选择新建 (New),然后选择字符串值 (String Value)。
-
将字符串值的名称从 New Value #1
更改为 LogType
。
-
双击新的 LogType
条目。当系统提示输入新值时,输入 ETW
。
-
退出注册表编辑器。
默认情况下,禁用 ETW。启用 ETW 后,您可以通过从 LogType 字符串中清除值 ETW 来禁用它。
虽然 LogLevel 的范围从 0 到 6,但对于 Windows 事件查看器,此范围被压缩到 0 到 3 的范围。
以下示例显示了在 Windows 事件查看器中显示时如何转换 LogLevel。
在事件查看器中查找日志
-
启动 Windows 事件查看器。
-
从事件查看器(本地) (Event Viewer (Local)),展开应用程序和服务日志 (Applications and Services Logs)。
-
展开包含您要查看的日志的文件夹(例如 VerticaODBC64
)。
-
选择文件夹下的 Vertica ODBC 日志。条目出现在右窗格中。
-
请注意事件 ID (Event ID) 字段中的值。每个事件日志条目包含四个事件 ID 之一:
-
0:信息性(调试、信息和跟踪事件)
-
1:错误
-
2:严重事件
-
3:警告
2.1.5 - Python 客户端驱动程序
Vertica 支持多个用于创建客户端应用程序的 Python 驱动程序。
先决条件
要创建 Python 客户端应用程序,您必须安装所需的驱动程序。
2.1.5.1 - 安装 Python 客户端驱动程序
Vertica 支持多个 Python 客户端驱动程序。
安装 Vertica-Python
有关安装和使用说明,请参阅 Vertica-Python 存储库。
安装 Pyodbc
Pyodbc 模块与 Vertica ODBC 客户端驱动程序交互。要安装它:
-
安装 ODBC 客户端驱动程序。
-
安装兼容版本的 Python 和 Pyodbc。
2.1.6 - Node.js 客户端驱动程序
开源 vertica-nodejs 客户端驱动程序允许您使用 JavaScript 与数据库交互。有关详细信息,请参阅 npm 上的 vertica-nodejs 包。
2.1.7 - OLE DB 客户端驱动程序
OLE DB 客户端驱动程序是 Microsoft Analysis Services (SSAS) 和 C# 客户端应用程序与 Vertica 数据库交互的接口。
2.1.7.1 - 安装 OLE DB 客户端驱动程序
要安装 Vertica OLE DB 客户端驱动程序:
-
下载 Windows 客户端驱动安装程序。有关此安装程序中包含的驱动程序的详细信息,请参阅 Windows 客户端驱动程序安装程序。
-
运行安装程序并按照提示安装驱动程序。
-
重新启动系统。
安装 OLE DB 客户端驱动程序后,您可以配置 ETW 日志记录。
有关 OLE DB 客户端驱动程序如何与其他 Microsoft 组件集成的详细信息,请参阅 Microsoft 组件配置。
有关连接属性的列表,请参阅 OLE DB 连接属性。
2.1.7.1.1 - OLE DB 连接属性
可以使用“连接管理器 (Connection Manager)”设置 OLE DB 连接字符串属性,这些属性定义了连接。可以从 Visual Studio 中访问“连接管理器 (Connection Manager)”。
这些连接参数会显示在“连接 (Connection)”页面上。
连接管理器 (Connection Manager) 对话框中的 全部 (All) 页面列出了提供程序的所有可能的连接字符串属性。
下表列出了全部 (All) 页面中的连接参数。
有关特定于 Microsoft 的 OLE DB 属性,请参阅 Microsoft 文档 [OLE DB Properties](https://msdn.microsoft.com/en-us/library/windows/desktop/ms723130(v=vs.85)。
2.1.7.1.2 - 配置 OLE DB 日志
以下参数控制 OLE DB 客户端驱动程序如何记录客户端和服务器之间的消息。要设置它们,请编辑 Windows 注册表中 HKEY_LOCAL_MACHINE\SOFTWARE\Vertica\OLEDB\Driver
下的密钥:
-
LogLevel:在客户端和服务器之间记录的消息的严重性。有效值包括:
-
0:无日志记录
-
1:严重错误
-
2:错误
-
3:警告
-
4:信息
-
5:调试
-
6:跟踪(所有消息)
为此设置指定的值设置了要记录的消息的最低严重性。例如,如果将 LogLevel 设置为 3,则表示客户端驱动程序将记录所有警告、错误和严重错误。
-
LogPath:用于存储日志文件的目录的绝对路径。例如: /var/log/verticaoledb
将 OLE DB 日志条目转移到 ETW
在 Windows 客户端上,您可以指示 Vertica 将 OLE DB 日志条目发送到 Windows 事件跟踪 (ETW)。设置后,OLE DB 日志条目将出现在 Windows 事件查看器中。要使用 ETW:
-
将驱动程序注册为 Windows 事件日志提供程序,并启用日志。
-
通过将字符串值添加到 Windows 注册表来激活 ETW。
-
了解 Vertica 如何压缩 Windows 事件查看器的日志级别。
-
了解在事件查看器中的何处可以找到日志。
-
了解日志条目中事件 ID 的含义。
将 OLE DB 驱动程序注册为 Windows 事件日志提供程序
要使用 ETW 日志记录,您必须将 OLE DB 驱动程序注册为 Windows 事件日志提供程序。您可以选择注册 32 位或 64 位驱动程序。注册驱动程序后,您必须启用日志。
重要
如果您既未注册驱动程序也未启用日志,则输出将定向到 stdout。
-
以管理员身份打开命令提示符窗口,或使用“以管理员身份运行 (Run as Administrator)”选项启动命令提示符。
重要
您必须具有管理员权限才能成功完成下一步。
-
运行命令 wevtutil im
以注册 32 位或 64 位版本的驱动程序。
-
对于 64 位 OLE DB 驱动程序,运行:
wevtutil im "c:\Program Files\Vertica Systems\OLEDB64\lib\VerticaOLEDB64.man"
/resourceFilePath:"c:\Program Files\Vertica Systems\OLEDB64\lib\vertica_8.1_oledb.dll"
/messageFilePath:"c:\Program Files\Vertica Systems\OLEDB64\lib\vertica_8.1_oledb.dll"
-
对于 32 位 OLE DB 驱动程序,运行:
wevtutil im "c:\Program Files (x86)\Vertica Systems\OLEDB32\lib\VerticaOLEDB32.man"
/resourceFilePath:"c:\Program Files (x86)\Vertica Systems\OLEDB32\lib\vertica_8.1_oledb.dll"
/messageFilePath:"c:\Program Files (x86)\Vertica Systems\OLEDB32\lib\vertica_8.1_oledb.dll"
-
运行命令 wevtutil sl
以启用日志。
-
对于 64 位 OLE DB 驱动程序日志,运行:
wevtutil sl VerticaOLEDB64/e:true
-
对于 32 位 ODBC 驱动程序日志,运行:
wevtutil sl VerticaOLEDB32/e:true
注意
如果您想稍后禁用日志,则可以使用相同的 wevtutil sl
命令,在发出语句时用 /e:false
代替 /e:true
。或者,您可以在 Windows 事件查看器本身中启用或禁用日志。
添加字符串值 LogType
默认情况下,Vertica 不会将 OLE DB 日志条目发送到 ETW。要激活 ETW,请将字符串 LogType
添加到 Windows 注册表,并将其值设置为 ETW
。
-
通过在 Windows“运行 (Run)”命令框中键入 regedit.exe
来启动注册表编辑器。
-
在注册表中,导航到:HKEY_LOCAL_MACHINE\SOFTWARE\Vertica\OLEDB\Driver
。
-
在注册表编辑器 (Registry Editor) 窗口的右窗格中右键单击。
-
选择新建 (New),然后选择字符串值 (String Value)。
-
将字符串值的名称从 New Value #1
更改为 LogType
。
-
双击新的 LogType
条目。当系统提示输入新值时,输入 ETW
。
-
退出注册表编辑器。
默认情况下,ETW 设置为 off。激活 ETW 后,您可以稍后通过从 LogType 字符串中清除值 ETW 将其关闭。
虽然 LogLevel 的范围从 0 到 6,但对于 Windows 事件查看器,此范围被压缩到 0 到 3 的范围。
以下示例显示了在 Windows 事件查看器中显示时如何转换 LogLevel。
在事件查看器中查找日志
-
启动 Windows 事件查看器。
-
从事件查看器(本地) (Event Viewer (Local)),展开应用程序和服务日志 (Applications and Services Logs)。
-
展开包含您要查看的日志的文件夹(例如 VerticaOLEDB64
)。
-
选择文件夹下的 Vertica ODBC 日志。条目出现在右窗格中。
-
请注意事件 ID (Event ID) 字段中的值。每个事件日志条目包含四个事件 ID 之一:
-
0:信息性(调试、信息和跟踪事件)
-
1:错误
-
2:严重事件
-
3:警告
2.1.7.2 - 适用于 Windows 的 Microsoft Connectivity Pack
适用于 Windows 的 Vertica Microsoft Connectivity Pack 提供了一个用于访问 Microsoft Business Intelligence 工具的配置文件。Connectivity Pack 作为 Windows 客户端驱动程序和工具的一部分安装。
有关 Microsoft Connectivity Pack 中包含的组件的详细信息,请参阅 Microsoft 组件。
2.1.7.2.1 - Microsoft 组件
本节介绍可与 Microsoft Visual Studio 和 Microsoft SQL Server 结合使用的 Microsoft Business Intelligence 组件。配置后,您可以使用这些 Microsoft 组件来开发使用 Vertica 服务器的业务解决方案。
重要
适用于 Windows 的客户端驱动程序和工具包括 Vertica Microsoft 连接包。要使用连接包访问 Microsoft 商业智能工具,请在安装后重新启动系统以确保集成。
2.1.7.2.1.1 - Microsoft 组件配置
使用 Vertica ADO.NET 驱动程序、Visual Studio 插件和 OLE DB 驱动程序,您可以将 Vertica 服务器与包含先前已安装在系统上的 Microsoft 组件的环境集成。还有其他工具可用于与 Microsoft SQL Server 集成。
可用的驱动程序提供了与以下 Microsoft 组件的集成:
-
适用于 Visual Studio 2008 的 Business Intelligence Development Studio (BIDS),与 SQL Server 2012 结合使用。BIDS 是一个基于客户端的应用程序,用于开发基于 Microsoft Visual Studio 开发环境的商业智能解决方案。此应用程序包含其他特定于 SQL Server Business Intelligence 的项目类型。作为开发人员,您可以使用 BIDS 开发业务解决方案。
-
适用于 Visual Studio 2008/2010/2012/2013/2015 的 SQL Server Data Tools - Business Intelligence (SSDT-BI),与 SQL Server 2012、SQL Server 2014 和 SQL Server 2016 结合使用。SSDT-BI 取代适用于 Visual Studio 2008、Visual Studio 2010、Visual Studio 2012、Visual Studio 2013 和 Visual Studio 2015 的 BIDS。此组件的用途与 BIDS 相同,即提供用于开发商业智能解决方案的开发环境。
-
适用于 SQL Server 2012、SQL Server 2014 和 SQL Server 2016 的 SQL Server Analysis Services (SSAS)。可以使用 SSAS 进行 OLAP 和数据挖掘,同时使用 Vertica 作为多维数据集创建的源。
-
适用于 SQL Server 2012、SQL Server 2014 和 SQL Server 2016 的 SQL Server Integration Services (SSIS)。SSIS 提供了 SQL Server 类型映射,用于在 Vertica 和 SQL Server 之间映射数据类型。可以将 SSIS 用于数据迁移、数据集成和工作流以及 ETL。
下图显示了 Microsoft 组件和 Vertica 依赖项之间的关系。
2.1.7.2.1.2 - BIDS 和 SSDT-BI
Microsoft Visual Studio 2008 中提供 Business Intelligence Development Studio (BIDS) 和其他特定于 SQL Server Business Intelligence 的项目类型。BIDS 是用于开发包括分析服务、集成服务和报告服务项目在内的业务解决方案的主要环境。
SQL Server Data Tools - Business Intelligence (SSDT-BI) 取代适用于 Visual Studio 2010、2012、2013 和 2015 的 BIDS。此组件的用途与 BIDS 相同,即提供用于开发业务解决方案的开发环境。
BIDS 和 SSDT-BI 都是基于客户端的应用程序,并且都包含其他特定于 SQL Server Business Intelligence 的项目类型。
可以使用 Visual Studio Shell 集成插件从 Visual Studio Server Explorer 中浏览数据库。使用此功能,您可以在 BIDS 或 SSDT-BI 开发环境外部工作,以执行诸如列出表或插入数据等任务。在 BIDS 或 SSDT-BI 模式下使用 Visual Studio 时,您可以使用 Vertica 数据库中的数据开发业务解决方案。例如,您可以创建多维数据集或打开表。
Microsoft 不支持以下配置:
-
不能将 Microsoft Visual Studio 2008 与 BIDS 开发环境结合使用以创建 SQL Server 2012 Business Intelligence 解决方案。
-
不能将 Microsoft Visual Studio 2010/2012/2013/2015 与 SSDT-BI 开发环境结合使用以创建 SQL Server 2008 Business Intelligence 解决方案。
2.1.7.2.1.3 - SQL Server Analysis Services (SSAS) 支持
BIDS 或 SSDT-BI 包含 Analysis Services 项目,该项目可用于开发适用于商业智能应用程序的联机分析处理 (Online Analytical Processing, OLAP)。此项目类型包含以下内容的模板:
它还提供处理这些对象的工具。
注意
注意: Micro Focus 建议使用 Vertica OLE DB 驱动程序从 SSAS 连接到 Vertica 服务器以提高性能。
可以在 OLE DB 连接属性 中找到 OLE DB 连接属性。
2.1.7.2.1.4 - SQL Server Integration Services (SSIS) 支持
BIDS 或 SSDT-BI 包含用于开发 ETL 解决方案的 Integration Services 项目。此项目类型包含以下内容的模板:
它还提供处理这些对象的工具。
通过 SSIS 和导入/导出向导,您可以使用 Vertica 作为数据源和数据目标。必须在集成服务器和 BIDS 工作站或 SSDT-BI 工作站上安装特定于 Vertica 的映射文件才能启用此功能。适用于 Windows 的 Vertica 客户端驱动程序和工具将以 32 位和 64 位版本安装这些映射文件作为“SQL Server 类型映射”组件。
注意
注意: 始终应使用 Vertica ADO.NET 驱动程序从 SSIS 连接到 Vertica 服务器。
2.1.7.2.1.5 - SQL Server Reporting Services (SSRS) 支持
BIDS 或 SSDT-BI 包含用于开发报表解决方案的报表项目。
可以使用 Vertica 作为 Reporting Services 的数据源。安装程序将实施各种配置文件修改,以在 BIDS 工作站或 SSDT-BI 工作站和 Reporting Services 服务器上启用此功能。
2.1.7.2.2 - 兼容性问题和限制
本节列出兼容性问题以及将 Microsoft Connectivity Pack 与 Microsoft Visual Studio 和 Microsoft SQL Server 集成的限制。
2.1.7.2.2.1 - BIDS 和 SSDT-BI 限制
BIDS 和 SSDT-BI 是适用于 Analysis Services、Integration Services 和 Reporting Services 项目的 32 位开发环境。它们并非设计用于在 64 位 Itanium 架构上运行,因此不会安装在 Itanium 服务器上。
2.1.7.2.2.3 - SSIS 数据类型限制
以下部分介绍了使用 SQL Server Integration Services (SSIS) 时的数据类型限制。
时间数据传输
传输时间数据时,SSIS 使用支持超过六位数精度的 TimeSpan 数据类型。Vertica ADO.NET 驱动程序会将 TimeSpan 转换为最多支持六位数的 Interval 数据类型。传输过程中,Interval 类型不会转换为 TimeSpan 类型。因此,如果时间值的精度超过六位数,则数据会被截断(而不是舍入)。
有关 ADO.NET 数据类型的信息,请参考 ADO.NET 数据类型。
DATE 和 DATETIME 精度
为确保正常运行而不发生错误,DATE 和 DATETIME 的范围介于 0001-01-01 00:00:00.0000000 到 9999-12-31 23:59:59.999999 之间。
在 SSIS 中,DATETIME 类型 (DT_TIMESTAMP) 对秒最多支持三个小数位。系统会自动丢弃后面的小数。只能对介于 January 1, 1753 到 December 31, 9999 之间的 DATETIME 值执行派生列转换。
数值精度
允许的最大和最小十进制值为:
-
最大值: +79,228,162,514,264,337,593,543,950,335
-
最小值: -79,228,162,514,264,337,593,543,950,335
例如,如果位数是 16,值的范围为:
+/- 7,922,816,251,426.4337593543950335
有效位数范围为小于 29 以及大于 38 的任何数字。使用 29 和 38 之间的小数位数不会生成错误。
请参阅:http://msdn.microsoft.com/en-us/library/system.decimal.maxvalue.aspx
不支持的浮点值
SQL Server 不支持 NaN、Infinity 或 –Infinity 值。使用 SSIS 在 Vertica 实例之间进行传输时,可以使用这些值,但这些值不能用于 SQL Server 目标。
字符串转换
在 SSIS 中使用的 CHAR 和 VARCHAR 数据类型为 DT_WSTR,其最大长度是 4000 个字符。
在 SSIS 中,Vertica 字符串会转换为 SSIS 中的 Unicode 字符串,以处理多语言数据。可以使用数据转换任务将这些字符串转换为 ASCII。
标度
只要所使用的小数位数超过 38,SSIS 就会将其替换为值 4。
区间转换
SSIS 不支持间隔类型。它会将这些类型转换为 TIME,并去除日组件。时间间隔类型超过一天的任何包将返回不正确的结果。
SQL Server 导入和导出向导的数据映射问题
使用 SQL Server 导入和导出向导创建集成服务包 (SSIS) 时,某些数据类型无法自动正确映射。将该向导与以下提供程序结合使用时,将出现映射问题:
要避免此问题,请使用 BIDS 或 SSDT-BI 手动更改类型映射。
数据传输失败
将集成服务包 (SSIS) 与适用于 SQL Server 2008 或 SQL Server 2012 的 SQL Server OLE DB 提供程序结合使用时,如果是从 Vertica 传输到 SQL Server,则某些数据类型的传输会失败。若要避免此问题,请使用 BIDS 或 SSDT-BI 传输数据。
VARBINARY/LONG VARBINARY 数据类型的批量插入
有时,VARBINARY 或 LONG VARBINARY 数据类型的批量插入的行之一会超过数据类型限制:
-
VARBINARY:65 KB
-
LONG VARBINARY:32 MB
在这种情况下,将拒绝所有行,而非仅拒绝长度超过类型限制的行。批量插入将失败,并显示消息“行被拒绝 (row(s) are rejected)”。
若要避免此问题,请使用谓词从源中筛选出无法适应接收数据库的行。
SQL Server 查询设计器布尔查询
在 SQL Server 查询设计器中发出布尔查询时,必须用引号将布尔列值括起来。否则,您将收到 SQL 执行错误(例如,someboolean = 'true'
)。
2.1.7.2.2.4 - SSRS 限制
数据连接向导解决方法
SSRS 报表向导提供一个数据连接向导。选择该向导并输入所有连接信息后,确定 按钮被禁用。您无法保存工作,也无法继续操作。解决方法是不要使用该向导,并改为使用以下面板:
报表向导 - 查询设计器
Vertica 使用报表向导的通用查询设计器。其他数据源使用支持以可视化方式构建查询的图形查询设计器。图形查询设计器是名为 Visual Data Tools (VDT) 的程序包的一部分。图形查询设计器只能与通用 OLE DB 提供程序和内置提供程序配合工作。您不能将其与 Vertica 数据提供程序结合使用。
报表生成器
Report Builder 是一款基于 Web 的报告设计工具。此工具不支持使用自定义数据扩展来创建报表,因此您不能将其与 Vertica 结合使用。使用报表生成器创建报表时,现有 Vertica 数据源会显示在可用数据源的列表中。但是,选择 Vertica 数据源会导致发生错误。
映射 Vertica ** 目标时未自动提供架构名称**
目前,当您映射 Vertica 目标时,不会自动提供架构名称。您必须手动输入架构名称或从下拉菜单中选择架构名称,如下所示:
2.1.8 - ADO.NET 客户端驱动程序
Vertica ADO.NET 驱动程序允许您使用 C# 访问 Vertica。
2.1.8.1 - 安装 ADO.NET 客户端驱动程序
先决条件
ADO.NET 客户端驱动程序需要以下条件:
安装
要安装 ADO.NET 客户端驱动程序:
-
下载 Windows 客户端驱动安装程序。有关此安装程序中包含的驱动程序的详细信息,请参阅 Windows 客户端驱动程序安装程序。
-
运行安装程序并按照提示安装驱动程序。
-
重新启动系统。
2.2 - 升级客户端驱动程序
通常会为 Vertica 服务器的每个新发行版更新 Vertica 客户端驱动程序。客户端驱动程序安装包包含相应 Vertica 服务器发行版的版本号。通常,驱动程序与下一个发行版具有向前兼容性,因此在升级到 Vertica Analytics Platform 服务器的下一个版本之后,客户端应用程序仍能使用较旧版本的驱动程序进行连接。有关哪些客户端驱动程序版本适用于每个 Vertica 服务器版本的详细信息,请参阅客户端驱动程序和服务器版本兼容性。
注意
Vertica ODBC、JDBC 和 ADO.NET 客户端驱动程序向后兼容所有受支持的 Vertica 服务器版本。
应在升级服务器之后尽快升级客户端,以利用新功能并保持与服务器的最高兼容性。
若要升级驱动程序,请遵循在最初安装驱动程序时使用的相同过程。新安装将覆盖旧安装。有关升级的任何特别说明,请参阅在客户端平台上安装驱动程序的特定说明。
注意
安装新的 ODBC 驱动程序不会更改现有 DSN 设置。如果客户端系统使用 DSN,您可能需要在 DSN 或
odbcinst.ini
文件中更改驱动程序设置。有关详细信息,请参阅
创建 ODBC 数据源名称。
2.3 - 设置客户端连接标记
连接到 Vertica 数据库时,您可以设置客户端连接标记。您还可以使用 SET_CLIENT_LABEL 函数设置客户端连接标记,或使用 GET_CLIENT_LABEL 函数返回客户端连接标记。
设置客户端连接标记:
=> SELECT SET_CLIENT_LABEL('py_data_load_application');
SET_CLIENT_LABEL
----------------------------------------------
client_label set to py_data_load_application
(1 row)
返回当前的客户端连接标记:
=> SELECT GET_CLIENT_LABEL();
GET_CLIENT_LABEL
--------------------------
py_data_load_application
(1 row)
JDBC
JDBC 客户端具有用于设置和返回客户端连接标签的方法:getClientInfo() 和 setClientInfo()。这些方法可与 SQL 函数 GET_CLIENT_LABEL 和 SET_CLIENT_LABEL 配合使用。
使用这两种方法时,请确保向 setter 和 getter 方法都传递字符串值 APPLICATIONNAME
。
setClientInfo() 用于创建客户端标签,而 getClientInfo() 则用于返回客户端标签:
import java.sql.*;
import java.util.Properties;
public class ClientLabelJDBC {
public static void main(String[] args) {
Properties myProp = new Properties();
myProp.put("user", "dbadmin");
myProp.put("password", "");
myProp.put("loginTimeout", "35");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://docc05.verticacorp.com:5433/doccdb", myProp);
System.out.println("Connected!");
conn.setClientInfo("APPLICATIONNAME", "JDBC Client - Data Load");
System.out.println("New Conn label: " + conn.getClientInfo("APPLICATIONNAME"));
conn.close();
} catch (SQLTransientConnectionException connException) {
// There was a potentially temporary network error
// Could automatically retry a number of times here, but
// instead just report error and exit.
System.out.print("Network connection issue: ");
System.out.print(connException.getMessage());
System.out.println(" Try again later!");
return;
} catch (SQLInvalidAuthorizationSpecException authException) {
// Either the username or password was wrong
System.out.print("Could not log into database: ");
System.out.print(authException.getMessage());
System.out.println(" Check the login credentials and try again.");
return;
} catch (SQLException e) {
// Catch-all for other exceptions
e.printStackTrace();
}
}
}
运行此方法时,会将以下结果打印到标准输出:
Connected!
New Conn Label: JDBC Client - Data Load
2.4 - 使用旧版驱动程序
Vertica 服务器支持来自以前版本的客户端驱动程序的连接。有关 Vertica 服务器和 Vertica 客户端版本之间兼容性的详细信息,请参阅客户端驱动程序和服务器版本兼容性。
3 - 访问 Vertica
下表显示了使用受支持的编程语言访问 Vertica 时必须设置的客户端驱动程序:
3.1 - C/C++
Vertica 提供开放式数据库连接 (ODBC) 驱动程序,此驱动程序可使应用程序能够连接到 Vertica 数据库。此驱动程序可供自定义编写的客户端应用程序(使用 ODBC API 与 Vertica 交互)使用。ODBC 还可由许多第三方应用程序(包括商业智能应用程序以及提取、转换和加载 (Extract, Transform, and Load, ETL) 应用程序)用来连接到 Vertica。
此部分详细介绍配置 Vertica ODBC 驱动程序的过程。此部分还介绍如何在自己的客户端应用程序中使用 ODBC API 连接到 Vertica。
尽管使用 C、C++、Perl、PHP 等编写的客户端应用程序均使用 ODBC 客户端驱动程序连接到 Vertica,但此部分仅阐述 C 和 C++ 应用程序。
3.1.1 - ODBC 体系结构
ODBC 架构包含四个层:
-
客户端应用程序
此层是一个应用程序,用于通过数据源名称 (DSN) 打开数据源。然后,此应用程序会将请求发送到数据源,并接收这些请求的结果。请求以调用 ODBC 函数的方式发出。
-
驱动程序管理器
此层是客户端系统上的一个库,用作客户端应用程序和一个或多个驱动程序之间的中介。该驱动程序管理器执行下列操作:
在 Windows 和 macOS 客户端系统上,驱动程序管理器由操作系统提供。在 Linux 系统上,您通常需要安装驱动程序管理器。有关可在客户端平台上与 Vertica 结合使用的驱动程序管理器的列表,请参阅客户端驱动程序支持。
-
Driver
此层是客户端系统上的一个库,提供对特定数据库的访问。此层可将请求转换为数据库所需的格式,还可将结果重新转换为客户端应用程序所需的格式。
-
数据库
数据库可处理在客户端应用程序上启动的请求并返回结果。
3.1.2 - ODBC 功能支持
适用于 Vertica 的 ODBC 驱动程序支持 Microsoft ODBC 3.5 规范中定义的大部分功能。以下功能不受支持:
Vertica ODBC 驱动程序会准确报告其功能。如果需要确定驱动程序是否符合特定功能,您应使用 SQLGetInfo()
函数直接查询驱动程序的功能。
3.1.3 - Vertica 和 ODBC 数据类型转换
大多数数据类型均可在 Vertica 和 ODBC 之间透明地进行转换。此部分介绍了几种需要特殊处理的数据类型。
注意
-
GEOMETRY 和 GEOGRAPHY 数据类型被 ODBC 驱动程序视为 LONG VARCHAR 数据。
-
Vertica 支持 ODBC 所支持的标准间隔数据类型。请参阅 Microsoft 的 ODBC 参考文档中的间隔数据类型。
-
Vertica 9.0.0 版引入了 UUID 数据类型,包括对 UUID 的 JDBC 支持。Vertica ADO.NET、ODBC 和 OLE DB 客户端在 9.0.1 版中添加了对 UUID 的完全支持。Vertica 保持与不支持 UUID 数据类型的旧 受支持 客户端驱动程序版本的向后兼容性,如下所示:
另请参阅
3.1.4 - ODBC 头文件
Vertica ODBC 驱动程序提供了名为
verticaodbc.h
的 C 头文件,该头文件定义了几个可在应用程序中使用的有用常数。使用这些常数,您可以访问和更改特定于 Vertica 的设置。
此文件的位置取决于客户端操作系统:
-
Linux和UNIX系统:/opt/vertica/include
-
Windows系统:C:\Program Files (x86)\Vertica\ODBC\include
下面列出了此文件中定义的常数。
3.1.5 - 连接到数据库
在任何 ODBC 应用程序中,第一步总是连接到数据库。创建与使用 ODBC 的数据源的连接时,应使用 DSN(其中包含要使用的驱动程序的详细信息、数据库主机和有关连接到数据源的其他基本信息)的名称。
若要连接到数据库,您需要对应用程序执行以下 4 个步骤:
-
调用 SQLAllocHandle()
来为 ODBC 环境分配句柄。此句柄用于创建连接对象和设置应用程序范围设置。
-
使用环境句柄设置应用程序要使用的 ODBC 版本。这样可确保数据源知道应用程序将使用哪个 API 与其交互。
-
通过调用 SQLAllocHandle()
来分配数据库连接句柄。此句柄代表与特定数据源的连接。
-
使用 SQLConnect()
或 SQLDriverConnect()
函数打开与数据库的连接。
注意
如果在连接字符串或 DSN 中指定区域设置,调用连接函数将返回有关成功连接的 SQL_SUCCESS_WITH_INFO,并显示有关区域设置状态的消息。
创建与数据库的连接时,如果在连接时只需要设置用户名和密码选项,请使用 SQLConnect()
。如果要更改诸如区域设置等选项,请使用 SQLDriverConnect()
。
以下示例演示了使用名为 ExampleDB 的 DSN 连接到数据库。成功创建连接后,此示例仅关闭该连接。
// Demonstrate connecting to Vertica using ODBC.
// Standard i/o library
#include <stdio.h>
#include <stdlib.h>
// Only needed for Windows clients
// #include <windows.h>
// SQL include files that define data types and ODBC API
// functions
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
int main()
{
SQLRETURN ret; // Stores return value from ODBC API calls
SQLHENV hdlEnv; // Handle for the SQL environment object
// Allocate an a SQL environment object
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hdlEnv);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not allocate a handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated an environment handle.\n");
}
// Set the ODBC version we are going to use to
// 3.
ret = SQLSetEnvAttr(hdlEnv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not set application version to ODBC 3.\n");
exit(EXIT_FAILURE);
} else {
printf("Set application version to ODBC 3.\n");
}
// Allocate a database handle.
SQLHDBC hdlDbc;
ret = SQLAllocHandle(SQL_HANDLE_DBC, hdlEnv, &hdlDbc);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not allocate database handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated Database handle.\n");
}
// Connect to the database using
// SQL Connect
printf("Connecting to database.\n");
const char *dsnName = "ExampleDB";
const char* userID = "ExampleUser";
const char* passwd = "password123";
ret = SQLConnect(hdlDbc, (SQLCHAR*)dsnName,
SQL_NTS,(SQLCHAR*)userID,SQL_NTS,
(SQLCHAR*)passwd, SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not connect to database.\n");
exit(EXIT_FAILURE);
} else {
printf("Connected to database.\n");
}
// We're connected. You can do real
// work here
// When done, free all of the handles to close them
// in an orderly fashion.
printf("Disconnecting and freeing handles.\n");
ret = SQLDisconnect( hdlDbc );
if(!SQL_SUCCEEDED(ret)) {
printf("Error disconnecting from database. Transaction still open?\n");
exit(EXIT_FAILURE);
}
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
exit(EXIT_SUCCESS);
}
运行以上代码后,将输出以下内容:
Allocated an environment handle.
Set application version to ODBC 3.
Allocated Database handle.
Connecting to database.
Connected to database.
Disconnecting and freeing handles.
有关使用 SQLDriverConnect 连接到数据库的示例,请参阅设置 ODBC 会话的区域设置和编码。
注意
-
如果您使用 DataDirect® 驱动程序管理器,则应在连接到 Vertica 时始终对 SQLDriverConnect 函数的 DriverCompletion 参数(函数调用中的最终参数)使用 SQL_DRIVER_NOPROMPT 值。Linux 和 UNIX 平台上 Vertica 的 ODBC 驱动程序不包含 UI,因此无法提示用户输入密码。
-
在 Windows 客户端平台上,ODBC 驱动程序会提示用户提供连接信息。有关详细信息,请参阅提示 Windows 用户提供缺少的连接属性。
-
如果数据库不符合 Vertica 许可协议,应用程序将在 SQLConnect()
函数的返回值中收到警告消息。始终应让应用程序检查此返回值是否为 SQL_SUCCESS_WITH_INFO。如果是,应让应用程序提取消息并向用户显示。
3.1.6 - 负载均衡
本机连接负载均衡
本机连接负载均衡有助于在 Vertica 数据库中的主机上分散客户端连接所带来的开销。服务器和客户端都必须启用本机连接负载均衡。如果两者者都启用了本机负载均衡,则当客户端最初连接到数据库中的主机时,此主机会从数据库中当前正在运行的主机列表中选择一个主机来处理客户端连接,并通知客户端它所选择的主机。
如果最初联系的主机没有选择自身来处理连接,则客户端会断开连接,然后打开指向第一个主机所选主机的另一个连接。指向此第二个主机的连接进程将照常进行—如果启用了 SSL,则会启动 SSL 协商,否则客户端会启动身份验证过程。有关详细信息,请参阅关于本机连接负载均衡。
若要在客户端上启用本机连接负载均衡,请在 DSN 条目或连接字符串中将 ConnectionLoadBalance 连接参数设置为 true。以下示例说明了如何在本机连接负载均衡已启用的情况下多次连接到数据库,以及从 V_MONITOR.CURRENT_SESSION 系统表获取处理连接的节点的名称。
// Demonstrate enabling native load connection balancing.
// Standard i/o library
#include <stdlib.h>
#include <iostream>
#include <assert.h>
// Only needed for Windows clients
// #include <windows.h>
// SQL include files that define data types and ODBC API
// functions
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
using namespace std;
int main()
{
SQLRETURN ret; // Stores return value from ODBC API calls
SQLHENV hdlEnv; // Handle for the SQL environment object
// Allocate an a SQL environment object
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hdlEnv);
assert(SQL_SUCCEEDED(ret));
// Set the ODBC version we are going to use to
// 3.
ret = SQLSetEnvAttr(hdlEnv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
assert(SQL_SUCCEEDED(ret));
// Allocate a database handle.
SQLHDBC hdlDbc;
ret = SQLAllocHandle(SQL_HANDLE_DBC, hdlEnv, &hdlDbc);
assert(SQL_SUCCEEDED(ret));
// Connect four times. If load balancing is on, client should
// connect to different nodes.
for (int x=1; x <= 4; x++) {
// Connect to the database using SQLDriverConnect. Set
// ConnectionLoadBalance to 1 (true) to enable load
// balancing.
cout << endl << "Connection attempt #" << x << "... ";
const char *connStr = "DSN=VMart;ConnectionLoadBalance=1;"
"UID=ExampleUser;PWD=password123";
ret = SQLDriverConnect(hdlDbc, NULL, (SQLCHAR*)connStr, SQL_NTS,
NULL, 0, NULL, SQL_DRIVER_NOPROMPT );
if(!SQL_SUCCEEDED(ret)) {
cout << "failed. Exiting." << endl;
exit(EXIT_FAILURE);
} else {
cout << "succeeded" << endl;
}
// We're connected. Query the v_monitor.current_session table to
// find the name of the node we've connected to.
// Set up a statement handle
SQLHSTMT hdlStmt;
SQLAllocHandle(SQL_HANDLE_STMT, hdlDbc, &hdlStmt);
assert(SQL_SUCCEEDED(ret));
ret = SQLExecDirect( hdlStmt, (SQLCHAR*)"SELECT node_name FROM "
"V_MONITOR.CURRENT_SESSION;", SQL_NTS );
if(SQL_SUCCEEDED(ret)) {
// Bind varible to column in result set.
SQLTCHAR node_name[256];
ret = SQLBindCol(hdlStmt, 1, SQL_C_TCHAR, (SQLPOINTER)node_name,
sizeof(node_name), NULL);
while(SQL_SUCCEEDED(ret = SQLFetchScroll(hdlStmt, SQL_FETCH_NEXT,1))) {
// Print the bound variables, which now contain the values from the
// fetched row.
cout << "Connected to node " << node_name << endl;
}
}
// Free statement handle
SQLFreeHandle(SQL_HANDLE_STMT,hdlStmt);
cout << "Disconnecting." << endl;
ret = SQLDisconnect( hdlDbc );
assert(SQL_SUCCEEDED(ret));
}
// When done, free all of the handles to close them
// in an orderly fashion.
cout << endl << "Freeing handles..." << endl;
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
cout << "Done!" << endl;
exit(EXIT_SUCCESS);
}
运行以上示例后,将生成类似于以下内容的输出:
Connection attempt #1... succeeded
Connected to node v_vmart_node0001
Disconnecting.
Connection attempt #2... succeeded
Connected to node v_vmart_node0002
Disconnecting.
Connection attempt #3... succeeded
Connected to node v_vmart_node0003
Disconnecting.
Connection attempt #4... succeeded
Connected to node v_vmart_node0001
Disconnecting.
Freeing handles...
Done!
基于主机名的负载均衡
您还可以通过将单个主机名解析为多个 IP 地址来平衡工作负载。ODBC 客户端驱动程序通过自动将主机名随机解析为指定的 IP 地址之一来实现负载均衡。
例如,假设主机名 verticahost.example.com
在 etc/hosts
中有以下条目:
192.0.2.0 verticahost.example.com
192.0.2.1 verticahost.example.com
192.0.2.2 verticahost.example.com
指定主机名 verticahost.example.com
会随机解析为列出的 IP 地址之一。
3.1.7 - 连接故障转移
如果客户端应用程序尝试连接到 Vertica 群集中处于关闭状态的主机,则使用默认连接配置时连接尝试将会失败。此故障通常会向用户返回一个错误。用户必须等待主机恢复并重试连接,或者手动编辑连接设置以选择其他主机。
由于 Vertica 分析型数据库采用分布式架构,通常您不会关注哪个数据库主机处理客户端应用程序的连接。可以使用客户端驱动程序的连接故障转移功能来防止用户在无法访问连接设置中所指定主机的情况下收到连接错误。JDBC 驱动程序可通过几种方式让客户端驱动程序在无法访问连接参数中所指定的主机时自动尝试连接到其他主机:
-
将 DNS 服务器配置为返回一个主机名的多个 IP 地址。在连接设置中使用此主机名时,客户端会尝试连接到 DNS 找到的第一个 IP 地址。如果位于该 IP 地址的主机无法访问,则客户端会尝试连接到第二个 IP,依此类推,直到其成功地连接到某个主机或试过所有 IP 地址为止。
-
提供当您在连接参数中指定的主要主机无法访问时客户端驱动程序尝试连接的备份主机列表。
-
(仅限 JDBC)在尝试连接到下一个节点之前,使用特定于驱动程序的连接属性来管理超时。
在所有方法中,故障转移过程对于客户端应用程序是透明的(除了在选择使用列表故障转移方法时需指定备份主机列表之外)。如果主要主机无法访问,客户端驱动程序会自动尝试连接到其他主机。
故障转移仅适用于初次建立客户端连接的情况。如果连接断开,驱动程序不会自动尝试重新连接到数据库中的其他主机。
通常可选择上述两种故障转移方法中的一种。但它们可以一起使用。如果您的 DNS 服务器返回多个 IP 地址,并且您提供了备份主机列表,则客户端会首先尝试连接 DNS 服务器返回的所有 IP,然后再尝试连接备份列表中的主机。
注意
如果备份主机列表中的主机名解析为多个 IP 地址,客户端不会尝试连接所有这些地址,而只会尝试连接列表中的第一个 IP 地址。
DNS 故障转移方法可集中处理配置客户端故障转移。在向 Vertica 分析型数据库群集添加新节点时,可以选择通过编辑 DNS 服务器设置来将这些节点添加到故障转移列表中。所有使用 DNS 服务器连接到 Vertica 分析型数据库的客户端系统都会自动采用连接故障转移,而无需更改任何设置。但此方法需要对所有客户端用于连接到 Vertica 分析型数据库群集的 DNS 服务器具有管理访问权限。这在贵组织中可能无法实现。
使用备份服务器列表要比编辑 DNS 服务器设置更加容易。但此方法不能集中处理故障转移功能。如果更改了 Vertica 分析型数据库群集,您可能需要在每个客户端系统上更新应用程序设置。
使用 DNS 故障转移
若要使用 DNS 故障转移,您需要更改 DNS 服务器的设置,将单一主机名映射为 Vertica 分析型数据库群集中主机的多个 IP 地址。然后让所有客户端应用程序都使用该主机名连接到 Vertica 分析型数据库。
您可以选择让 DNS 服务器为主机名返回任何所需数量的 IP 地址。在小型群集中,您可以选择让其返回群集中所有主机的 IP 地址。但对于大型群集,应考虑选择返回一部分主机。否则可能会导致长时间延迟,因为客户端驱动程序尝试连接到数据库中处于关闭状态的每个主机会失败。
使用备份主机列表
若要启用基于备份列表的连接故障转移,需要在客户端应用程序的 BackupServerNode
参数中指定主机的至少一个 IP 地址或主机名。可以视情况在主机名或 IP 后面使用冒号和端口号。如果未提供端口号,驱动程序会默认使用标准 Vertica 端口号 (5433)。若要列出多个主机,请用逗号分隔这些主机。
以下示例演示了如何通过设置 BackupServerNode
连接参数来指定可尝试连接的其他主机。由于有意在连接字符串中使用了一个不存在的节点,因此初始连接失败。客户端驱动程序必须尝试通过备份主机来与 Vertica 建立连接。
// Demonstrate using connection failover.
// Standard i/o library
#include <stdlib.h>
#include <iostream>
#include <assert.h>
// Only needed for Windows clients
// #include <windows.hgt;
// SQL include files that define data types and ODBC API
// functions
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
using namespace std;
int main()
{
SQLRETURN ret; // Stores return value from ODBC API calls
SQLHENV hdlEnv; // Handle for the SQL environment object
// Allocate an a SQL environment object
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hdlEnv);
assert(SQL_SUCCEEDED(ret));
// Set the ODBC version we are going to use to
// 3.
ret = SQLSetEnvAttr(hdlEnv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
assert(SQL_SUCCEEDED(ret));
// Allocate a database handle.
SQLHDBC hdlDbc;
ret = SQLAllocHandle(SQL_HANDLE_DBC, hdlEnv, &hdlDbc);
assert(SQL_SUCCEEDED(ret));
/* DSN for this connection specifies a bad node, and good backup nodes:
[VMartBadNode]
Description=VMart Vertica Database
Driver=/opt/vertica/lib64/libverticaodbc.so
Database=VMart
Servername=badnode.example.com
BackupServerNode=v_vmart_node0002.example.com,v_vmart_node0003.example.com
*/
// Connect to the database using SQLConnect
cout << "Connecting to database." << endl;
const char *dsnName = "VMartBadNode"; // Name of the DSN
const char* userID = "ExampleUser"; // Username
const char* passwd = "password123"; // password
ret = SQLConnect(hdlDbc, (SQLCHAR*)dsnName,
SQL_NTS,(SQLCHAR*)userID,SQL_NTS,
(SQLCHAR*)passwd, SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
cout << "Could not connect to database." << endl;
exit(EXIT_FAILURE);
} else {
cout << "Connected to database." << endl;
}
// We're connected. Query the v_monitor.current_session table to
// find the name of the node we've connected to.
// Set up a statement handle
SQLHSTMT hdlStmt;
SQLAllocHandle(SQL_HANDLE_STMT, hdlDbc, &hdlStmt);
assert(SQL_SUCCEEDED(ret));
ret = SQLExecDirect( hdlStmt, (SQLCHAR*)"SELECT node_name FROM "
"v_monitor.current_session;", SQL_NTS );
if(SQL_SUCCEEDED(ret)) {
// Bind varible to column in result set.
SQLTCHAR node_name[256];
ret = SQLBindCol(hdlStmt, 1, SQL_C_TCHAR, (SQLPOINTER)node_name,
sizeof(node_name), NULL);
while(SQL_SUCCEEDED(ret = SQLFetchScroll(hdlStmt, SQL_FETCH_NEXT,1))) {
// Print the bound variables, which now contain the values from the
// fetched row.
cout << "Connected to node " << node_name << endl;
}
}
cout << "Disconnecting." << endl;
ret = SQLDisconnect( hdlDbc );
assert(SQL_SUCCEEDED(ret));
// When done, free all of the handles to close them
// in an orderly fashion.
cout << endl << "Freeing handles..." << endl;
SQLFreeHandle(SQL_HANDLE_STMT,hdlStmt);
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
cout << "Done!" << endl;
exit(EXIT_SUCCESS);
}
运行此示例后,系统控制台上的输出类似于以下内容:
Connecting to database.
Connected to database.
Connected to node v_vmart_node0002
Disconnecting.
Freeing handles...
Done!
请注意,系统会与备份列表中的第一个节点建立连接(节点 2)。
注意
启用本机连接负载均衡时,在 BackupServerNode 连接参数中指定的其他服务器将只用于指向 Vertica 主机的初始连接。如果主机将客户端重定向至数据库群集中的其他主机来处理其连接请求,则第二个连接将不使用备份节点列表。这很少出现问题,因为本机连接负载均衡知道数据库中的哪个节点目前正处于运行状态。 有关详细信息,请参阅
负载均衡。
3.1.8 - 提示 Windows 用户提供缺少的连接属性
如果缺少必需信息,Vertica Windows ODBC 驱动程序会提示用户提供连接信息。当客户端应用程序调用 SQLDriverConnect
以连接到 Vertica 并且以下任一条件成立时,驱动程序将显示“Vertica 连接对话框 (Vertica Connection Dialog)”:
如果以上任一条件成立,驱动程序将向用户显示“Vertica 连接对话框 (Vertica Connection Dialog)”以提示用户提供连接信息。
该对话框包含填入的连接字符串或 DSN 中提供的所有属性值。
注意
连接字符串至少需要指定 Vertica 作为驱动程序,否则 Windows 不知道应使用 Vertica ODBC 驱动程序来打开连接。
连接对话框上的必填字段是“数据库 (Database)”、“UID”、“服务器 (Server)”和“端口 (Port)”。填充这些字段后,表单将启用确定 (OK) 按钮。
如果用户在该对话框上单击取消 (Cancel),SQLDriverConnect
函数调用将立即返回 SQL_NO_DATA,而不会尝试连接到 Vertica。如果用户提供的连接信息不完整或不正确,连接函数将在连接尝试失败后返回 SQL_ERROR。
注意
如果 SQLDriverConnect
函数调用的 DriverCompletion 属性为 SQL_DRIVER_NOPROMPT,ODBC 驱动程序将立即返回 SQL_ERROR,以指示由于未提供足够信息且不允许驱动程序提示用户提供缺少的信息而无法连接。
3.1.9 - 提示 Windows 用户提供密码
如果向 SQLDriverConnect
函数(客户端应用程序调用此函数以连接到 Vertica)提供的连接字符串或 DSN 缺少连接到数据库所需的任何必需的连接属性,Vertica Windows ODBC 驱动程序将打开一个对话框,提示用户输入缺失的信息(请参阅提示 Windows 用户提供缺少的连接属性)。一般情况下,不将用户密码视为必需的连接属性,因为 Vertica 用户帐户可能不具有密码。如果缺少密码属性,ODBC 驱动程序仍会在未提供密码的情况下尝试连接到 Vertica。
您可以使用 PromptOnNoPassword DSN 参数来强制 ODBC 驱动程序将密码视为必需的连接属性。如果不想在 DSN 条目中存储密码,则此参数很有用。保存在 DSN 条目中的密码是不安全的,因为这些密码作为明文存储在 Windows 注册表中,从而对同一个系统上的其他用户可见。
还有另外两个因素决定 ODBC 驱动程序是否显示 Vertica“连接 (Connection)”对话框。这两个因素如下所示(按优先级顺序):
下表显示了 PromptOnNoPassword DSN 参数、SQLDriverConnect
函数的 DriverCompletion 参数以及“DSN 或连接字符串是否包含密码”如何交互以控制是否显示 Vertica“连接 (Connection)”对话框。
以下示例代码演示了如何在 C++ 中将 PromptOnNoPassword DSN 参数和系统 DSN 一起使用:
wstring connectString = L "DSN=VerticaDSN;PromptOnNoPassword=1;";
retcode = SQLDriverConnect(
hdbc,
0,
(SQLWCHAR * ) connectString.c_str(),
connectString.length(),
OutConnStr,
255, &
amp; OutConnStrLen,
SQL_DRIVER_COMPLETE);
无密码条目与空密码
连接字符串或 DSN 中不包含密码和包含空密码是两种不同情况。仅当连接字符串或 DSN 不具有 PWD 属性(存放用户密码)时,PromptOnNoPassword DSN 参数才起作用。如果具有该属性,则即使该属性为空,PromptOnNoPassword 也不会提示 Windows ODBC 驱动程序显示 Vertica“连接 (Connection)”对话框。
如果要使用 DSN 为连接提供属性,则此差别会造成混乱。在 Windows ODBC 管理器中为 DSN 连接输入密码并保存该 DSN 连接后,Windows 会将 PWD 属性添加到注册表中的 DSN 定义。如果以后删除该密码,PWD 属性会保留在 DSN 定义中(只会将其值设置为空字符串)。即使您只是使用 ODBC 管理器对话框上的“测试 (Test)”按钮测试 DSN,并且稍后在保存之前清除了该 DSN,也会创建 PWD 属性。
设置了密码后,从 DSN 定义中移除 PWD 属性的唯一方法是使用 Windows 注册表编辑器:
-
单击 Windows“开始”菜单,然后单击“运行”。
-
在“运行”对话框中,键入 regedit,然后单击“确定”。
-
在“注册表编辑器 (Registry Editor)”窗口中,单击“编辑 (Edit)”>“查找 (Find)”(或按 Ctrl+F)。
-
在“查找”窗口中,输入要删除其 PWD 属性的 DSN 的名称,然后单击“确定”。
-
如果查找操作无法定位到 ODBC.INI 文件夹下的某个文件夹,请单击“编辑 (Edit)”>“查找下一个 (Find Next)”(或按 F3),直至已突出显示与 DSN 的名称匹配的文件夹为止。
-
选择 PWD 项,然后按 Delete。
-
单击“是”以确认删除该值。
该 DSN 现已不具有 PWD 属性,并且可以在与 PromptOnNoPassword=true 和 DriverConnect=SQL_DRIVER_COMPLETE 一起使用时促使连接对话框显示出来。
3.1.10 - 设置 ODBC 会话的区域设置和编码
Vertica 提供以下方法来设置 ODBC 会话的区域设置和编码:
-
使用 DSN 指定所有已建立的连接的区域设置:
-
在 SQLDriverConnect()
函数的连接字符串中设置 Locale 连接参数。例如:
SQLDriverConnect(conn, NULL, (SQLCHAR*)"DSN=Vertica;Locale=en_GB@collation=binary", SQL_NTS, szConnOut, sizeof(szConnOut), &iAvailable, SQL_DRIVER_NOPROMPT)
-
使用 SQLSetConnectAttr()
设置编码和区域设置。通常,您应该始终使用此函数设置编码,而不是在 DSN 中设置它。
-
传递 SQL_ATTR_VERTICA_LOCALE
常量和 ICU 字符串作为属性值。例如:
=> SQLSetConnectAttr(hdlDbc, SQL_ATTR_VERTICA_LOCALE, (SQLCHAR*)newLocale,
SQL_NTS);
-
传递 SQL_ATTR_AP_WCHAR_TYPE
常量和编码作为属性值。例如:
=> rc = SQLSetConnectAttr (hdbc, SQL_ATTR_APP_WCHAR_TYPE, (void *)SQL_DD_CP_UTF16, SQL_IS_INTEGER);
注意
-
如果让客户端系统使用非 Unicode 区域设置(例如,在 Linux 平台上设置 LANG=C
)并对与 Vertica 的连接使用 Unicode 区域设置,则会生成诸如“(10170) 来自数据源的数据进行字符串数据右截断 (String data right truncation on data from data source)”等错误。如果从 Vertica 收到的数据不采用 UTF-8 格式,驱动程序将基于系统的区域设置来分配字符串内存,并且非 UTF-8 数据会触发超限。如果始终在客户端系统上使用 Unicode 区域设置,您可以避免这些错误。
如果在连接字符串或 DSN 中指定区域设置,调用连接函数将返回有关成功连接的 SQL_SUCCESS_WITH_INFO,并显示有关区域设置状态的消息。
-
ODBC 应用程序可以处于 ANSI 模式或 Unicode 模式:
向 Vertica 服务器传递数据时,ODBC 驱动程序会将 UCS-2 转换为 UTF-8,并会将 Vertica 服务器发来的数据从 UTF-8 转换为 UCS-2。
-
如果最终用户应用程序尚未处于 UCS-2 编码模式,则应用程序应负责将输入数据转换为 UCS-2,否则会出现意外结果。例如:
ODBC 应用程序应使用 SQLSetConnectAttr
设置正确的服务器会话区域设置(如果与数据库范围设置不同),以便在服务器上设置正确的排序规则和字符串函数行为。
以下示例代码演示了同时使用连接字符串和 SQLSetConnectAttr()
函数来设置区域设置。
// Standard i/o library
#include <stdio.h>
#include <stdlib.h>
// Only needed for Windows clients
// #include <windows.h>
// SQL include files that define data types and ODBC API
// functions
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
// Vertica-specific definitions. This include file is located as
// /opt/vertica/include on database hosts.
#include <verticaodbc.h>
int main()
{
SQLRETURN ret; // Stores return value from ODBC API calls
SQLHENV hdlEnv; // Handle for the SQL environment object
// Allocate an a SQL environment object
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hdlEnv);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not allocate a handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated an environment handle.\n");
}
// Set the ODBC version we are going to use to 3.
ret = SQLSetEnvAttr(hdlEnv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not set application version to ODBC 3.\n");
exit(EXIT_FAILURE);
} else {
printf("Set application version to ODBC 3.\n");
}
// Allocate a database handle.
SQLHDBC hdlDbc;
ret = SQLAllocHandle(SQL_HANDLE_DBC, hdlEnv, &hdlDbc);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not allocate database handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated Database handle.\n");
}
// Connect to the database using SQLDriverConnect
printf("Connecting to database.\n");
// Set the locale to English in Great Britain.
const char *connStr = "DSN=ExampleDB;locale=en_GB;"
"UID=dbadmin;PWD=password123";
ret = SQLDriverConnect(hdlDbc, NULL, (SQLCHAR*)connStr, SQL_NTS,
NULL, 0, NULL, SQL_DRIVER_NOPROMPT );
if(!SQL_SUCCEEDED(ret)) {
printf("Could not connect to database.\n");
exit(EXIT_FAILURE);
} else {
printf("Connected to database.\n");
}
// Get the Locale
char locale[256];
SQLGetConnectAttr(hdlDbc, SQL_ATTR_VERTICA_LOCALE, locale, sizeof(locale),
0);
printf("Locale is set to: %s\n", locale);
// Set the locale to a new value
const char* newLocale = "en_GB";
SQLSetConnectAttr(hdlDbc, SQL_ATTR_VERTICA_LOCALE, (SQLCHAR*)newLocale,
SQL_NTS);
// Get the Locale again
SQLGetConnectAttr(hdlDbc, SQL_ATTR_VERTICA_LOCALE, locale, sizeof(locale),
0);
printf("Locale is now set to: %s\n", locale);
// Set the encoding
SQLSetConnectAttr (hdbc, SQL_ATTR_APP_WCHAR_TYPE, (void *)SQL_DD_CP_UTF16,
SQL_IS_INTEGER);
// When done, free all of the handles to close them
// in an orderly fashion.
printf("Disconnecting and freeing handles.\n");
ret = SQLDisconnect( hdlDbc );
if(!SQL_SUCCEEDED(ret)) {
printf("Error disconnecting from database. Transaction still open?\n");
exit(EXIT_FAILURE);
}
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
exit(EXIT_SUCCESS);
}
3.1.11 - AUTOCOMMIT 事务和 ODBC 事务
AUTOCOMMIT 连接属性控制 INSERT、ALTER、COPY 和其他数据处理语句是否在完成后自动提交。默认情况下,AUTOCOMMIT 已启用,所有语句将在执行后提交。这通常不是最佳设置,因为效率较低。此外,您通常想要控制是否作为一个整体提交一组语句,而非逐个提交语句。例如,您可能只想在所有插入都成功时才提交一系列插入。如果 AUTOCOMMIT 已禁用,则当其中一个语句失败时,您可以回退事务。
如果 AUTOCOMMIT 已打开,则会在执行语句之后立即提交这些语句的结果。不能回退在 AUTOCOMMIT 模式下执行的语句。
例如,如果 AUTOCOMMIT 已打开,将自动提交以下单个 INSERT 语句:
ret = SQLExecDirect(hdlStmt, (SQLCHAR*)"INSERT INTO customers VALUES(500,"
"'Smith, Sam', '123-456-789');", SQL_NTS);
如果 AUTOCOMMIT 已关闭,您需要在执行语句之后手动提交事务。例如:
ret = SQLExecDirect(hdlStmt, (SQLCHAR*)"INSERT INTO customers VALUES(500,"
"'Smith, Sam', '123-456-789');", SQL_NTS);
// Other inserts and data manipulations
// Commit the statements(s)
ret = SQLEndTran(SQL_HANDLE_DBC, hdlDbc, SQL_COMMIT);
只有在调用 SQLEndTran()
时,才会提交插入的行。提交事务之前,您可以随时回退 INSERT 和其他语句。
注意
使用
SQLPrepare()
创建预定义的语句时,这些语句会缓存 AUTOCOMMIT 设置。稍后,更改连接的 AUTOCOMMIT 设置不会影响先前创建的预定义的语句的 AUTOCOMMIT 设置。有关详细信息,请参阅
使用预定义的语句。
以下示例演示了关闭 AUTOCOMMIT 以及执行插入和手动提交事务。
// Some standard headers
#include <stdio.h>
#include <stdlib.h>
// Only needed for Windows clients
// #include <windows.h>
// Standard ODBC headers
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
int main()
{
// Set up the ODBC environment
SQLRETURN ret;
SQLHENV hdlEnv;
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hdlEnv);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not allocate a handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated an environment handle.\n");
}
// Tell ODBC that the application uses ODBC 3.
ret = SQLSetEnvAttr(hdlEnv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not set application version to ODBC3.\n");
exit(EXIT_FAILURE);
} else {
printf("Set application to ODBC 3.\n");
}
// Allocate a database handle.
SQLHDBC hdlDbc;
ret = SQLAllocHandle(SQL_HANDLE_DBC, hdlEnv, &hdlDbc);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not allocate database handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated Database handle.\n");
}
// Connect to the database
printf("Connecting to database.\n");
const char *dsnName = "ExampleDB";
const char* userID = "dbadmin";
const char* passwd = "password123";
ret = SQLConnect(hdlDbc, (SQLCHAR*)dsnName,
SQL_NTS,(SQLCHAR*)userID,SQL_NTS,
(SQLCHAR*)passwd, SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not connect to database.\n");
exit(EXIT_FAILURE);
} else {
printf("Connected to database.\n");
}
// Get the AUTOCOMMIT state
SQLINTEGER autoCommitState;
SQLGetConnectAttr(hdlDbc, SQL_ATTR_AUTOCOMMIT, &autoCommitState, 0, NULL);
printf("Autocommit is set to: %d\n", autoCommitState);
// Disable AUTOCOMMIT
printf("Disabling autocommit.\n");
ret = SQLSetConnectAttr(hdlDbc, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF,
SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not disable autocommit.\n");
exit(EXIT_FAILURE);
}
// Get the AUTOCOMMIT state again
SQLGetConnectAttr(hdlDbc, SQL_ATTR_AUTOCOMMIT, &autoCommitState, 0, NULL);
printf("Autocommit is set to: %d\n", autoCommitState);
// Set up a statement handle
SQLHSTMT hdlStmt;
SQLAllocHandle(SQL_HANDLE_STMT, hdlDbc, &hdlStmt);
// Create a table to hold the data
SQLExecDirect(hdlStmt, (SQLCHAR*)"DROP TABLE IF EXISTS customers",
SQL_NTS);
SQLExecDirect(hdlStmt, (SQLCHAR*)"CREATE TABLE customers "
"(CustID int, CustName varchar(100), Phone_Number char(15));",
SQL_NTS);
// Insert a single row.
ret = SQLExecDirect(hdlStmt, (SQLCHAR*)"INSERT INTO customers VALUES(500,"
"'Smith, Sam', '123-456-789');", SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not perform single insert.\n");
} else {
printf("Performed single insert.\n");
}
// Need to commit the transaction before closing, since autocommit is
// disabled. Otherwise SQLDisconnect returns an error.
printf("Committing transaction.\n");
ret = SQLEndTran(SQL_HANDLE_DBC, hdlDbc, SQL_COMMIT);
if(!SQL_SUCCEEDED(ret)) {
printf("Error committing transaction.\n");
exit(EXIT_FAILURE);
}
// Clean up
printf("Free handles.\n");
ret = SQLDisconnect(hdlDbc);
if(!SQL_SUCCEEDED(ret)) {
printf("Error disconnecting from database. Transaction still open?\n");
exit(EXIT_FAILURE);
}
SQLFreeHandle(SQL_HANDLE_STMT, hdlStmt);
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
exit(EXIT_SUCCESS);
}
运行以上代码后,将生成以下输出:
Allocated an environment handle.
Set application to ODBC 3.
Allocated Database handle.
Connecting to database.
Connected to database.
Autocommit is set to: 1
Disabling autocommit.
Autocommit is set to: 0
Performed single insert.
Committing transaction.
Free handles.
注意
您也可以在 ODBC 连接字符串中禁用 AUTOCOMMIT。有关详细信息,请参阅
设置 DSN 连接属性。
3.1.12 - 检索数据
若要通过 ODBC 检索数据,应执行将返回结果集的查询(例如 SELECT),然后使用以下两种方法之一检索结果:
使用以上两种方法时,应在结果集中循环直至到达结尾(由 SQL_NO_DATA 返回状态指示)或遇到错误为止。
注意
Vertica 对每个连接仅支持一个游标。如果尝试对每个连接使用多个光标,将导致发生错误。例如,如果在其他语句的结果集处于打开状态时执行某个语句,您将收到错误。
以下代码示例演示了如何通过以下过程从 Vertica 检索数据:
-
连接到数据库。
-
执行将返回所有表的 ID 和名称的 SELECT 语句。
-
将两个变量绑定到结果集中的两个列。
-
在结果集中循环,并输出 ID 和名称值。
// Demonstrate running a query and getting results by querying the tables
// system table for a list of all tables in the current schema.
// Some standard headers
#include <stdlib.h>
#include <sstream>
#include <iostream>
#include <assert.h>
// Standard ODBC headers
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
// Use std namespace to make output easier
using namespace std;
// Helper function to print SQL error messages.
template <typename HandleT>
void reportError(int handleTypeEnum, HandleT hdl)
{
// Get the status records.
SQLSMALLINT i, MsgLen;
SQLRETURN ret2;
SQLCHAR SqlState[6], Msg[SQL_MAX_MESSAGE_LENGTH];
SQLINTEGER NativeError;
i = 1;
cout << endl;
while ((ret2 = SQLGetDiagRec(handleTypeEnum, hdl, i, SqlState, &NativeError,
Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA) {
cout << "error record #" << i++ << endl;
cout << "sqlstate: " << SqlState << endl;
cout << "detailed msg: " << Msg << endl;
cout << "native error code: " << NativeError << endl;
}
}
typedef struct {
SQLHENV hdlEnv;
SQLHDBC hdlDbc;
} DBConnection;
void connect(DBConnection *pConnInfo)
{
// Set up the ODBC environment
SQLRETURN ret;
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &pConnInfo->hdlEnv);
assert(SQL_SUCCEEDED(ret));
// Tell ODBC that the application uses ODBC 3.
ret = SQLSetEnvAttr(pConnInfo->hdlEnv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER)SQL_OV_ODBC3, SQL_IS_UINTEGER);
assert(SQL_SUCCEEDED(ret));
// Allocate a database handle.
ret = SQLAllocHandle(SQL_HANDLE_DBC, pConnInfo->hdlEnv, &pConnInfo->hdlDbc);
assert(SQL_SUCCEEDED(ret));
// Connect to the database
cout << "Connecting to database." << endl;
const char* dsnName = "ExampleDB";
const char* userID = "dbadmin";
const char* passwd = "password123";
ret = SQLConnect(pConnInfo->hdlDbc, (SQLCHAR*)dsnName,
SQL_NTS, (SQLCHAR*)userID, SQL_NTS,
(SQLCHAR*)passwd, SQL_NTS);
if (!SQL_SUCCEEDED(ret)) {
cout << "Could not connect to database" << endl;
reportError<SQLHDBC>(SQL_HANDLE_DBC, pConnInfo->hdlDbc);
exit(EXIT_FAILURE);
}
else {
cout << "Connected to database." << endl;
}
}
void disconnect(DBConnection *pConnInfo)
{
SQLRETURN ret;
// Clean up by shutting down the connection
cout << "Free handles." << endl;
ret = SQLDisconnect(pConnInfo->hdlDbc);
if (!SQL_SUCCEEDED(ret)) {
cout << "Error disconnecting. Transaction still open?" << endl;
exit(EXIT_FAILURE);
}
SQLFreeHandle(SQL_HANDLE_DBC, pConnInfo->hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, pConnInfo->hdlEnv);
}
void executeQuery(SQLHDBC hdlDbc, SQLCHAR* pQuery)
{
SQLRETURN ret;
// Set up a statement handle
SQLHSTMT hdlStmt;
SQLAllocHandle(SQL_HANDLE_STMT, hdlDbc, &hdlStmt);
assert(SQL_SUCCEEDED(ret));
// Execute a query to get the names and IDs of all tables in the schema
// search p[ath (usually public).
ret = SQLExecDirect(hdlStmt, pQuery, SQL_NTS);
if (!SQL_SUCCEEDED(ret)) {
// Report error an go no further if statement failed.
cout << "Error executing statement." << endl;
reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);
exit(EXIT_FAILURE);
}
else {
// Query succeeded, so bind two variables to the two colums in the
// result set,
cout << "Fetching results..." << endl;
SQLBIGINT table_id; // Holds the ID of the table.
SQLTCHAR table_name[256]; // buffer to hold name of table
ret = SQLBindCol(hdlStmt, 1, SQL_C_SBIGINT, (SQLPOINTER)&table_id,
sizeof(table_id), NULL);
ret = SQLBindCol(hdlStmt, 2, SQL_C_TCHAR, (SQLPOINTER)table_name,
sizeof(table_name), NULL);
// Loop through the results,
while (SQL_SUCCEEDED(ret = SQLFetchScroll(hdlStmt, SQL_FETCH_NEXT, 1))) {
// Print the bound variables, which now contain the values from the
// fetched row.
cout << table_id << " | " << table_name << endl;
}
// See if loop exited for reasons other than running out of data
if (ret != SQL_NO_DATA) {
// Exited for a reason other than no more data... report the error.
reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);
}
}
SQLFreeHandle(SQL_HANDLE_STMT, hdlStmt);
}
int main()
{
DBConnection conn;
connect(&conn);
executeQuery(conn.hdlDbc,
(SQLCHAR*)"SELECT table_id, table_name FROM tables ORDER BY table_name");
executeQuery(conn.hdlDbc,
(SQLCHAR*)"SELECT table_id, table_name FROM tables ORDER BY table_id");
disconnect(&conn);
exit(EXIT_SUCCESS);
}
在 vmart 数据库中运行以上示例代码后,将生成类似于以下内容的输出:
Connecting to database.
Connected to database.
Fetching results...
45035996273970908 | call_center_dimension
45035996273970836 | customer_dimension
45035996273972958 | customers
45035996273970848 | date_dimension
45035996273970856 | employee_dimension
45035996273970868 | inventory_fact
45035996273970904 | online_page_dimension
45035996273970912 | online_sales_fact
45035996273970840 | product_dimension
45035996273970844 | promotion_dimension
45035996273970860 | shipping_dimension
45035996273970876 | store_dimension
45035996273970894 | store_orders_fact
45035996273970880 | store_sales_fact
45035996273972806 | t
45035996273970852 | vendor_dimension
45035996273970864 | warehouse_dimension
Fetching results...
45035996273970836 | customer_dimension
45035996273970840 | product_dimension
45035996273970844 | promotion_dimension
45035996273970848 | date_dimension
45035996273970852 | vendor_dimension
45035996273970856 | employee_dimension
45035996273970860 | shipping_dimension
45035996273970864 | warehouse_dimension
45035996273970868 | inventory_fact
45035996273970876 | store_dimension
45035996273970880 | store_sales_fact
45035996273970894 | store_orders_fact
45035996273970904 | online_page_dimension
45035996273970908 | call_center_dimension
45035996273970912 | online_sales_fact
45035996273972806 | t
45035996273972958 | customers
Free handles.
3.1.13 - 加载数据
许多客户端应用程序的主要任务是将数据加载到 Vertica 数据库。有多种不同方法可用于通过 ODBC 插入数据,本节中的主题介绍这些方法。
3.1.13.1 - 使用单行插入
将数据加载到 Vertica 的最简单方法是使用 SQLExecuteDirect 函数运行 INSERT SQL 语句。但此方法仅能插入单个数据行。
ret = SQLExecDirect(hstmt, (SQLTCHAR*)"INSERT into Customers values"
"(1,'abcda','efgh','1')", SQL_NTS);
3.1.13.2 - 使用预定义的语句
Vertica 支持将服务器端预定义的语句与 ODBC 和 JDBC 结合使用。使用预定义的语句,您只需定义一个语句一次,然后可以使用不同的参数多次运行该语句。要执行的语句包含占位符而非参数。执行语句时,应为每个占位符提供值。
在以下示例查询中,占位符用问号 (?) 表示:
SELECT * FROM public.inventory_fact WHERE product_key = ?
服务器端预定义的语句用于以下用途:
以下示例演示了使用预定义的语句执行单一插入。
// Some standard headers
#include <stdio.h>
#include <stdlib.h>
// Only needed for Windows clients
// #include <windows.h>
// Standard ODBC headers
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
// Some constants for the size of the data to be inserted.
#define CUST_NAME_LEN 50
#define PHONE_NUM_LEN 15
#define NUM_ENTRIES 4
int main()
{
// Set up the ODBC environment
SQLRETURN ret;
SQLHENV hdlEnv;
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hdlEnv);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not allocate a handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated an environment handle.\n");
}
// Tell ODBC that the application uses ODBC 3.
ret = SQLSetEnvAttr(hdlEnv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not set application version to ODBC3.\n");
exit(EXIT_FAILURE);
} else {
printf("Set application to ODBC 3.\n");
}
// Allocate a database handle.
SQLHDBC hdlDbc;
ret = SQLAllocHandle(SQL_HANDLE_DBC, hdlEnv, &hdlDbc);
// Connect to the database
printf("Connecting to database.\n");
const char *dsnName = "ExampleDB";
const char* userID = "dbadmin";
const char* passwd = "password123";
ret = SQLConnect(hdlDbc, (SQLCHAR*)dsnName,
SQL_NTS,(SQLCHAR*)userID,SQL_NTS,
(SQLCHAR*)passwd, SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not connect to database.\n");
exit(EXIT_FAILURE);
} else {
printf("Connected to database.\n");
}
// Disable AUTOCOMMIT
printf("Disabling autocommit.\n");
ret = SQLSetConnectAttr(hdlDbc, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF,
SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not disable autocommit.\n");
exit(EXIT_FAILURE);
}
// Set up a statement handle
SQLHSTMT hdlStmt;
SQLAllocHandle(SQL_HANDLE_STMT, hdlDbc, &hdlStmt);
SQLExecDirect(hdlStmt, (SQLCHAR*)"DROP TABLE IF EXISTS customers",
SQL_NTS);
SQLExecDirect(hdlStmt, (SQLCHAR*)"CREATE TABLE customers "
"(CustID int, CustName varchar(100), Phone_Number char(15));",
SQL_NTS);
// Set up a bunch of variables to be bound to the statement
// parameters.
// Create the prepared statement. This will insert data into the
// table we created above.
printf("Creating prepared statement\n");
ret = SQLPrepare (hdlStmt, (SQLTCHAR*)"INSERT INTO customers (CustID, "
"CustName, Phone_Number) VALUES(?,?,?)", SQL_NTS) ;
if(!SQL_SUCCEEDED(ret)) {
printf("Could not create prepared statement\n");
SQLFreeHandle(SQL_HANDLE_STMT, hdlStmt);
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
exit(EXIT_FAILURE);
} else {
printf("Created prepared statement.\n");
}
SQLINTEGER custID = 1234;
SQLCHAR custName[100] = "Fein, Fredrick";
SQLVARCHAR phoneNum[15] = "555-123-6789";
SQLLEN strFieldLen = SQL_NTS;
SQLLEN custIDLen = 0;
// Bind the data arrays to the parameters in the prepared SQL
// statement
ret = SQLBindParameter(hdlStmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,
0, 0, &custID, 0 , &custIDLen);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not bind custID array\n");
SQLFreeHandle(SQL_HANDLE_STMT, hdlStmt);
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
exit(EXIT_FAILURE);
} else {
printf("Bound custID to prepared statement\n");
}
// Bind CustNames
SQLBindParameter(hdlStmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,
50, 0, (SQLPOINTER)custName, 0, &strFieldLen);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not bind custNames\n");
SQLFreeHandle(SQL_HANDLE_STMT, hdlStmt);
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
exit(EXIT_FAILURE);
} else {
printf("Bound custName to prepared statement\n");
}
// Bind phoneNums
SQLBindParameter(hdlStmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
15, 0, (SQLPOINTER)phoneNum, 0, &strFieldLen);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not bind phoneNums\n");
SQLFreeHandle(SQL_HANDLE_STMT, hdlStmt);
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
exit(EXIT_FAILURE);
} else {
printf("Bound phoneNum to prepared statement\n");
}
// Execute the prepared statement.
printf("Running prepared statement...");
ret = SQLExecute(hdlStmt);
if(!SQL_SUCCEEDED(ret)) {
printf("not successful!\n");
} else {
printf("successful.\n");
}
// Done with batches, commit the transaction
printf("Committing transaction\n");
ret = SQLEndTran(SQL_HANDLE_DBC, hdlDbc, SQL_COMMIT);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not commit transaction\n");
} else {
printf("Committed transaction\n");
}
// Clean up
printf("Free handles.\n");
ret = SQLDisconnect( hdlDbc );
if(!SQL_SUCCEEDED(ret)) {
printf("Error disconnecting. Transaction still open?\n");
exit(EXIT_FAILURE);
}
SQLFreeHandle(SQL_HANDLE_STMT, hdlStmt);
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
exit(EXIT_SUCCESS);
}
3.1.13.3 - 使用批量插入
可以使用批量插入将数据区块插入到数据库中。通过将数据拆分为多个批,您可以在加载每个批后收到有关任何拒绝的行的信息,从而监控加载进度。若要通过 ODBC 执行批量加载,通常可以将预定义的语句与绑定到数组(包含要加载的数据)结合使用。对于每个批,应将新的数据集加载到数组中,然后执行预定义的语句。
执行批量加载时,Vertica 使用 COPY 语句加载数据。所加载的其他每个批使用同一个 COPY 语句。该语句将保持打开,直至您执行以下操作为止:结束语句,关闭语句的光标或执行非 INSERT 语句。
将单个 COPY 语句用于多个批可以通过以下途径提高批量加载效率:
-
减少插入各个批的开销
-
将各个批合并为较大的 ROS 容器
注意
如果数据库连接已启用 AUTOCOMMIT,则事务会在执行每个批量插入语句之后自动提交,从而关闭 COPY 语句。保持启用 AUTOCOMMIT 会降低批量加载的效率,并且会增加数据库中的开销,因为所有较小的加载会合并到一起。
虽然 Vertica 使用单个 COPY 语句插入一个事务中的多个批,但您可以在加载每个批之后查找由于行格式无效或存在数据类型问题而被拒绝的行(如果有)。有关详细信息,请参阅跟踪加载状态 (ODBC)。
注意
虽然您可以在批量加载事务期间查找拒绝的行,但只会在 COPY 语句结束后报告其他类型的错误(例如,磁盘空间不足或节点关闭,此类错误会导致数据库不安全)。
由于批量加载共享一个 COPY 语句,因此一个批中的错误会导致回退同一个事务中较早的批。
批量插入步骤
应用程序执行 ODBC 批量插入所需完成的步骤如下:
-
连接到数据库。
-
禁用连接的自动提交。
-
创建可插入要加载的数据的预定义的语句。
-
将该预定义的语句的参数绑定到包含要加载的数据的数组。
-
使用各个批的数据填充数组。
-
执行该预定义的语句。
-
(可选)检查批量加载的结果,以查找拒绝的行。
-
重复以上三个步骤,直至已加载所有要加载的数据为止。
-
提交事务。
-
(可选)检查整个批处理事务的结果。
以下示例代码演示了以上步骤的简化版本。
// Some standard headers
#include <stdio.h>
#include <stdlib.h>
// Only needed for Windows clients
// #include <windows.h>
// Standard ODBC headers
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
int main()
{
// Number of data rows to insert
const int NUM_ENTRIES = 4;
// Set up the ODBC environment
SQLRETURN ret;
SQLHENV hdlEnv;
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hdlEnv);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not allocate a handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated an environment handle.\n");
}
// Tell ODBC that the application uses ODBC 3.
ret = SQLSetEnvAttr(hdlEnv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not set application version to ODBC3.\n");
exit(EXIT_FAILURE);
} else {
printf("Set application to ODBC 3.\n");
}
// Allocate a database handle.
SQLHDBC hdlDbc;
ret = SQLAllocHandle(SQL_HANDLE_DBC, hdlEnv, &hdlDbc);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not allocate database handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated Database handle.\n");
}
// Connect to the database
printf("Connecting to database.\n");
const char *dsnName = "ExampleDB";
const char* userID = "dbadmin";
const char* passwd = "password123";
ret = SQLConnect(hdlDbc, (SQLCHAR*)dsnName,
SQL_NTS,(SQLCHAR*)userID,SQL_NTS,
(SQLCHAR*)passwd, SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not connect to database.\n");
exit(EXIT_FAILURE);
} else {
printf("Connected to database.\n");
}
// Disable AUTOCOMMIT
printf("Disabling autocommit.\n");
ret = SQLSetConnectAttr(hdlDbc, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF,
SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not disable autocommit.\n");
exit(EXIT_FAILURE);
}
// Set up a statement handle
SQLHSTMT hdlStmt;
SQLAllocHandle(SQL_HANDLE_STMT, hdlDbc, &hdlStmt);
// Create a table to hold the data
SQLExecDirect(hdlStmt, (SQLCHAR*)"DROP TABLE IF EXISTS customers",
SQL_NTS);
SQLExecDirect(hdlStmt, (SQLCHAR*)"CREATE TABLE customers "
"(CustID int, CustName varchar(100), Phone_Number char(15));",
SQL_NTS);
// Create the prepared statement. This will insert data into the
// table we created above.
printf("Creating prepared statement\n");
ret = SQLPrepare (hdlStmt, (SQLTCHAR*)"INSERT INTO customers (CustID, "
"CustName, Phone_Number) VALUES(?,?,?)", SQL_NTS) ;
if(!SQL_SUCCEEDED(ret)) {
printf("Could not create prepared statement\n");
exit(EXIT_FAILURE);
} else {
printf("Created prepared statement.\n");
}
// This is the data to be inserted into the database.
SQLCHAR custNames[][50] = { "Allen, Anna", "Brown, Bill", "Chu, Cindy",
"Dodd, Don" };
SQLINTEGER custIDs[] = { 100, 101, 102, 103};
SQLCHAR phoneNums[][15] = {"1-617-555-1234", "1-781-555-1212",
"1-508-555-4321", "1-617-555-4444"};
// Bind the data arrays to the parameters in the prepared SQL
// statement. First is the custID.
ret = SQLBindParameter(hdlStmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,
0, 0, (SQLPOINTER)custIDs, sizeof(SQLINTEGER) , NULL);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not bind custID array\n");
exit(EXIT_FAILURE);
} else {
printf("Bound CustIDs array to prepared statement\n");
}
// Bind CustNames
ret = SQLBindParameter(hdlStmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,
50, 0, (SQLPOINTER)custNames, 50, NULL);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not bind custNames\n");
exit(EXIT_FAILURE);
} else {
printf("Bound CustNames array to prepared statement\n");
}
// Bind phoneNums
ret = SQLBindParameter(hdlStmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
15, 0, (SQLPOINTER)phoneNums, 15, NULL);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not bind phoneNums\n");
exit(EXIT_FAILURE);
} else {
printf("Bound phoneNums array to prepared statement\n");
}
// Tell the ODBC driver how many rows we have in the
// array.
ret = SQLSetStmtAttr( hdlStmt, SQL_ATTR_PARAMSET_SIZE,
(SQLPOINTER)NUM_ENTRIES, 0 );
if(!SQL_SUCCEEDED(ret)) {
printf("Could not bind set parameter size\n");
exit(EXIT_FAILURE);
} else {
printf("Bound phoneNums array to prepared statement\n");
}
// Add multiple batches to the database. This just adds the same
// batch of data four times for simplicity's sake. Each call adds
// the 4 rows into the database.
for (int batchLoop=1; batchLoop<=5; batchLoop++) {
// Execute the prepared statement, loading all of the data
// in the arrays.
printf("Adding Batch #%d...", batchLoop);
ret = SQLExecute(hdlStmt);
if(!SQL_SUCCEEDED(ret)) {
printf("not successful!\n");
} else {
printf("successful.\n");
}
}
// Done with batches, commit the transaction
printf("Committing transaction\n");
ret = SQLEndTran(SQL_HANDLE_DBC, hdlDbc, SQL_COMMIT);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not commit transaction\n");
} else {
printf("Committed transaction\n");
}
// Clean up
printf("Free handles.\n");
ret = SQLDisconnect( hdlDbc );
if(!SQL_SUCCEEDED(ret)) {
printf("Error disconnecting. Transaction still open?\n");
exit(EXIT_FAILURE);
}
SQLFreeHandle(SQL_HANDLE_STMT, hdlStmt);
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
exit(EXIT_SUCCESS);
}
运行以上代码后的结果如下所示。
Allocated an environment handle.
Set application to ODBC 3.
Allocated Database handle.
Connecting to database.
Connected to database.
Creating prepared statement
Created prepared statement.
Bound CustIDs array to prepared statement
Bound CustNames array to prepared statement
Bound phoneNums array to prepared statement
Adding Batch #1...successful.
Adding Batch #2...successful.
Adding Batch #3...successful.
Adding Batch #4...successful.
Adding Batch #5...successful.
Committing transaction
Committed transaction
Free handles.
结果表如下所示:
=> SELECT * FROM customers;
CustID | CustName | Phone_Number
--------+-------------+-----------------
100 | Allen, Anna | 1-617-555-1234
101 | Brown, Bill | 1-781-555-1212
102 | Chu, Cindy | 1-508-555-4321
103 | Dodd, Don | 1-617-555-4444
100 | Allen, Anna | 1-617-555-1234
101 | Brown, Bill | 1-781-555-1212
102 | Chu, Cindy | 1-508-555-4321
103 | Dodd, Don | 1-617-555-4444
100 | Allen, Anna | 1-617-555-1234
101 | Brown, Bill | 1-781-555-1212
102 | Chu, Cindy | 1-508-555-4321
103 | Dodd, Don | 1-617-555-4444
100 | Allen, Anna | 1-617-555-1234
101 | Brown, Bill | 1-781-555-1212
102 | Chu, Cindy | 1-508-555-4321
103 | Dodd, Don | 1-617-555-4444
100 | Allen, Anna | 1-617-555-1234
101 | Brown, Bill | 1-781-555-1212
102 | Chu, Cindy | 1-508-555-4321
103 | Dodd, Don | 1-617-555-4444
(20 rows)
注意
与 SQL_C_NUMERIC 数据类型绑定的输入参数使用默认数值精度 (37) 和默认小数位数 (0),而不使用由 SQL_NUMERIC_STRUCT 输入值设置的精度和小数位数。此行为遵守 ODBC 标准。如果不想使用默认精度和小数位数,可以使用 SQLSetDescField()
或 SQLSetDescRec()
在语句属性中更改精度和小数位数。
3.1.13.3.1 - 跟踪加载状态 (ODBC)
加载一批数据之后,客户端应用程序可以获取已处理的行数并确定每个是被接受还是被拒绝。
确定接受的行的数量
若要获取某个批所处理的行数,请将名为 SQL_ATTR_PARAMS_PROCESSED_PTR 且指向用于接收行数的变量的属性添加到语句对象中:
SQLULEN rowsProcessed;
SQLSetStmtAttr(hdlStmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &rowsProcessed, 0);
当应用程序调用 SQLExecute()
以插入该批时,Vertica ODBC 驱动程序会将已处理的行数(不一定等于已成功插入的行数)保存到您在 SQL_ATTR_PARAMS_PROCESSED_PTR 语句属性中指定的变量。
查找接受的行和拒绝的行
应用程序还可以设置名为 SQL_ATTR_PARAM_STATUS_PTR 且指向一个数组(ODBC 驱动程序可以将插入每个行的结果存储在此数组中)的语句属性:
SQLUSMALLINT rowResults[ NUM_ENTRIES ];
SQLSetStmtAttr(hdlStmt, SQL_ATTR_PARAM_STATUS_PTR, rowResults, 0);
该数组至少必须与每个批中要插入的行数一样大。
当应用程序调用 SQLExecute
以插入一个批时,ODBC 驱动程序会使用值填充该数组,这些值指示每个行已成功插入(SQL_PARAM_SUCCESS 或 SQL_PARAM_SUCCESS_WITH_INFO)还是遇到了错误 (SQL_PARAM_ERROR)。
以下示例扩展了使用批量插入中所示的示例,以便同时报告已处理的行数和插入的每个行的状态。
// Some standard headers
#include <stdio.h>
#include <stdlib.h>
// Only needed for Windows clients
// #include <windows.h>
// Standard ODBC headers
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
// Helper function to print SQL error messages.
template <typename HandleT>
void reportError(int handleTypeEnum, HandleT hdl)
{
// Get the status records.
SQLSMALLINT i, MsgLen;
SQLRETURN ret2;
SQLCHAR SqlState[6], Msg[SQL_MAX_MESSAGE_LENGTH];
SQLINTEGER NativeError;
i = 1;
printf("\n");
while ((ret2 = SQLGetDiagRec(handleTypeEnum, hdl, i, SqlState, &NativeError,
Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA) {
printf("error record %d\n", i);
printf("sqlstate: %s\n", SqlState);
printf("detailed msg: %s\n", Msg);
printf("native error code: %d\n\n", NativeError);
i++;
}
}
int main()
{
// Number of data rows to insert
const int NUM_ENTRIES = 4;
SQLRETURN ret;
SQLHENV hdlEnv;
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hdlEnv);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not allocate a handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated an environment handle.\n");
}
ret = SQLSetEnvAttr(hdlEnv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not set application version to ODBC3.\n");
exit(EXIT_FAILURE);
} else {
printf("Set application to ODBC 3.\n");
}
SQLHDBC hdlDbc;
ret = SQLAllocHandle(SQL_HANDLE_DBC, hdlEnv, &hdlDbc);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not allocate database handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated Database handle.\n");
}
// Connect to the database
printf("Connecting to database.\n");
const char *dsnName = "ExampleDB";
const char* userID = "dbadmin";
const char* passwd = "password123";
ret = SQLConnect(hdlDbc, (SQLCHAR*)dsnName,
SQL_NTS,(SQLCHAR*)userID,SQL_NTS,
(SQLCHAR*)passwd, SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not connect to database.\n");
reportError<SQLHDBC>(SQL_HANDLE_DBC, hdlDbc);
exit(EXIT_FAILURE);
} else {
printf("Connected to database.\n");
}
// Set up a statement handle
SQLHSTMT hdlStmt;
SQLAllocHandle(SQL_HANDLE_STMT, hdlDbc, &hdlStmt);
SQLExecDirect(hdlStmt, (SQLCHAR*)"DROP TABLE IF EXISTS customers",
SQL_NTS);
// Create a table into which we can store data
printf("Creating table.\n");
ret = SQLExecDirect(hdlStmt, (SQLCHAR*)"CREATE TABLE customers "
"(CustID int, CustName varchar(50), Phone_Number char(15));",
SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
reportError<SQLHDBC>( SQL_HANDLE_STMT, hdlStmt );
exit(EXIT_FAILURE);
} else {
printf("Created table.\n");
}
// Create the prepared statement. This will insert data into the
// table we created above.
printf("Creating prepared statement\n");
ret = SQLPrepare (hdlStmt, (SQLTCHAR*)"INSERT INTO customers (CustID, "
"CustName, Phone_Number) VALUES(?,?,?)", SQL_NTS) ;
if(!SQL_SUCCEEDED(ret)) {
reportError<SQLHDBC>( SQL_HANDLE_STMT, hdlStmt );
exit(EXIT_FAILURE);
} else {
printf("Created prepared statement.\n");
}
// This is the data to be inserted into the database.
char custNames[][50] = { "Allen, Anna", "Brown, Bill", "Chu, Cindy",
"Dodd, Don" };
SQLINTEGER custIDs[] = { 100, 101, 102, 103};
char phoneNums[][15] = {"1-617-555-1234", "1-781-555-1212",
"1-508-555-4321", "1-617-555-4444"};
// Bind the data arrays to the parameters in the prepared SQL
// statement
ret = SQLBindParameter(hdlStmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,
0, 0, (SQLPOINTER)custIDs, sizeof(SQLINTEGER) , NULL);
if(!SQL_SUCCEEDED(ret)) {
reportError<SQLHDBC>( SQL_HANDLE_STMT, hdlStmt );
exit(EXIT_FAILURE);
} else {
printf("Bound CustIDs array to prepared statement\n");
}
// Bind CustNames
SQLBindParameter(hdlStmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,
50, 0, (SQLPOINTER)custNames, 50, NULL);
if(!SQL_SUCCEEDED(ret)) {
reportError<SQLHDBC>( SQL_HANDLE_STMT, hdlStmt );
exit(EXIT_FAILURE);
} else {
printf("Bound CustNames array to prepared statement\n");
}
// Bind phoneNums
SQLBindParameter(hdlStmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
15, 0, (SQLPOINTER)phoneNums, 15, NULL);
if(!SQL_SUCCEEDED(ret)) {
reportError<SQLHDBC>( SQL_HANDLE_STMT, hdlStmt );
exit(EXIT_FAILURE);
} else {
printf("Bound phoneNums array to prepared statement\n");
}
// Set up a variable to recieve number of parameters processed.
SQLULEN rowsProcessed;
// Set a statement attribute to point to the variable
SQLSetStmtAttr(hdlStmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &rowsProcessed, 0);
// Set up an array to hold the result of each row insert
SQLUSMALLINT rowResults[ NUM_ENTRIES ];
// Set a statement attribute to point to the array
SQLSetStmtAttr(hdlStmt, SQL_ATTR_PARAM_STATUS_PTR, rowResults, 0);
// Tell the ODBC driver how many rows we have in the
// array.
SQLSetStmtAttr(hdlStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)NUM_ENTRIES, 0);
// Add multiple batches to the database. This just adds the same
// batch of data over and over again for simplicity's sake.
for (int batchLoop=1; batchLoop<=5; batchLoop++) {
// Execute the prepared statement, loading all of the data
// in the arrays.
printf("Adding Batch #%d...", batchLoop);
ret = SQLExecute(hdlStmt);
if(!SQL_SUCCEEDED(ret)) {
reportError<SQLHDBC>( SQL_HANDLE_STMT, hdlStmt );
exit(EXIT_FAILURE);
}
// Number of rows processed is in rowsProcessed
printf("Params processed: %d\n", rowsProcessed);
printf("Results of inserting each row:\n");
int i;
for (i = 0; i<NUM_ENTRIES; i++) {
SQLUSMALLINT result = rowResults[i];
switch(rowResults[i]) {
case SQL_PARAM_SUCCESS:
case SQL_PARAM_SUCCESS_WITH_INFO:
printf(" Row %d inserted successsfully\n", i+1);
break;
case SQL_PARAM_ERROR:
printf(" Row %d was not inserted due to an error.", i+1);
break;
default:
printf(" Row %d had some issue with it: %d\n", i+1, result);
}
}
}
// Done with batches, commit the transaction
printf("Commit Transaction\n");
ret = SQLEndTran(SQL_HANDLE_DBC, hdlDbc, SQL_COMMIT);
if(!SQL_SUCCEEDED(ret)) {
reportError<SQLHDBC>( SQL_HANDLE_STMT, hdlStmt );
}
// Clean up
printf("Free handles.\n");
ret = SQLDisconnect( hdlDbc );
if(!SQL_SUCCEEDED(ret)) {
printf("Error disconnecting. Transaction still open?\n");
exit(EXIT_FAILURE);
}
SQLFreeHandle(SQL_HANDLE_STMT, hdlStmt);
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
exit(EXIT_SUCCESS);
}
运行以上示例代码后,将生成以下输出:
Allocated an environment handle.Set application to ODBC 3.
Allocated Database handle.
Connecting to database.
Connected to database.
Creating table.
Created table.
Creating prepared statement
Created prepared statement.
Bound CustIDs array to prepared statement
Bound CustNames array to prepared statement
Bound phoneNums array to prepared statement
Adding Batch #1...Params processed: 4
Results of inserting each row:
Row 1 inserted successfully
Row 2 inserted successfully
Row 3 inserted successfully
Row 4 inserted successfully
Adding Batch #2...Params processed: 4
Results of inserting each row:
Row 1 inserted successfully
Row 2 inserted successfully
Row 3 inserted successfully
Row 4 inserted successfully
Adding Batch #3...Params processed: 4
Results of inserting each row:
Row 1 inserted successfully
Row 2 inserted successfully
Row 3 inserted successfully
Row 4 inserted successfully
Adding Batch #4...Params processed: 4
Results of inserting each row:
Row 1 inserted successfully
Row 2 inserted successfully
Row 3 inserted successfully
Row 4 inserted successfully
Adding Batch #5...Params processed: 4
Results of inserting each row:
Row 1 inserted successfully
Row 2 inserted successfully
Row 3 inserted successfully
Row 4 inserted successfully
Commit Transaction
Free handles.
3.1.13.3.2 - 批量加载过程中的错误处理
加载各个批时,您可以查找有关已接受的行数和已拒绝的行的信息(有关详细信息,请参阅跟踪加载状态 (ODBC))。插入各个批时,不会发生其他错误(例如磁盘空间错误)。此行为是由单个 COPY 语句对多个连续批执行加载导致的。使用单个 COPY 语句可使批量加载过程更快执行。只有 COPY 语句关闭时,才会提交批量数据,而且 Vertica 会报告其他类型错误。
批量加载应用程序应在 COPY 语句关闭时检查错误。一般情况下,可以通过调用 SQLEndTran()
函数以结束事务来强制 COPY 语句关闭。您还可以通过以下方法强制 COPY 语句关闭:使用 SQLCloseCursor()
函数关闭光标,或者在加载中插入最后一批之前将数据库连接的 AutoCommit 属性设置为 true。
注意
如果您执行任何非插入语句,COPY 语句也会关闭。但是,您可能无法凭直觉确定必须处理 COPY 语句(其中的查询可能不相关)中的错误,这样会造成混乱并导致更难以维护应用程序。您应当在批量加载结束时显式终止 COPY 语句,同时处理任何错误。
3.1.13.4 - 使用 COPY 语句
COPY 允许您将存储在数据库节点上的文件中的数据批量加载到 Vertica 数据库。此方法是将数据加载到 Vertica 的最高效方法,因为文件驻留在数据库服务器上。您必须是超级用户才能使用 COPY 访问数据库节点的文件系统。
重要
在 Vertica ≤ 9.2 版本中创建的数据库中,COPY 支持 DIRECT 选项,该选项指定将数据直接加载到
ROS 而不是 WOS。将大型 (>100MB) 文件加载到数据库时使用此选项;否则,负载可能会填满 WOS。发生这种情况时, Tuple Mover 必须对 WOS 数据执行
移出 操作。直接加载到 ROS 更高效,并且可避免强制执行 moveout。
在 Vertica 9.3 中创建的数据库中,Vertica 忽略加载选项和提示,并始终使用 DIRECT 加载方法。≥ 10.0 版本创建的数据库不再支持 WOS 和移出操作;所有数据总是直接加载到 ROS 中。
注意
当在 COPY 命令中指定了例外和拒绝的数据修饰符时,将在客户端计算机上创建例外/拒绝文件。从驱动程序执行 COPY 查询时,请为这些修饰符指定本地路径和文件名。
以下示例演示了如何使用 COPY 命令。
// Some standard headers
#include <stdio.h>
#include <stdlib.h>
// Only needed for Windows clients
// #include <windows.h>
// Standard ODBC headers
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
// Helper function to determine if an ODBC function call returned
// successfully.
bool notSuccess(SQLRETURN ret) {
return (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO);
}
int main()
{
// Set up the ODBC environment
SQLRETURN ret;
SQLHENV hdlEnv;
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hdlEnv);
if(notSuccess(ret)) {
printf("Could not allocate a handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated an environment handle.\n");
}
// Tell ODBC that the application uses ODBC 3.
ret = SQLSetEnvAttr(hdlEnv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
if(notSuccess(ret)) {
printf("Could not set application version to ODBC3.\n");
exit(EXIT_FAILURE);
} else {
printf("Set application to ODBC 3.\n");
}
// Allocate a database handle.
SQLHDBC hdlDbc;
ret = SQLAllocHandle(SQL_HANDLE_DBC, hdlEnv, &hdlDbc);
// Connect to the database
printf("Connecting to database.\n");
const char *dsnName = "ExampleDB";
// Note: User MUST be a database superuser to be able to access files on the
// filesystem of the node.
const char* userID = "dbadmin";
const char* passwd = "password123";
ret = SQLConnect(hdlDbc, (SQLCHAR*)dsnName,
SQL_NTS,(SQLCHAR*)userID,SQL_NTS,
(SQLCHAR*)passwd, SQL_NTS);
if(notSuccess(ret)) {
printf("Could not connect to database.\n");
exit(EXIT_FAILURE);
} else {
printf("Connected to database.\n");
}
// Disable AUTOCOMMIT
printf("Disabling autocommit.\n");
ret = SQLSetConnectAttr(hdlDbc, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, SQL_NTS);
if(notSuccess(ret)) {
printf("Could not disable autocommit.\n");
exit(EXIT_FAILURE);
}
// Set up a statement handle
SQLHSTMT hdlStmt;
SQLAllocHandle(SQL_HANDLE_STMT, hdlDbc, &hdlStmt);
// Create table to hold the data
SQLExecDirect(hdlStmt, (SQLCHAR*)"DROP TABLE IF EXISTS customers",
SQL_NTS);
SQLExecDirect(hdlStmt, (SQLCHAR*)"CREATE TABLE customers"
"(Last_Name char(50) NOT NULL, First_Name char(50),Email char(50), "
"Phone_Number char(15));",
SQL_NTS);
// Run the copy command to load data.
ret=SQLExecDirect(hdlStmt, (SQLCHAR*)"COPY customers "
"FROM '/data/customers.txt'",
SQL_NTS);
if(notSuccess(ret)) {
printf("Data was not successfully loaded.\n");
exit(EXIT_FAILURE);
} else {
// Get number of rows added.
SQLLEN numRows;
ret=SQLRowCount(hdlStmt, &numRows);
printf("Successfully inserted %d rows.\n", numRows);
}
// Done with batches, commit the transaction
printf("Committing transaction\n");
ret = SQLEndTran(SQL_HANDLE_DBC, hdlDbc, SQL_COMMIT);
if(notSuccess(ret)) {
printf("Could not commit transaction\n");
} else {
printf("Committed transaction\n");
}
// Clean up
printf("Free handles.\n");
SQLFreeHandle(SQL_HANDLE_STMT, hdlStmt);
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
exit(EXIT_SUCCESS);
}
该示例在运行后输出了以下内容:
Allocated an environment handle.
Set application to ODBC 3.
Connecting to database.
Connected to database.
Disabling autocommit.
Successfully inserted 10001 rows.
Committing transaction
Committed transaction
Free handles.
3.1.13.5 - 使用 COPY LOCAL 从客户端进行流式数据传输
COPY LOCAL 将数据从客户端系统文件流式传输到 Vertica 数据库。此语句通过 ODBC 驱动程序进行工作,从而简化了将数据文件从客户端传输到服务器的任务。
COPY LOCAL 通过 ODBC 驱动程序以透明方式工作。当客户端应用程序执行 COPY LOCAL 语句时,ODBC 驱动程序将从客户端读取数据文件并将其流式传输到服务器。
注意
COPY LOCAL 必须是查询中的第一条语句,否则 Vertica 会返回错误。
此示例演示如何使用 COPY LOCAL 语句从客户端系统加载数据:
// Some standard headers
#include <stdio.h>
#include <stdlib.h>
// Only needed for Windows clients
// #include <windows.h>
// Standard ODBC headers
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
int main()
{
// Set up the ODBC environment
SQLRETURN ret;
SQLHENV hdlEnv;
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hdlEnv);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not allocate a handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Allocated an environment handle.\n");
}
// Tell ODBC that the application uses ODBC 3.
ret = SQLSetEnvAttr(hdlEnv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not set application version to ODBC3.\n");
exit(EXIT_FAILURE);
} else {
printf("Set application to ODBC 3.\n");
}
// Allocate a database handle.
SQLHDBC hdlDbc;
ret = SQLAllocHandle(SQL_HANDLE_DBC, hdlEnv, &hdlDbc);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not aalocate a database handle.\n");
exit(EXIT_FAILURE);
} else {
printf("Set application to ODBC 3.\n");
}
// Connect to the database
printf("Connecting to database.\n");
const char *dsnName = "ExampleDB";
const char* userID = "dbadmin";
const char* passwd = "password123";
ret = SQLConnect(hdlDbc, (SQLCHAR*)dsnName,
SQL_NTS,(SQLCHAR*)userID,SQL_NTS,
(SQLCHAR*)passwd, SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
printf("Could not connect to database.\n");
exit(EXIT_FAILURE);
} else {
printf("Connected to database.\n");
}
// Set up a statement handle
SQLHSTMT hdlStmt;
SQLAllocHandle(SQL_HANDLE_STMT, hdlDbc, &hdlStmt);
// Create table to hold the data
SQLExecDirect(hdlStmt, (SQLCHAR*)"DROP TABLE IF EXISTS customers",
SQL_NTS);
SQLExecDirect(hdlStmt, (SQLCHAR*)"CREATE TABLE customers"
"(Last_Name char(50) NOT NULL, First_Name char(50),Email char(50), "
"Phone_Number char(15));",
SQL_NTS);
// Run the copy command to load data.
ret=SQLExecDirect(hdlStmt, (SQLCHAR*)"COPY customers "
"FROM LOCAL '/home/dbadmin/customers.txt'",
SQL_NTS);
if(!SQL_SUCCEEDED(ret)) {
printf("Data was not successfully loaded.\n");
exit(EXIT_FAILURE);
} else {
// Get number of rows added.
SQLLEN numRows;
ret=SQLRowCount(hdlStmt, &numRows);
printf("Successfully inserted %d rows.\n", numRows);
}
// COPY commits automatically, unless it is told not to, so
// there is no need to commit the transaction.
// Clean up
printf("Free handles.\n");
ret = SQLDisconnect( hdlDbc );
if(!SQL_SUCCEEDED(ret)) {
printf("Error disconnecting. Transaction still open?\n");
exit(EXIT_FAILURE);
}
SQLFreeHandle(SQL_HANDLE_STMT, hdlStmt);
SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
exit(EXIT_SUCCESS);
}
除了使用 COPY 语句的 LOCAL 选项从客户端系统而非数据库节点的文件系统加载数据之外,此示例与使用 COPY 语句中所示的示例基本相同。
注意
在 Windows 客户端上,由于 Windows API 中存在的限制,您为 COPY LOCAL 文件提供的路径将限制为 216 个字符。
3.2 - C#
使用适用于 ADO.NET 的 Vertica 驱动程序,采用 C# 编写的应用程序可以从 Vertica 数据库读取数据以及更新其中的数据和将数据加载到其中。此驱动程序提供了一个数据适配器(Vertica 数据适配器),该数据适配器有助于从数据库将数据读取到数据集中,还有助于将数据集中已更改的数据写回数据库。此驱动程序还提供用于读取数据的数据读取器 (VerticaDataReader)。此驱动程序需要 .NET Framework 版本 3.5 以上。
有关 ADO.NET 的详细信息,请参阅:
先决条件
创建 C# 客户端应用程序之前,必须安装 ADO.NET 客户端驱动程序。
3.2.1 - ADO.NET 数据类型
下表详细介绍了 Vertica 数据类型与 .NET 数据类型和 ADO.NET 数据类型之间的映射。
UUID 向后兼容性
Vertica 9.0.0 版引入了 UUID 数据类型,包括对 UUID 的 JDBC 支持。Vertica ADO.NET、ODBC 和 OLE DB 客户端在 9.0.1 版中添加了对 UUID 的完全支持。Vertica 保持与不支持 UUID 数据类型的旧 受支持 客户端驱动程序版本的向后兼容性,如下所示:
3.2.2 - 设置 ADO.NET 会话的区域设置
-
ADO.NET 应用程序使用 UTF-16 字符集编码,并负责将任何非 UTF-16 编码数据转换为 UTF-16。针对 ODBC 的相同警告在违反此编码时适用。
-
向 Vertica 服务器传递数据时,ADO.NET 驱动程序会将 UTF-16 数据转换为 UTF-8,并且会将 Vertica 服务器发送的数据从 UTF-8 转换为 UTF-16。
-
ADO.NET 应用程序应通过执行 SET LOCALE TO 命令来设置正确的服务器会话区域设置,以便获取预期的排序规则和服务器上的字符串函数行为。
-
如果在数据库级别没有默认会话区域设置,则 ADO.NET 应用程序需要通过执行 SET LOCALE TO 命令来设置正确的服务器会话区域设置,以便获取预期的排序规则和服务器上的字符串函数行为。请参阅 SET LOCALE 命令。
3.2.3 - 连接到数据库
3.2.3.1 - 使用 TLS:在 Windows 中安装证书
您可以选择使用 TLS 来保护 ADO.NET 应用程序和 Vertica 之间的通信。Vertica ADO.NET 驱动程序在查找 TLS 证书时使用默认的 Windows 密钥库。该密钥库与 Internet Explorer 使用的密钥库相同。
在客户端上使用 TLS 之前,您必须在服务器上实施 TLS。请参阅 TLS 协议,执行其中的步骤,然后返回到本主题以在 Windows 上安装 TLS 证书。
要对 ADO.NET 和 Vertica 的连接使用 TLS:
将服务器证书和客户端证书导入到 Windows 密钥库:
-
将在服务器上启用 TLS 时生成的 server.crt 文件复制到 Windows 计算机。
-
双击该证书。
-
让 Windows 确定密钥类型,然后单击安装。
导入 CA 的公用证书:
必须为证书建立信任链。您可能需要导入 CA 的公用证书(尤其是当该证书是自签名证书时)。
-
使用以上过程中的同一个证书,双击该证书。
-
选择将所有的证书都放入下列存储。
-
单击浏览 (Browse),选择受信任的根证书颁发机构 (Trusted Root Certification Authorities),然后单击下一步 (Next)。
-
单击安装 (Install)。
在 ADO.NET 应用程序中启用 SSL
在连接字符串中,务必通过将 VerticaConnectionStringBuilder
中的 SSL
属性设置为 true 来启用 SSL,例如:
//configure connection properties VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.17.10";
builder.Database = "VMart";
builder.User = "dbadmin";
builder.SSL = true;
//open the connection
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
3.2.3.2 - 打开和关闭数据库连接 (ADO.NET)
通过 ADO.NET 访问 Vertica 中的数据之前,您必须使用 VerticaConnection 类(此类是 System.Data.DbConnection 的实施)创建与数据库的连接。VerticaConnection 使用包含连接属性作为字符串的单个参数。您可以手动创建属性关键字的字符串以用作参数,或者也可以使用 VerticaConnectionStringBuilder 类构建连接字符串。
要下载 ADO.NET 驱动程序,请转到客户端驱动程序下载页面。
本主题详细介绍以下内容:
要手动创建连接字符串:
请参阅 ADO.NET 连接属性,了解在连接字符串中使用的可用属性列表。至少要指定主机、数据库和用户。
-
为每个属性提供一个值,并依次附加各个属性和值(用分号分隔)。将此字符串分配给变量。例如:
String connectString = "DATABASE=VMart;HOST=v_vmart_node0001;USER=dbadmin";
-
构建指定了连接字符串的 Vertica 连接对象。
VerticaConnection _conn = new VerticaConnection(connectString)
-
打开连接。
_conn.Open();
-
创建命令对象,并将它与连接相关联。所有 VerticaCommand 对象都必须与连接相关联。
VerticaCommand command = _conn.CreateCommand();
要使用 VerticaConnectionStringBuilder 类创建连接字符串并打开连接:
-
创建 VerticaConnectionStringBuilder 类的新对象。
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
-
使用属性值更新 VerticaConnectionStringBuilder 对象。请参阅 ADO.NET 连接属性,了解在连接字符串中使用的可用属性列表。至少要指定主机、数据库和用户。
builder.Host = "v_vmart_node0001";
builder.Database = "VMart";
builder.User = "dbadmin";
-
构建将 VerticaConnectionStringBuilder 连接对象指定为字符串的 Vertica 连接对象。
VerticaConnection _conn = new VerticaConnection(builder.ToString());
-
打开连接。
_conn.Open();
-
创建命令对象,并将它与连接相关联。所有 VerticaCommand 对象都必须与连接相关联。
VerticaCommand command = _conn.CreateCommand;
注意
如果数据库与 Vertica 许可证不相符,则对
VerticaConnection.open()
的调用会将警告消息返回到控制台和日志。有关详细信息,请参阅
管理许可证。
要关闭连接:
使用完数据库后,关闭连接。如未关闭连接,应用程序的性能和可扩展性会降低。还会导致其他客户端无法获取锁。
_conn.Close();
示例用法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Vertica.Data.VerticaClient;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.10";
builder.Database = "VMart";
builder.User = "dbadmin";
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
//Perform some operations
_conn.Close();
}
}
}
3.2.3.3 - ADO.NET 连接属性
要下载 ADO.NET 驱动程序,请转到客户端驱动程序下载页面。
可以使用连接属性来配置 ADO.NET 客户端应用程序和 Vertica 数据库之间的连接。属性提供有关连接的基本信息,例如,连接到数据库所需的服务器名称和端口号。
您可以通过两种方法设置连接属性:
3.2.3.4 - ADO.NET 中的负载均衡
本机连接负载均衡
本机连接负载均衡有助于在 Vertica 数据库中的主机上分散客户端连接所带来的开销。服务器和客户端都必须启用本机连接负载均衡。如果两者者都启用了本机负载均衡,则当客户端最初连接到数据库中的主机时,此主机会从数据库中当前正在运行的主机列表中选择一个主机来处理客户端连接,并通知客户端它所选择的主机。
如果最初联系的主机没有选择自身来处理连接,则客户端会断开连接,然后打开指向第一个主机所选主机的另一个连接。指向此第二个主机的连接进程将照常进行—如果启用了 SSL,则会启动 SSL 协商,否则客户端会启动身份验证过程。有关详细信息,请参阅关于本机连接负载均衡。
若要在客户端上启用本机连接负载均衡,请在连接字符串中将 ConnectionLoadBalance
连接参数设置为 true,或者使用 ConnectionStringBuilder()
进行此设置。以下示例说明了如何在本机连接负载均衡已启用的情况下多次连接到数据库,以及从 V_MONITOR.CURRENT_SESSION 系统表获取处理连接的节点的名称。
using System;
using System.Text;
using System.Data;
using Vertica.Data.VerticaClient;
namespace ConsoleApplication1 {
class Program {
static void Main(string[] args) {
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "v_vmart_node0001.example.com";
builder.Database = "VMart";
builder.User = "dbadmin";
// Enable native client load balancing in the client,
// must also be enabled on the server!
builder.ConnectionLoadBalance = true;
// Connect 3 times to verify a new node is connected
// for each connection.
for (int i = 1; i <= 4; i++) {
try {
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
if (i == 1) {
// On the first connection, check the server policy for load balance
VerticaCommand sqlcom = _conn.CreateCommand();
sqlcom.CommandText = "SELECT LOAD_BALANCE_POLICY FROM V_CATALOG.DATABASES";
var returnValue = sqlcom.ExecuteScalar();
Console.WriteLine("Status of load balancy policy
on server: " + returnValue.ToString() + "\n");
}
VerticaCommand command = _conn.CreateCommand();
command.CommandText = "SELECT node_name FROM V_MONITOR.CURRENT_SESSION";
VerticaDataReader dr = command.ExecuteReader();
while (dr.Read()) {
Console.Write("Connect attempt #" + i + "... ");
Console.WriteLine("Connected to node " + dr[0]);
}
dr.Close();
_conn.Close();
Console.WriteLine("Disconnecting.\n");
}
catch(Exception e) {
Console.WriteLine(e.Message);
}
}
}
}
}
运行上述示例得到了以下输出:
Status of load balancing policy on server: roundrobin
Connect attempt #1... Connected to node v_vmart_node0001
Disconnecting.
Connect attempt #2... Connected to node v_vmart_node0002
Disconnecting.
Connect attempt #3... Connected to node v_vmart_node0003
Disconnecting.
Connect attempt #4... Connected to node v_vmart_node0001
Disconnecting.
基于主机名的负载均衡
您还可以通过将单个主机名解析为多个 IP 地址来平衡工作负载。ADO.NET 客户端驱动程序通过自动将主机名随机解析为指定的 IP 地址之一来实现负载均衡。
例如,假设主机名 verticahost.example.com
在 C:\Windows\System32\drivers\etc\hosts
中有以下条目:
192.0.2.0 verticahost.example.com
192.0.2.1 verticahost.example.com
192.0.2.2 verticahost.example.com
指定主机名 verticahost.example.com
会随机解析为列出的 IP 地址之一。
3.2.3.5 - ADO.NET 连接故障转移
如果客户端应用程序尝试连接到 Vertica 群集中处于关闭状态的主机,则使用默认连接配置时连接尝试将会失败。此故障通常会向用户返回一个错误。用户必须等待主机恢复并重试连接,或者手动编辑连接设置以选择其他主机。
由于 Vertica 分析型数据库采用分布式架构,通常您不会关注哪个数据库主机处理客户端应用程序的连接。可以使用客户端驱动程序的连接故障转移功能来防止用户在无法访问连接设置中所指定主机的情况下收到连接错误。JDBC 驱动程序可通过几种方式让客户端驱动程序在无法访问连接参数中所指定的主机时自动尝试连接到其他主机:
-
将 DNS 服务器配置为返回一个主机名的多个 IP 地址。在连接设置中使用此主机名时,客户端会尝试连接到 DNS 找到的第一个 IP 地址。如果位于该 IP 地址的主机无法访问,则客户端会尝试连接到第二个 IP,依此类推,直到其成功地连接到某个主机或试过所有 IP 地址为止。
-
提供当您在连接参数中指定的主要主机无法访问时客户端驱动程序尝试连接的备份主机列表。
-
(仅限 JDBC)在尝试连接到下一个节点之前,使用特定于驱动程序的连接属性来管理超时。
在所有方法中,故障转移过程对于客户端应用程序是透明的(除了在选择使用列表故障转移方法时需指定备份主机列表之外)。如果主要主机无法访问,客户端驱动程序会自动尝试连接到其他主机。
故障转移仅适用于初次建立客户端连接的情况。如果连接断开,驱动程序不会自动尝试重新连接到数据库中的其他主机。
通常可选择上述两种故障转移方法中的一种。但它们可以一起使用。如果您的 DNS 服务器返回多个 IP 地址,并且您提供了备份主机列表,则客户端会首先尝试连接 DNS 服务器返回的所有 IP,然后再尝试连接备份列表中的主机。
注意
如果备份主机列表中的主机名解析为多个 IP 地址,客户端不会尝试连接所有这些地址,而只会尝试连接列表中的第一个 IP 地址。
DNS 故障转移方法可集中处理配置客户端故障转移。在向 Vertica 分析型数据库群集添加新节点时,可以选择通过编辑 DNS 服务器设置来将这些节点添加到故障转移列表中。所有使用 DNS 服务器连接到 Vertica 分析型数据库的客户端系统都会自动采用连接故障转移,而无需更改任何设置。但此方法需要对所有客户端用于连接到 Vertica 分析型数据库群集的 DNS 服务器具有管理访问权限。这在贵组织中可能无法实现。
使用备份服务器列表要比编辑 DNS 服务器设置更加容易。但此方法不能集中处理故障转移功能。如果更改了 Vertica 分析型数据库群集,您可能需要在每个客户端系统上更新应用程序设置。
使用 DNS 故障转移
若要使用 DNS 故障转移,您需要更改 DNS 服务器的设置,将单一主机名映射为 Vertica 分析型数据库群集中主机的多个 IP 地址。然后让所有客户端应用程序都使用该主机名连接到 Vertica 分析型数据库。
您可以选择让 DNS 服务器为主机名返回任何所需数量的 IP 地址。在小型群集中,您可以选择让其返回群集中所有主机的 IP 地址。但对于大型群集,应考虑选择返回一部分主机。否则可能会导致长时间延迟,因为客户端驱动程序尝试连接到数据库中处于关闭状态的每个主机会失败。
使用备份主机列表
若要启用基于备份列表的连接故障转移,需要在客户端应用程序的 BackupServerNode
参数中指定主机的至少一个 IP 地址或主机名。可以视情况在主机名或 IP 后面使用冒号和端口号。如果未提供端口号,驱动程序会默认使用标准 Vertica 端口号 (5433)。若要列出多个主机,请用逗号分隔这些主机。
以下示例演示了如何通过设置 BackupServerNode
连接参数来指定可尝试连接的其他主机。由于有意在连接字符串中使用了一个不存在的节点,因此初始连接失败。客户端驱动程序必须尝试通过备份主机来与 Vertica 建立连接。
using System;
using System.Text;
using System.Data;
using Vertica.Data.VerticaClient;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
VerticaConnectionStringBuilder builder =
new VerticaConnectionStringBuilder();
builder.Host = "not.a.real.host:5433";
builder.Database = "VMart";
builder.User = "dbadmin";
builder.BackupServerNode =
"another.broken.node:5433,v_vmart_node0002.example.com:5433";
try
{
VerticaConnection _conn =
new VerticaConnection(builder.ToString());
_conn.Open();
VerticaCommand sqlcom = _conn.CreateCommand();
sqlcom.CommandText = "SELECT node_name FROM current_session";
var returnValue = sqlcom.ExecuteScalar();
Console.WriteLine("Connected to node: " +
returnValue.ToString() + "\n");
_conn.Close();
Console.WriteLine("Disconnecting.\n");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
注意
-
启用本机连接负载均衡时,在 BackupServerNode 连接参数中指定的其他服务器将只用于指向 Vertica 主机的初始连接。如果主机将客户端重定向至数据库群集中的其他主机来处理其连接请求,则第二个连接将不使用备份节点列表。这很少出现问题,因为本机连接负载均衡知道数据库中的哪个节点目前正处于运行状态。 请参阅ADO.NET 中的负载均衡。
-
从 BackupServerNode 列表中获取的与主机的连接未池化以用于 ADO.NET 连接。
3.2.4 - 使用 ADO.NET 查询数据库
本节介绍如何创建查询以执行下列操作:
注意
用于查询数据库的 ExecuteNonQuery() 方法将返回一个 int32 和受该查询影响的行数。int32 类型的最大大小是一个常数,并且定义为 2,147,483,547。如果查询返回的结果数超过 int32 的最大大小,则 ADO.NET 将由于 int32 类型溢出而引发异常。但是,即使报告输出值失败,该查询也会由 Vertica 进行处理。这是 .NET 中的限制,因为 ExecuteNonQuery() 是标准 ADO.NET 接口的一部分。
3.2.4.1 - 插入数据 (ADO.NET)
可以使用 VerticaCommand 类完成插入数据。VerticaCommand 是 DbCommand 的实施。使用它可以创建 SQL 语句以及向数据库发送该语句。使用 CommandText 方法将某个 SQL 语句分配给命令,然后通过调用 ExecuteNonQuery 方法来执行该 SQL 语句。ExecuteNonQuery 方法用于执行不返回结果集的语句。
要插入一行数据:
-
创建与数据库的连接。
-
使用连接创建命令对象。
VerticaCommand command = _conn.CreateCommand();
-
使用 INSERT 语句插入数据。以下是简单插入操作的示例。请注意,它不包含 COMMIT 语句,因为 Vertica ADO.NET 驱动程序在自动提交模式下工作。
command.CommandText =
"INSERT into test values(2, 'username', 'email', 'password')";
-
执行查询。rowsAdded 变量包含由 INSERT 语句添加的行数。
Int32 rowsAdded = command.ExecuteNonQuery();
ExecuteNonQuery() 方法将返回受 UPDATE、INSERT 和 DELETE 语句的命令影响的行数。此方法对所有其他类型的语句返回 -1。如果发生回退,则此方法也设置为 -1。
示例用法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Vertica.Data.VerticaClient;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.10";
builder.Database = "VMart";
builder.User = "dbadmin";
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
VerticaCommand command = _conn.CreateCommand();
command.CommandText =
"INSERT into test values(2, 'username', 'email', 'password')";
Int32 rowsAdded = command.ExecuteNonQuery();
Console.WriteLine( rowsAdded + " rows added!");
_conn.Close();
}
}
}
3.2.4.1.1 - 使用参数
可以使用参数高效地重复执行相似 SQL 语句。
使用参数
VerticaParameter 是 ADO.NET 中的 System.Data.DbParameter 基本类的扩展,用于在发送到服务器的命令中设置参数。可以在 WHERE 子句中的值不是静态值的所有查询 (SELECT/INSERT/UPDATE/DELETE) 中使用参数;此方式适用于包含已知列集但其筛选器条件由应用程序或最终用户动态设置的所有查询。以这种方式使用参数可大大降低出现 SQL 注入问题的可能性;即使只是从多个变量创建 SQL 查询,也会出现这种问题。
您需要为参数分配有效的 DbType、VerticaDbType 或系统类型。有关 System、Vertica 和 DbTypes 的映射,请参阅数据类型和 ADO.NET 数据类型。
若要创建参数占位符,请在实际查询字符串中的参数名称前面放置电子邮件符号 (@) 或冒号 (:) 字符。请勿在占位符指示符(@ 或 :)和占位符之间插入任何空格。
注意
@ 字符是用于标识参数的首选方法。为了提供向后兼容性,也支持 (:) 字符。
例如,以下典型查询使用字符串 'MA' 作为筛选器。
SELECT customer_name, customer_address, customer_city, customer_state
FROM customer_dimension WHERE customer_state = 'MA';
相反,可以编写查询以使用参数。在以下示例中,字符串 MA 替换为参数占位符 @STATE。
SELECT customer_name, customer_address, customer_city, customer_state
FROM customer_dimension WHERE customer_state = @STATE;
例如,前一个示例的 ADO.net 代码编写如下:
VerticaCommand command = _conn.CreateCommand();
command.CommandText = “SELECT customer_name, customer_address, customer_city, customer_state
FROM customer_dimension WHERE customer_state = @STATE”;
command.Parameters.Add(new VerticaParameter( “STATE”, VerticaType.VarChar));
command.Parameters["STATE"].Value = "MA";
注意
虽然 VerticaCommand 类支持 Prepare() 方法,但您不需要为参数化的语句调用 Prepare() 方法,因为 Vertica 会自动为您准备语句。
3.2.4.1.2 - 创建和回退事务
创建事务
Vertica 中的事务具有以下特性:原子、一致、隔离和持久。使用 Vertica ADO.NET 驱动程序连接到数据库时,连接处于自动提交模式,并且每个查询会在执行后提交。可以将多个语句收集到单个事务,然后使用单个事务同时提交这些语句。如果代码确定不应提交事务,您还可以选择在提交事务之前回退该事务。
这些事务使用 VerticaTransaction 对象,该对象是 DbTransaction 的实施。您必须将事务与 VerticaCommand 对象相关联。
以下代码使用显式事务将行逐个插入到 VMart 架构的表中。
要使用 ADO.NET 驱动程序在 Vertica 中创建事务:
-
创建与数据库的连接。
-
使用连接创建命令对象。
VerticaCommand command = _conn.CreateCommand();
-
启动显式事务,并将命令与该事务相关联。
VerticaTransaction txn = _conn.BeginTransaction();
command.Connection = _conn;
command.Transaction = txn;
-
执行各个 SQL 语句以添加行。
command.CommandText =
"insert into product_dimension values( ... )";
command.ExecuteNonQuery();
command.CommandText =
"insert into store_orders_fact values( ... )";
-
提交事务。
txn.Commit();
回退事务
如果代码检查到错误,则您可以捕获错误并回退整个事务。
VerticaTransaction txn = _conn.BeginTransaction();
VerticaCommand command = new
VerticaCommand("insert into product_dimension values( 838929, 5, 'New item 5' )", _conn);
// execute the insert
command.ExecuteNonQuery();
command.CommandText = "insert into product_dimension values( 838929, 6, 'New item 6' )";
// try insert and catch any errors
bool error = false;
try
{
command.ExecuteNonQuery();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
error = true;
}
if (error)
{
txn.Rollback();
Console.WriteLine("Errors. Rolling Back.");
}
else
{
txn.Commit();
Console.WriteLine("Queries Successful. Committing.");
}
提交和回退示例
以下示例详细介绍了如何在事务期间提交或回退查询。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Vertica.Data.VerticaClient;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.10";
builder.Database = "VMart";
builder.User = "dbadmin";
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
bool error = false;
VerticaCommand command = _conn.CreateCommand();
VerticaCommand command2 = _conn.CreateCommand();
VerticaTransaction txn = _conn.BeginTransaction();
command.Connection = _conn;
command.Transaction = txn;
command.CommandText =
"insert into test values(1, 'test', 'test', 'test' )";
Console.WriteLine(command.CommandText);
try
{
command.ExecuteNonQuery();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
error = true;
}
command.CommandText =
"insert into test values(2, 'ear', 'eye', 'nose', 'extra' )";
Console.WriteLine(command.CommandText);
try
{
command.ExecuteNonQuery();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
error = true;
}
if (error)
{
txn.Rollback();
Console.WriteLine("Errors. Rolling Back.");
}
else
{
txn.Commit();
Console.WriteLine("Queries Successful. Committing.");
}
_conn.Close();
}
}
}
该示例将在控制台上显示以下输出:
insert into test values(1, 'test', 'test', 'test' )
insert into test values(2, 'ear', 'eye', 'nose', 'extra' )
[42601]ERROR: INSERT has more expressions than target columns
Errors. Rolling Back.
另请参阅
3.2.4.1.2.1 - 设置事务隔离级别
可以按连接和事务设置事务隔离级别。有关 Vertica 中支持的事务隔离级别的概述,请参阅
事务。若要为连接设置默认事务隔离级别,请在 VerticaConnectionStringBuilder 字符串中使用 IsolationLevel 关键字(有关详细信息,请参阅连接字符串关键字)。若要为单个事务设置隔离级别,请将隔离级别传递到用于启动事务的 VerticaConnection.BeginTransaction()
方法调用。
要按连接设置隔离级别:
-
使用 VerticaConnectionStringBuilder 构建连接字符串。
-
为 IsolationLevel 生成器字符串提供一个值。此字符串可接受以下两个值之一:IsolationLevel.ReadCommited(默认)或 IsolationLevel.Serializeable。例如:
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.100";
builder.Database = "VMart";
builder.User = "dbadmin";
builder.IsolationLevel = System.Data.IsolationLevel.Serializeable
VerticaConnection _conn1 = new VerticaConnection(builder.ToString());
_conn1.Open();
要按事务设置隔离级别:
-
在 BeginTransaction 方法上设置 IsolationLevel,例如
VerticaTransaction txn = _conn.BeginTransaction(IsolationLevel.Serializable);
示例用法:
以下示例演示了下列操作:
-
获取连接的事务隔离级别。
-
使用连接属性设置连接的隔离级别。
-
为新事务设置事务隔离级别。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
Vertica.Data.VerticaClient;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.10";
builder.Database = "VMart";
builder.User = "dbadmin";
VerticaConnection _conn1 = new VerticaConnection(builder.ToString());
_conn1.Open();
VerticaTransaction txn1 = _conn1.BeginTransaction();
Console.WriteLine("\n Transaction 1 Transaction Isolation Level: " +
txn1.IsolationLevel.ToString());
txn1.Rollback();
VerticaTransaction txn2 = _conn1.BeginTransaction(IsolationLevel.Serializable);
Console.WriteLine("\n Transaction 2 Transaction Isolation Level: " +
txn2.IsolationLevel.ToString());
txn2.Rollback();
VerticaTransaction txn3 = _conn1.BeginTransaction(IsolationLevel.ReadCommitted);
Console.WriteLine("\n Transaction 3 Transaction Isolation Level: " +
txn3.IsolationLevel.ToString());
_conn1.Close();
}
}
}
运行后,以上示例代码会将以下内容输出到系统控制台:
Transaction 1 Transaction Isolation Level: ReadCommitted
Transaction 2 Transaction Isolation Level: Serializable
Transaction 3 Transaction Isolation Level: ReadCommitted
3.2.4.2 - 读取数据 (ADO.Net)
若要从数据库读取数据,请使用 VerticaDataReader(一种 DbDataReader 实施)。此实施可通过分析应用程序在服务器上运行,从而将大量数据快速移出服务器。
注意
当 VerticaCommand 与打开的 VerticaDataReader 关联时,它无法执行任何其他操作。若要执行其他操作,请关闭数据读取器或使用其他 VerticaCommand 对象。
要使用 VerticaDataReader 从数据库读取数据:
-
创建与数据库的连接。
-
使用连接创建命令对象。
VerticaCommand command = _conn.CreateCommand();
-
创建查询。此查询与示例 VMart 数据库配合工作。
command.CommandText =
"SELECT fat_content, product_description " +
"FROM (SELECT DISTINCT fat_content, product_description" +
" FROM product_dimension " +
" WHERE department_description " + " IN ('Dairy') " +
" ORDER BY fat_content) AS food " +
"LIMIT 10;";
-
执行读取器以从查询返回结果。以下命令将调用 VerticaCommand 对象的 ExecuteReader 方法以获取 VerticaDataReader 对象。
VerticaDataReader dr = command.ExecuteReader();
-
读取数据。数据读取器将在连续流中返回结果。因此,您必须逐行从表中读取数据。以下示例使用 while 循环来完成此操作。
Console.WriteLine("\n\n Fat Content\t Product Description");
Console.WriteLine("------------\t -------------------");
int rows = 0;
while (dr.Read())
{
Console.WriteLine(" " + dr[0] + " \t " + dr[1]);
++rows;
}
Console.WriteLine("------------\n (" + rows + " rows)\n");
-
完成后,关闭数据读取器以释放资源。
dr.Close();
示例用法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Vertica.Data.VerticaClient;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.10";
builder.Database = "VMart";
builder.User = "dbadmin";
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
VerticaCommand command = _conn.CreateCommand();
command.CommandText =
"SELECT fat_content, product_description " +
"FROM (SELECT DISTINCT fat_content, product_description" +
" FROM product_dimension " +
" WHERE department_description " +
" IN ('Dairy') " +
" ORDER BY fat_content) AS food " +
"LIMIT 10;";
VerticaDataReader dr = command.ExecuteReader();
Console.WriteLine("\n\n Fat Content\t Product Description");
Console.WriteLine("------------\t -------------------");
int rows = 0;
while (dr.Read())
{
Console.WriteLine(" " + dr[0] + " \t " + dr[1]);
++rows;
}
Console.WriteLine("------------\n (" + rows + " rows)\n");
dr.Close();
_conn.Close();
}
}
}
3.2.4.3 - 通过 ADO.Net 加载数据
此部分详细介绍可用于通过 ADO.NET 客户端驱动程序加载 Vertica 中的数据的各种方法:
3.2.4.3.1 - 使用 Vertica 数据适配器
Vertica 数据适配器 (VerticaDataAdapter) 可使客户端能够在数据集和 Vertica 数据库之间交换数据。该适配器是 DbDataAdapter 的实施。例如,您可以使用 VerticaDataAdapter 仅读取数据;或者也可以从数据库将数据读取到数据集中,然后将数据集中已更改的数据写回数据库。
批量更新
使用 Update() 方法更新数据集时,您可以选择在调用 Update() 之前使用 UpdateBatchSize() 方法,以便减少客户端在执行更新期间与服务器通信的次数。UpdateBatchSize 的默认值为 1。如果对数据集使用多个 rows.Add() 命令,则您可以将批大小更改为最佳大小以加快客户端完成更新所需执行的操作。
使用数据适配器从 Vertica 读取数据:
以下示例详细介绍如何对 VMart 架构执行 select 查询并将结果加载到 DataTable,然后将 DataTable 的内容输出到控制台。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
Vertica.Data.VerticaClient;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.10";
builder.Database = "VMart";
builder.User = "dbadmin";
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
// Try/Catch any exceptions
try
{
using (_conn)
{
// Create the command
VerticaCommand command = _conn.CreateCommand();
command.CommandText = "select product_key, product_description " +
"from product_dimension where product_key < 10";
// Associate the command with the connection
command.Connection = _conn;
// Create the DataAdapter
VerticaDataAdapter adapter = new VerticaDataAdapter();
adapter.SelectCommand = command;
// Fill the DataTable
DataTable table = new DataTable();
adapter.Fill(table);
// Display each row and column value.
int i = 1;
foreach (DataRow row in table.Rows)
{
foreach (DataColumn column in table.Columns)
{
Console.Write(row[column] + "\t");
}
Console.WriteLine();
i++;
}
Console.WriteLine(i + " rows returned.");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
_conn.Close();
}
}
}
从 Vertica 将数据读取到数据集并更改数据:
以下示例显示了如何使用数据适配器从 VMart 架构的维度表读取数据以及将数据插入到其中。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using Vertica.Data.VerticaClient
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.10";
builder.Database = "VMart";
builder.User = "dbadmin";
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
// Try/Catch any exceptions
try
{
using (_conn)
{
//Create a data adapter object using the connection
VerticaDataAdapter da = new VerticaDataAdapter();
//Create a select statement that retrieves data from the table
da.SelectCommand = new
VerticaCommand("select * from product_dimension where product_key < 10",
_conn);
//Set up the insert command for the data adapter, and bind variables for some of the columns
da.InsertCommand = new
VerticaCommand("insert into product_dimension values( :key, :version, :desc )",
_conn);
da.InsertCommand.Parameters.Add(new VerticaParameter("key", VerticaType.BigInt));
da.InsertCommand.Parameters.Add(new VerticaParameter("version", VerticaType.BigInt));
da.InsertCommand.Parameters.Add(new VerticaParameter("desc", VerticaType.VarChar));
da.InsertCommand.Parameters[0].SourceColumn = "product_key";
da.InsertCommand.Parameters[1].SourceColumn = "product_version";
da.InsertCommand.Parameters[2].SourceColumn = "product_description";
da.TableMappings.Add("product_key", "product_key");
da.TableMappings.Add("product_version", "product_version");
da.TableMappings.Add("product_description", "product_description");
//Create and fill a Data set for this dimension table, and get the resulting DataTable.
DataSet ds = new DataSet();
da.Fill(ds, 0, 0, "product_dimension");
DataTable dt = ds.Tables[0];
//Bind parameters and add two rows to the table.
DataRow dr = dt.NewRow();
dr["product_key"] = 838929;
dr["product_version"] = 5;
dr["product_description"] = "New item 5";
dt.Rows.Add(dr);
dr = dt.NewRow();
dr["product_key"] = 838929;
dr["product_version"] = 6;
dr["product_description"] = "New item 6";
dt.Rows.Add(dr);
//Extract the changes for the added rows.
DataSet ds2 = ds.GetChanges();
//Send the modifications to the server.
int updateCount = da.Update(ds2, "product_dimension");
//Merge the changes into the original Data set, and mark it up to date.
ds.Merge(ds2);
ds.AcceptChanges();
Console.WriteLine(updateCount + " updates made!");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
_conn.Close();
}
}
}
3.2.4.3.2 - 使用批量插入和预定义的语句
可以使用带有参数的预定义的语句批量加载数据。如果遇到任何错误,您还可以使用事务回退批量加载。
如果要加载大批量数据(超过 100 MB),请考虑使用直接批量插入。
以下示例详细介绍使用包含在数组中的数据以及参数和事务来批量加载数据。
可以通过以下命令创建在此示例中使用的 test 表:
=> CREATE TABLE test (id INT, username VARCHAR(24), email VARCHAR(64), password VARCHAR(8));
使用参数和事务的示例批量插入
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Vertica.Data.VerticaClient;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.10";
builder.Database = "VMart";
builder.User = "dbadmin";
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
// Create arrays for column data
int[] ids = {1, 2, 3, 4};
string[] usernames = {"user1", "user2", "user3", "user4"};
string[] emails = { "user1@example.com", "user2@example.com","user3@example.com","user4@example.com" };
string[] passwords = { "pass1", "pass2", "pass3", "pass4" };
// create counters for accepted and rejected rows
int rows = 0;
int rejRows = 0;
bool error = false;
// Create the transaction
VerticaTransaction txn = _conn.BeginTransaction();
// Create the parameterized query and assign parameter types
VerticaCommand command = _conn.CreateCommand();
command.CommandText = "insert into TEST values (@id, @username, @email, @password)";
command.Parameters.Add(new VerticaParameter("id", VerticaType.BigInt));
command.Parameters.Add(new VerticaParameter("username", VerticaType.VarChar));
command.Parameters.Add(new VerticaParameter("email", VerticaType.VarChar));
command.Parameters.Add(new VerticaParameter("password", VerticaType.VarChar));
// Prepare the statement
command.Prepare();
// Loop through the column arrays and insert the data
for (int i = 0; i < ids.Length; i++) {
command.Parameters["id"].Value = ids[i];
command.Parameters["username"].Value = usernames[i];
command.Parameters["email"].Value = emails[i];
command.Parameters["password"].Value = passwords[i];
try
{
rows += command.ExecuteNonQuery();
}
catch (Exception e)
{
Console.WriteLine("\nInsert failed - \n " + e.Message + "\n");
++rejRows;
error = true;
}
}
if (error)
{
// Roll back if errors
Console.WriteLine("Errors. Rolling Back Transaction.");
Console.WriteLine(rejRows + " rows rejected.");
txn.Rollback();
}
else
{
// Commit if no errors
Console.WriteLine("No Errors. Committing Transaction.");
txn.Commit();
Console.WriteLine("Inserted " + rows + " rows. ");
}
_conn.Close();
}
}
}
3.2.4.3.3 - 通过 ADO.NET 进行流式数据传输
以下两个选项可用于通过 ADO.NET 将客户端上的文件中的数据以流式传输到 Vertica 数据库:
此部分中的主题介绍了使用这些选项的方法。
3.2.4.3.3.1 - 通过 VerticaCopyStream 从客户端进行流式传输
使用 VerticaCopyStream
类可以将数据从客户端系统流式传输到 Vertica 数据库。通过此类,您可以直接使用 SQL COPY 语句,而不必通过将 STDIN 替换为一个或多个数据流先将数据复制到数据库群集中的主机。
注意:
-
使用事务并对 copy 命令禁用自动提交可提高性能。
-
可以通过将 copy 命令与“no commit”修饰符结合使用来禁用自动提交。您必须显式禁用提交。使用 VerticaCopyStream 时,启用事务不会禁用自动提交。
-
与 VerticaCopyStream 结合使用的 copy 命令使用 copy 语法。
-
每次调用 execute 时,都会将 VerticaCopyStream.rejects 置零。如果要捕获拒绝数,请将 VerticaCopyStream.rejects 的值分配给另一个变量,然后再次调用 execute。
-
可以使用 AddStream() 调用添加多个流。
示例用法:
以下示例演示了使用 VerticaCopyStream 将文件流复制到 Vertica。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.IO;
using Vertica.Data.VerticaClient;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
// Configure connection properties
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.10";
builder.Database = "VMart";
builder.User = "dbadmin";
//open the connection
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
try
{
using (_conn)
{
// Start a transaction
VerticaTransaction txn = _conn.BeginTransaction();
// Create a table for this example
VerticaCommand command = new VerticaCommand("DROP TABLE IF EXISTS copy_table", _conn);
command.ExecuteNonQuery();
command.CommandText = "CREATE TABLE copy_table (Last_Name char(50), "
+ "First_Name char(50),Email char(50), "
+ "Phone_Number char(15))";
command.ExecuteNonQuery();
// Create a new filestream from the data file
string filename = "C:/customers.txt";
Console.WriteLine("\n\nLoading File: " + filename);
FileStream inputfile = File.OpenRead(filename);
// Define the copy command
string copy = "copy copy_table from stdin record terminator E'\n' delimiter '|'" + " enforcelength "
+ " no commit";
// Create a new copy stream instance with the connection and copy statement
VerticaCopyStream vcs = new VerticaCopyStream(_conn, copy);
// Start the VerticaCopyStream process
vcs.Start();
// Add the file stream
vcs.AddStream(inputfile, false);
// Execute the copy
vcs.Execute();
// Finish stream and write out the list of inserted and rejected rows
long rowsInserted = vcs.Finish();
IList<long> rowsRejected = vcs.Rejects;
// Does not work when rejected or exceptions defined
Console.WriteLine("Number of Rows inserted: " + rowsInserted);
Console.WriteLine("Number of Rows rejected: " + rowsRejected.Count);
if (rowsRejected.Count > 0)
{
for (int i = 0; i < rowsRejected.Count; i++)
{
Console.WriteLine("Rejected row #{0} is row {1}", i, rowsRejected[i]);
}
}
// Commit the changes
txn.Commit();
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
//close the connection
_conn.Close();
}
}
}
3.2.4.3.3.2 - 将复制功能与 ADO.NET 配合使用
若要将 COPY 与 ADO.NET 结合使用,只需执行 COPY 语句并指定指向客户端系统上的源文件的路径即可。此方法比使用 VerticaCopyStream 类更简单。但是,如果有许多文件要复制到数据库,或者如果数据来自本地文件以外的其他源(例如,通过网络连接进行流式传输),您可能倾向于使用 VerticaCopyStream。
以下示例代码演示了使用 COPY 将文件从客户端复制到数据库。此示例与“使用 COPY 语句进行批量加载”中所示的代码相同,并且数据文件的路径位于客户端系统而非服务器上。
若要加载存储在数据库节点上的数据,请使用 VerticaCommand 对象创建 COPY 命令:
-
通过数据文件存储在的节点创建与数据库的连接。
-
使用连接创建命令对象。
VerticaCommand command = _conn.CreateCommand();
-
复制数据。以下是使用 COPY 命令加载数据的示例。此示例通过使用 LOCAL 修饰符发出命令来复制位于客户端本地的文件。
command.CommandText = "copy lcopy_table from '/home/dbadmin/customers.txt'"
+ " record terminator E'\n' delimiter '|'"
+ " enforcelength ";
Int32 insertedRows = command.ExecuteNonQuery();
Console.WriteLine(insertedRows + " inserted.");
示例用法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.IO;
using Vertica.Data.VerticaClient;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
// Configure connection properties
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.10";
builder.Database = "VMart";
builder.User = "dbadmin";
// Open the connection
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
try
{
using (_conn)
{
// Start a transaction
VerticaTransaction txn = _conn.BeginTransaction();
// Create a table for this example
VerticaCommand command = new VerticaCommand("DROP TABLE IF EXISTS lcopy_table", _conn);
command.ExecuteNonQuery();
command.CommandText = "CREATE TABLE IF NOT EXISTS lcopy_table (Last_Name char(50), "
+ "First_Name char(50),Email char(50), "
+ "Phone_Number char(15))";
command.ExecuteNonQuery();
// Define the copy command
command.CommandText = "copy lcopy_table from '/home/dbadmin/customers.txt'"
+ " record terminator E'\n' delimiter '|'"
+ " enforcelength "
+ " no commit";
// Execute the copy
Int32 insertedRows = command.ExecuteNonQuery();
Console.WriteLine(insertedRows + " inserted.");
// Commit the changes
txn.Commit();
}
}
catch (Exception e)
{
Console.WriteLine("Exception: " + e.Message);
}
// Close the connection
_conn.Close();
}
}
}
3.2.5 - 取消 ADO.NET 查询
您可以通过调用任何 Command 对象的 .Cancel()
方法来取消正在运行的 vsql 查询。SampleCancelTests
类演示了如何在读取指定数量的行后取消查询。它实现了以下方法:
SampleCancelTest()
执行 Setup()
函数以创建测试表。然后,它调用 RunQueryAndCancel()
和 RunSecondQuery()
以演示如何在读取指定的行数后取消查询。最后,它运行 Cleanup()
函数以删除测试表。
Setup()
为示例查询创建一个数据库。
Cleanup()
删除数据库。
RunQueryAndCancel()
从返回 100 多个行的查询中准确读取 100 行。
RunSecondQuery()
从查询中读取所有行。
using System;
using Vertica.Data.VerticaClient;
class SampleCancelTests
{
// Creates a database table, executes a query that cancels during a read loop,
// executes a query that does not cancel, then drops the test database table.
// connection: A connection to a Vertica database.
public static void SampleCancelTest(VerticaConnection connection)
{
VerticaCommand command = connection.CreateCommand();
Setup(command);
try
{
Console.WriteLine("Running query that will cancel after reading 100 rows...");
RunQueryAndCancel(command);
Console.WriteLine("Running a second query...");
RunSecondQuery(command);
Console.WriteLine("Finished!");
}
finally
{
Cleanup(command);
}
}
// Set up the database table for the example.
// command: A Command object used to execute the query.
private static void Setup(VerticaCommand command)
{
// Create table used for test.
Console.WriteLine("Creating and loading table...");
command.CommandText = "DROP TABLE IF EXISTS adocanceltest";
command.ExecuteNonQuery();
command.CommandText = "CREATE TABLE adocanceltest(id INTEGER, time TIMESTAMP)";
command.ExecuteNonQuery();
command.CommandText = @"INSERT INTO adocanceltest
SELECT row_number() OVER(), slice_time
FROM(
SELECT slice_time FROM(
SELECT '2021-01-01'::timestamp s UNION ALL SELECT '2022-01-01'::timestamp s
) sq TIMESERIES slice_time AS '1 second' OVER(ORDER BY s)
) sq2";
command.ExecuteNonQuery();
}
// Clean up the database after running the example.
// command: A Command object used to execute the query.
private static void Cleanup(VerticaCommand command)
{
command.CommandText = "DROP TABLE IF EXISTS adocanceltest";
command.ExecuteNonQuery();
}
// Execute a query that returns many rows and cancels after reading 100.
// command: A Command object used to execute the query.
private static void RunQueryAndCancel(VerticaCommand command)
{
command.CommandText = "SELECT COUNT(id) from adocanceltest";
int fullRowCount = Convert.ToInt32(command.ExecuteScalar());
command.CommandText = "SELECT id, time FROM adocanceltest";
VerticaDataReader dr = command.ExecuteReader();
int nCount = 0;
try
{
while (dr.Read())
{
nCount++;
if (nCount == 100)
{
// After reaching 100 rows, cancel the command
// Note that it is not necessary to read the remaining rows
command.Cancel();
return;
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
dr.Close();
// Verify that the cancel stopped the query
Console.WriteLine((fullRowCount - nCount) + " rows out of " + fullRowCount + " discarded by cancel");
}
}
// Execute a simple query and read all results.
// command: A Command object used to execute the query.
private static void RunSecondQuery(VerticaCommand command)
{
command.CommandText = "SELECT 1 FROM dual";
VerticaDataReader dr = command.ExecuteReader();
try
{
while (dr.Read())
{
;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine("Warning: no exception should be thrown on query after cancel");
}
finally
{
dr.Close();
}
}
}
3.2.6 - 处理消息
可以通过对 VerticaConnection 委托类使用 InfoMessage 事件来捕获 Vertica 向 ADO.NET 驱动程序提供的信息消息和警告消息。此类可捕获严重性不足以强制触发异常但仍可能提供对应用程序有益的信息的消息。
要使用 VerticaInfoMessageEventHander 类:
-
创建一个方法以处理从事件处理程序发送的消息:
static void conn_InfoMessage(object sender, VerticaInfoMessageEventArgs e)
{
Console.WriteLine(e.SqlState + ": " + e.Message);
}
-
为 InfoMessage 事件创建一个连接并注册新的 VerticaInfoMessageHandler 委托:
_conn.InfoMessage += new VerticaInfoMessageEventHandler(conn_InfoMessage);
-
执行查询。如果生成了消息,则会运行事件处理函数。
-
可以使用以下命令取消订阅事件:
_conn.InfoMessage -= new VerticaInfoMessageEventHandler(conn_InfoMessage);
示例
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Vertica.Data.VerticaClient;
namespace ConsoleApplication {
class Program {
// define message handler to deal with messages
static void conn_InfoMessage(object sender, VerticaInfoMessageEventArgs e) {
Console.WriteLine(e.SqlState + ": " + e.Message);
}
static void Main(string[] args) {
//configure connection properties
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.10";
builder.Database = "VMart";
builder.User = "dbadmin";
//open the connection
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
//create message handler instance by subscribing it to the InfoMessage event of the connection
_conn.InfoMessage += new VerticaInfoMessageEventHandler(conn_InfoMessage);
//create and execute the command
VerticaCommand cmd = _conn.CreateCommand();
cmd.CommandText = "drop table if exists fakeTable";
cmd.ExecuteNonQuery();
//close the connection
_conn.Close();
}
}
}
运行该示例后,将显示以下内容:
00000: Nothing was dropped
3.2.7 - 获取表元数据
可以通过对连接使用 GetSchema() 方法并将元数据加载到 DataTable 中来获取表元数据:
DataTable table = _conn.GetSchema("Tables", new string[] { database_name, schema_name, table_name, table_type });
例如:
DataTable table = _conn.GetSchema("Tables", new string[] { null, null, null, "SYSTEM TABLE" });
database_name、schema_name 和 table_name 可以设置为 null,也可以设置为特定名称,或者也可以使用 LIKE 模式。
table_type 可以设置为以下值之一:
-
"SYSTEM TABLE"
-
"TABLE"
-
"GLOBAL TEMPORARY"
-
"LOCAL TEMPORARY"
-
"VIEW"
-
NULL
如果 table_type 设置为 null,则会返回所有元数据表的元数据。
示例用法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Vertica.Data.VerticaClient;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
//configure connection properties
VerticaConnectionStringBuilder builder = new VerticaConnectionStringBuilder();
builder.Host = "192.168.1.10";
builder.Database = "VMart";
builder.User = "dbadmin";
//open the connection
VerticaConnection _conn = new VerticaConnection(builder.ToString());
_conn.Open();
//create a new data table containing the schema
//the last argument can be "SYSTEM TABLE", "TABLE", "GLOBAL TEMPORARY",
// "LOCAL TEMPORARY", "VIEW", or null for all types
DataTable table = _conn.GetSchema("Tables", new string[] { null, null, null, "SYSTEM TABLE" });
//print out the schema
foreach (DataRow row in table.Rows) {
foreach (DataColumn col in table.Columns)
{
Console.WriteLine("{0} = {1}", col.ColumnName, row[col]);
}
Console.WriteLine("============================");
}
//close the connection
_conn.Close();
}
}
}
3.3 - Java
Vertica JDBC 驱动程序为您提供了标准 JDBC API。如果已使用 JDBC 访问过其他数据库,您应该会觉得访问 Vertica 的方式有些熟悉。此部分介绍如何使用 JDBC 将 Java 应用程序连接到 Vertica。
先决条件
在创建 Java 客户端应用程序之前,您必须安装 JDBC 客户端驱动程序。
3.3.1 - JDBC 功能支持
Vertica JDBC 驱动程序符合 JDBC 4.0 标准(但它不实施这些标准中的所有可选功能)。应用程序可以使用 DatabaseMetaData
类来确定驱动程序是否支持它要使用的特定功能。此外,此驱动程序实施 Wrapper
接口,该接口可让客户端代码发现特定于 Vertica 的 JDBC 标准类(例如,VerticaConnection
类和 VerticaStatement
类)扩展。
使用 Vertica JDBC 驱动程序时,应谨记以下一些重要事实:
多个 SQL 语句支持
Vertica JDBC 驱动程序可以执行包含多个语句的字符串。例如:
stmt.executeUpdate("CREATE TABLE t(a INT);INSERT INTO t VALUES(10);");
仅 Statement
界面支持执行包含多个 SQL 语句的字符串。不能将多个语句字符串与 PreparedStatement
一起使用。从主机文件系统复制文件的 COPY 语句可使用多个语句字符串。但是,客户端 COPY 语句 (COPY FROM STDIN) 不起作用。
将多批内容转换为 COPY 语句
Vertica JDBC 驱动程序可将所有批量插入转换为 Vertica COPY 语句。如果关闭 JDBC 连接的 AutoCommit 属性,JDBC 驱动程序将使用单个 COPY 语句加载连续批量插入中的数据,这样可通过减少开销来提高加载性能。有关详细信息,请参阅使用 JDBC 预定义的语句批量插入。
多个 JDBC 版本支持
Vertica JDBC 驱动程序同时实施符合 JDBC 3.0 标准和 JDBC 4.0 标准的接口。驱动程序返回到应用程序的接口取决于应用程序运行于的 JVM 版本。如果应用程序正在 5.0 JVM 上运行,则驱动程序会向应用程序提供 JDBC 3.0 类。如果应用程序正在 6.0 或更高版本 JVM 上运行,则驱动程序会向应用程序提供 JDBC 4.0 类。
多重活动结果集 (MARS)
Vertica JDBC 驱动程序支持多重活动结果集 (MARS)。MARS 允许在单个连接中执行多个查询。ResultBufferSize 将查询的结果直接发送到客户端,而 MARS 先将结果存储在服务器上。完成查询和存储所有结果之后,您可向服务器发送检索请求以将行返回到客户端。
3.3.2 - 创建和配置连接
Java 应用程序必须先创建连接才能与 Vertica 交互。使用 JDBC 连接到 Vertica 与连接到大多数其他数据库相似。
导入 SQL 包
创建连接之前,您必须导入 Java SQL 包。执行此操作的一种简单方法是使用通配符导入整个包:
您可能还需要导入 Properties
类。在将连接实例化时,您可以使用此类的实例来传递连接属性,而不必将所有内容都编码到连接字符串中:
import java.util.Properties;
如果应用程序需要在 Java 5 JVM 中运行,它将使用符合 JDBC 3.0 的较旧版本驱动程序。此驱动程序要求使用 Class.forName()
方法手动加载 Vertica JDBC 驱动程序:
// Only required for old JDBC 3.0 driver
try {
Class.forName("com.vertica.jdbc.Driver");
} catch (ClassNotFoundException e) {
// Could not find the driver class. Likely an issue
// with finding the .jar file.
System.err.println("Could not find the JDBC driver class.");
e.printStackTrace();
return; // Exit. Cannot do anything further.
}
应用程序可能在 Java 6 或更高版本的 JVM 中运行。如果是这样,则 JVM 会自动加载兼容 Vertica JDBC 4.0 的驱动程序,而不要求调用 Class.forName
。但是,发出此调用不会对进程产生负面影响。因此,如果您希望应用程序同时与 Java 5 和 Java 6(或更高版本)JVM 兼容,应用程序仍可以调用 Class.forName
。
打开连接
导入 SQL 包之后,您便已准备好通过调用 DriverManager.getConnection()
方法来创建连接。您至少必须向此方法提供以下信息:
前三个参数始终作为连接字符串的一部分提供,连接字符串是一个 URL,可指示 JDBC 驱动程序从何处查找数据库。连接字符串的格式为:
"jdbc:vertica://VerticaHost:portNumber/databaseName"
此连接字符串的第一部分选择了 Vertica JDBC 驱动程序,后跟数据库的位置。
可以通过以下三种方法之一向 JDBC 驱动程序提供后两个参数(用户名和密码):
-
作为连接字符串的一部分。将以 URL 参数的相似方式对这两个参数进行编码:
"jdbc:vertica://VerticaHost:portNumber/databaseName?user=username&password=password"
-
作为单独的参数传递到 DriverManager.getConnection()
:
Connection conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:portNumber/databaseName",
"username", "password");
-
在 Properties
对象中:
Properties myProp = new Properties();
myProp.put("user", "username");
myProp.put("password", "password");
Connection conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:portNumber/databaseName", myProp);
对以上三种方法来说,Properties
对象是最灵活的,因为使用此对象可以轻松地将其他连接属性传递到 getConnection()
方法。有关其他连接属性的详细信息,请参阅连接属性和设置和获取连接属性值。
如果建立与数据库的连接时出现任何问题,getConnection()
方法将在其子类之一上引发 SQLException
。若要防止异常,请将方法包含在 try-catch 块中,如以下有关建立连接的完整示例所示。
import java.sql.*;
import java.util.Properties;
public class VerySimpleVerticaJDBCExample {
public static void main(String[] args) {
/*
* If your client needs to run under a Java 5 JVM, It will use the older
* JDBC 3.0-compliant driver, which requires you manually load the
* driver using Class.forname
*/
/*
* try { Class.forName("com.vertica.jdbc.Driver"); } catch
* (ClassNotFoundException e) { // Could not find the driver class.
* Likely an issue // with finding the .jar file.
* System.err.println("Could not find the JDBC driver class.");
* e.printStackTrace(); return; // Bail out. We cannot do anything
* further. }
*/
Properties myProp = new Properties();
myProp.put("user", "dbadmin");
myProp.put("password", "vertica");
myProp.put("loginTimeout", "35");
myProp.put("KeystorePath", "c:/keystore/keystore.jks");
myProp.put("KeystorePassword", "keypwd");
myProp.put("TrustStorePath", "c:/truststore/localstore.jks");
myProp.put("TrustStorePassword", "trustpwd");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://V_vmart_node0001.example.com:5433/vmart", myProp);
System.out.println("Connected!");
conn.close();
} catch (SQLTransientConnectionException connException) {
// There was a potentially temporary network error
// Could automatically retry a number of times here, but
// instead just report error and exit.
System.out.print("Network connection issue: ");
System.out.print(connException.getMessage());
System.out.println(" Try again later!");
return;
} catch (SQLInvalidAuthorizationSpecException authException) {
// Either the username or password was wrong
System.out.print("Could not log into database: ");
System.out.print(authException.getMessage());
System.out.println(" Check the login credentials and try again.");
return;
} catch (SQLException e) {
// Catch-all for other exceptions
e.printStackTrace();
}
}
}
使用密钥库和信任库创建连接
您可以使用密钥库和信任库创建与 JDBC 客户端驱动程序的安全连接。有关 Vertica 中的安全性的详细信息,请参阅安全性和身份验证。
有关如何在 Vertica 中生成(或导入外部)证书的示例和说明,请参阅生成 TLS 证书和密钥。
要在 Vertica 中查看您的密钥和证书,请参阅CERTIFICATES和CRYPTOGRAPHIC_KEYS。
-
生成您自己的自签名证书或使用现有 CA(证书颁发机构)证书作为根 CA。有关此过程的信息,请参考 Schannel 文档。
-
可选:生成或导入由根 CA 签署的中间 CA 证书。虽然中间 CA 证书不是必需的,但拥有中间 CA 证书对于测试和调试连接很有用。
-
为 Vertica 生成并签署(或导入)服务器证书。
-
使用 ALTER TLS CONFIGURATION 将 Vertica 配置为使用客户端/服务器 TLS 进行新连接。有关详细信息,请参阅配置客户端-服务器 TLS。
对于服务器模式(无客户端证书验证):
=> ALTER TLS CONFIGURATION server TLSMODE 'ENABLE';
=> ALTER TLS CONFIGURATION server CERTIFICATE server_cert;
对于相互模式(根据 TLSMODE 进行不同严格性的客户端证书验证):
=> ALTER TLS CONFIGURATION server TLSMODE 'TRY_VERIFY';
=> ALTER TLS CONFIGURATION server CERTIFICATE server_cert ADD CA CERTIFICATES ca_cert;
-
或者,您可以使用 CREATE AUTHENTICATION 禁用所有非 SSL 连接。
=> CREATE AUTHENTICATION no_tls METHOD 'reject' HOST NO TLS '0.0.0.0/0';
=> CREATE AUTHENTICATION no_tls METHOD 'reject' HOST NO TLS '::/128';
-
使用签署服务器证书的同一 CA 为您的客户端生成和签署证书。
-
将 pem 证书链转换为单个 pkcs 12 文件。
-
将客户端密钥和链从 pkcs12 文件导入到密钥库 JKS 文件中。有关使用 keytool 命令界面的信息,请参考 Java 文档。
$ keytool -importkeystore -srckeystore -alias my_alias -srcstoretype PKCS12 -srcstorepass my_password -noprompt -deststorepass my_password -destkeypass my_password -destkeystore /tmp/keystore.jks
-
将 CA 导入到信任库 JKS 文件中。
$ keytool -import -file certs/intermediate_ca.pem -alias my_alias -trustcacerts -keystore /tmp/truststore.jks -storepass my_truststore_password -noprompt
用法注意事项
3.3.2.1 - JDBC 连接属性
可以使用连接属性来配置 JDBC 客户端应用程序和 Vertica 数据库之间的连接。属性提供有关连接的基本信息,例如,用来连接到数据库的服务器名称和端口号。您还可以使用属性来调整连接的性能以及启用日志记录。
可以采用以下方式之一设置连接属性:
-
将属性名称和值包括在传递给方法 DriverManager.getConnection()
的连接字符串中。
-
在 Properties
对象中设置属性,然后将其传递给方法 DriverManager.getConnection()
。
-
使用方法 VerticaConnection.setProperty()
。使用此方法,您只能更改那些在创建连接后仍可更改的连接属性。
此外,一些标准 JDBC 连接属性在 Connection
界面上具有 getter 和 setter,例如 Connection.setAutoCommit()
。
连接属性
您只能在打开与数据库的连接之前设置下表中的属性。其中两个属性对每个连接都是必需的。
OAuth 连接属性
以下连接属性与 JDBC 中的 OAuth 相关。
超时属性
使用以下参数,您可以为每个步骤指定各种超时以及 JDBC 与 Vertica 数据库的整体连接。
下图说明了这些属性之间的关系以及它们在 JDBC 尝试连接到 Vertica 数据库时所起的作用:
常规属性
可以在建立连接之后设置以下属性。这些属性全都不是必需的。
日志记录属性
必须在打开连接之前设置这些控制客户端日志记录的属性。这些属性全都不是必需的,您无法在 Connection
对象已实例化之后更改所有这些属性。
Kerberos 连接参数
可以使用以下参数为使用 Kerberos 的客户端身份验证设置服务名称主体和主机名主体。
可路由连接 API 连接参数
可以使用以下参数来设置属性,以便为连接启用和配置可路由连接查找。
注意
您也可以使用 VerticaConnection.setProperty()
方法设置具有标准 JDBC 连接 setter 的属性(例如 AutoCommit)。
有关操作这些属性的信息,请参阅设置和获取连接属性值。
3.3.2.2 - 设置和获取连接属性值
可以采用以下方式之一设置连接属性:
-
将属性名称和值包括在传递给方法 DriverManager.getConnection()
的连接字符串中。
-
在 Properties
对象中设置属性,然后将其传递给方法 DriverManager.getConnection()
。
-
使用方法 VerticaConnection.setProperty()
。使用此方法,您只能更改那些在创建连接后仍可更改的连接属性。
此外,一些标准 JDBC 连接属性在 Connection
界面上具有 getter 和 setter,例如 Connection.setAutoCommit()
。
在连接时设置属性
创建与 Vertica 的连接时,您可以通过以下方式设置连接属性:
连接字符串属性
您可以使用用于用户名和密码的相同 URL 参数格式在连接字符串中指定连接属性。例如,以下字符串启用 TLS 连接:
"jdbc:vertica://VerticaHost:5433/db?user=UserName&password=Password&TLSmode=require"
使用 setProperty()
方法设置主机名会替代已在连接字符串中设置的主机名。如果出现这种情况,Vertica 可能无法连接到主机。例如,如果使用以上连接字符串,以下代码会替代 VerticaHost
名称:
Properties props = new Properties();
props.setProperty("dataSource", dataSourceURL);
props.setProperty("database", database);
props.setProperty("user", user);
props.setProperty("password", password);
ps.setProperty("jdbcDriver", jdbcDriver);
props.setProperty("hostName", "NonVertica_host");
但是,如果需要新连接或替代连接,您可以在主机名属性对象中输入有效的主机名。
NonVertica_host
主机名将替代连接字符串中的 VerticaHost
名称。若要避免此问题,请将 props.setProperty("hostName", "NonVertica_host");
行注释掉:
//props.setProperty("hostName", "NonVertica_host");
属性对象
要使用传递给 getConnection()
调用的 Properties
对象设置连接属性:
-
导入 java.util.Properties
类以实例化 Properties
对象。
-
使用 put()
方法向对象添加名称-值对。
Properties myProp = new Properties();
myProp.put("user", "ExampleUser");
myProp.put("password", "password123");
myProp.put("LoginTimeout", "35");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:/ExampleDB", myProp);
} catch (SQLException e) {
e.printStackTrace();
}
注意
无论属性值的数据类型是哪种,在 Properties 对象中设置的所有值的数据类型均为字符串。
在连接后获取和设置属性
与 Vertica 建立连接后,您可以使用 VerticaConnection
方法 getProperty()
和 setProperty()
分别设置一些连接属性的值。
使用 VerticaConnection.getProperty()
方法,您可以获取部分连接属性的值。使用此方法更改与 Vertica 建立连接后可设置的属性值。
由于这些方法特定于 Vertica,因此您必须使用以下方法之一将 Connection
对象强制转换为 VerticaConnection
接口:
以下示例演示了获取和设置只读属性的值。
import java.sql.*;
import java.util.Properties;
import com.vertica.jdbc.*;
public class SetConnectionProperties {
public static void main(String[] args) {
// Note: If your application needs to run under Java 5, you need to
// load the JDBC driver using Class.forName() here.
Properties myProp = new Properties();
myProp.put("user", "ExampleUser");
myProp.put("password", "password123");
// Set ReadOnly to true initially
myProp.put("ReadOnly", "true");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:5433/ExampleDB",
myProp);
// Show state of the ReadOnly property. This was set at the
// time the connection was created.
System.out.println("ReadOnly state: "
+ ((VerticaConnection) conn).getProperty(
"ReadOnly"));
// Change it and show it again
((VerticaConnection) conn).setProperty("ReadOnly", false);
System.out.println("ReadOnly state is now: " +
((VerticaConnection) conn).getProperty(
"ReadOnly"));
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
运行以上示例后,将在标准输出上输出以下内容:
ReadOnly state: true
ReadOnly state is now: false
3.3.2.3 - 为 JDBC 客户端配置 TLS
要为 JDBC 客户端配置 TLS:
设置密钥库/信任库属性
可以通过以下方式设置密钥库和信任库属性,每种方式各有利弊:
驱动程序级别配置
如果您将 DbVizualizer 等工具用于多个连接,请使用 JDBC 连接属性配置密钥库和信任库。但是,这确实会在连接字符串中公开这些值:
-
KeyStorePath
-
KeyStorePassword
-
TrustStorePath
-
TrustStorePassword
例如:
Properties props = new Properties();
props.setProperty("KeyStorePath", keystorepath);
props.setProperty("KeyStorePassword", keystorepassword);
props.setProperty("TrustStorePath", truststorepath);
props.setProperty("TrustStorePassword", truststorepassword);
JVM 级别配置
在 JVM 级别设置密钥库和信任库参数会将它们从连接字符串中排除,这可能更适合安全要求较严格的环境:
例如:
System.setProperty("javax.net.ssl.keyStore","clientKeyStore.key");
System.setProperty("javax.net.ssl.trustStore","clientTrustStore.key");
System.setProperty("javax.net.ssl.keyStorePassword","new_keystore_password")
System.setProperty("javax.net.ssl.trustStorePassword","new_truststore_password");
设置 TLSmode 连接属性
您可以设置 TLSmode 连接属性以确定如何处理证书。默认情况下,禁用 TLSmode。
TLSmode 标识 Vertica 应用于 JDBC 连接的安全级别。必须将 Vertica 配置为处理 TLS 连接,然后才能与其建立 TLS 加密连接。有关详细信息,请参阅TLS 协议。有效值包括:
-
disable
:JDBC 使用纯文本连接并且不实施任何安全措施。
-
require
:JDBC 使用 TLS 连接而不验证 CA 证书。
-
verify-ca
:JDBC 使用 TLS 连接并确认服务器证书已由证书颁发机构签名。此设置等效于已弃用的 ssl=true
属性。
-
verify-full
:JDBC 使用 TLS 连接,确认服务器证书已由证书颁发机构签名,并验证主机名与服务器证书中提供的名称匹配。
如果设置了此属性和 SSL 属性,则此属性优先。
例如,要将 JDBC 配置为使用 TLS 连接服务器而不验证 CA 证书,您可以通过 VerticaConnection.setProperty()
方法将 TLSmode 属性设置为 'require':
Properties props = new Properties();
props.setProperty("TLSmode", "verify-full");
运行 SSL 调试实用程序
配置 TLS 后,您可以为调试实用程序运行以下命令:
$ java -Djavax.net.debug=ssl
通过调试实用程序,可以使用多个调试指示符(选项)。指示符有助于缩小返回的调试信息的范围。例如,可以指定其中一个选项来打印握手消息或会话活动。
有关该调试实用程序及其选项的信息,请参阅 Oracle 文档《JSSE 参考指南》中的“调试实用程序”。
有关解释调试信息的说明,请参阅 Oracle 文档调试 SSL/TLS 连接。
3.3.2.4 - 设置和返回客户端连接标记
JDBC 客户端具有用于设置和返回客户端连接标签的方法:getClientInfo() 和 setClientInfo()。这些方法可与 SQL 函数 GET_CLIENT_LABEL 和 SET_CLIENT_LABEL 配合使用。
使用这两种方法时,请确保向 setter 和 getter 方法都传递字符串值 APPLICATIONNAME
。
setClientInfo() 用于创建客户端标签,而 getClientInfo() 则用于返回客户端标签:
import java.sql.*;
import java.util.Properties;
public class ClientLabelJDBC {
public static void main(String[] args) {
Properties myProp = new Properties();
myProp.put("user", "dbadmin");
myProp.put("password", "");
myProp.put("loginTimeout", "35");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://docc05.verticacorp.com:5433/doccdb", myProp);
System.out.println("Connected!");
conn.setClientInfo("APPLICATIONNAME", "JDBC Client - Data Load");
System.out.println("New Conn label: " + conn.getClientInfo("APPLICATIONNAME"));
conn.close();
} catch (SQLTransientConnectionException connException) {
// There was a potentially temporary network error
// Could automatically retry a number of times here, but
// instead just report error and exit.
System.out.print("Network connection issue: ");
System.out.print(connException.getMessage());
System.out.println(" Try again later!");
return;
} catch (SQLInvalidAuthorizationSpecException authException) {
// Either the username or password was wrong
System.out.print("Could not log into database: ");
System.out.print(authException.getMessage());
System.out.println(" Check the login credentials and try again.");
return;
} catch (SQLException e) {
// Catch-all for other exceptions
e.printStackTrace();
}
}
}
运行此方法时,会将以下结果打印到标准输出:
Connected!
New Conn Label: JDBC Client - Data Load
3.3.2.5 - 设置 JDBC 会话的区域设置
可以通过以下方法设置连接的区域设置:在打开该连接时将 SET LOCALE 语句包含到 ConnSettings 属性;或者在打开该连接之后的任何时候执行 SET LOCALE 语句。更改 Connection
对象的区域设置会影响使用该对象实例化的所有 Statement
对象。
可以通过执行 SHOW LOCALE 查询来获取区域设置。以下示例演示了使用 ConnSettings 设置区域设置以及执行语句和获取区域设置:
import java.sql.*;
import java.util.Properties;
public class GetAndSetLocale {
public static void main(String[] args) {
// If running under a Java 5 JVM, you need to load the JDBC driver
// using Class.forname here
Properties myProp = new Properties();
myProp.put("user", "ExampleUser");
myProp.put("password", "password123");
// Set Locale to true en_GB on connection. After the connection
// is established, the JDBC driver runs the statements in the
// ConnSettings property.
myProp.put("ConnSettings", "SET LOCALE TO en_GB");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:5433/ExampleDB",
myProp);
// Execute a query to get the locale. The results should
// show "en_GB" as the locale, since it was set by the
// conn settings property.
Statement stmt = conn.createStatement();
ResultSet rs = null;
rs = stmt.executeQuery("SHOW LOCALE");
System.out.print("Query reports that Locale is set to: ");
while (rs.next()) {
System.out.println(rs.getString(2).trim());
}
// Now execute a query to set locale.
stmt.execute("SET LOCALE TO en_US");
// Run query again to get locale.
rs = stmt.executeQuery("SHOW LOCALE");
System.out.print("Query now reports that Locale is set to: ");
while (rs.next()) {
System.out.println(rs.getString(2).trim());
}
// Clean up
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
运行以上示例后,将在系统控制台上显示以下内容:
Query reports that Locale is set to: en_GB (LEN)
Query now reports that Locale is set to: en_US (LEN)
注意:
3.3.2.6 - 更改事务隔离级别
通过更改事务隔离级别,您可以选择让事务以哪种方式防止来自其他事务的干扰。默认情况下,JDBC 驱动程序会匹配 Vertica 服务器的事务隔离级别。Vertica 的默认事务隔离级别是 READ_COMMITTED
,这意味着在提交由某个事务进行的任何更改之前,任何其他事务无法读取这些更改。此级别可防止事务读取由稍后回退的其他事务插入的数据。
Vertica 还支持 SERIALIZABLE
事务隔离级别。此级别可锁定表,以防止其他事务更改查询的 WHERE
子句的结果。锁定表会产生性能影响,因为一次只有一个事务能够访问该表。
事务将保留其隔离级别直至其完成,即使在事务处理期间会话的隔离级别发生更改也是如此。Vertica 内部进程(例如
Tuple Mover 和
刷新操作)以及 DDL 操作始终以 SERIALIZABLE 隔离级别运行以确保一致性。
建立了连接之后,您可以使用 Connection
对象的 setter (setTransactionIsolation()
) 和 getter (getTransactionIsolation()
) 更改事务隔离级别的连接属性。事务隔离属性的值是一个整数。Connection
接口定义了一些常数,这些常数有助于以更直观的方式设置值:
注意
Connection
接口还定义了另外两个事务隔离常数(READ_UNCOMMITTED
和 REPEATABLE_READ
)。由于 Vertica 不支持这些隔离级别,因此它们分别转换为 READ_COMMITTED
和 SERIALIZABLE
。
以下示例演示了将事务隔离级别设置为 SERIALIZABLE。
import java.sql.*;
import java.util.Properties;
public class SetTransactionIsolation {
public static void main(String[] args) {
Properties myProp = new Properties();
myProp.put("user", "ExampleUser");
myProp.put("password", "password123");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:5433/ExampleDB",
myProp);
// Get default transaction isolation
System.out.println("Transaction Isolation Level: "
+ conn.getTransactionIsolation());
// Set transaction isolation to SERIALIZABLE
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
// Get the transaction isolation again
System.out.println("Transaction Isolation Level: "
+ conn.getTransactionIsolation());
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
运行此示例后,以下内容会输出到控制台:
Transaction Isolation Level: 2Transaction Isolation Level: 8
3.3.2.7 - JDBC 连接池
池数据源一组持续连接来减少在客户端和服务器之间重复打开网络连接的开销。与持续打开一个小型连接池并使其可供新请求使用相比,为每个请求打开新连接对服务器和客户端来说成本更高。当请求到达时,将为其分配该池中预先存在的连接之一。仅当该池中没有可用连接时,才会创建新连接。该请求完成后,连接将返回到池中并等待为其他请求提供服务。
Vertica JDBC 驱动程序支持 JDBC 4.0 标准中定义的连接池。如果要将基于 J2EE 的应用程序服务器与 Vertica 结合使用,该应用程序服务器应已有内置的数据池功能。唯一的要求是该应用程序服务器可与 Vertica 的 JDBC 驱动程序所实施的 PooledConnection
接口配合工作。应用程序服务器的池功能通常已针对该服务器设计用于处理的工作负载进行了适当优化。有关如何使用池连接的详细信息,请参阅应用程序服务器的文档。一般情况下,应在代码中以透明方式使用池连接,您只需打开连接,应用程序将处理池连接的详细信息。
如果不想使用应用程序服务器,或者应用程序服务器未提供与 Vertica 兼容的连接池,您可以使用第三方池库(例如,开源 c3p0 库或 DBCP 库)来实施连接池。
注意
Vertica 分析数据库客户端驱动程序的本机连接负载均衡功能可与应用程序服务器提供的第三方连接池和第三方池库配合工作。有关详细信息,请参阅
JDBC 中的负载均衡。
3.3.2.8 - JDBC 中的负载均衡
本机连接负载均衡
本机连接负载均衡有助于在 Vertica 数据库中的主机上分散客户端连接所带来的开销。服务器和客户端都必须启用本机连接负载均衡。如果两者者都启用了本机负载均衡,则当客户端最初连接到数据库中的主机时,此主机会从数据库中当前正在运行的主机列表中选择一个主机来处理客户端连接,并通知客户端它所选择的主机。
如果最初联系的主机没有选择自身来处理连接,则客户端会断开连接,然后打开指向第一个主机所选主机的另一个连接。指向此第二个主机的连接进程将照常进行—如果启用了 SSL,则会启动 SSL 协商,否则客户端会启动身份验证过程。有关详细信息,请参阅关于本机连接负载均衡。
若要在客户端上启用本机连接负载均衡,请将 ConnectionLoadBalance 连接参数设置为 true。以下示例演示了下列操作:
import java.sql.*;
import java.util.Properties;
import java.sql.*;
import java.util.Properties;
public class JDBCLoadingBalanceExample {
public static void main(String[] args) {
Properties myProp = new Properties();
myProp.put("user", "dbadmin");
myProp.put("password", "example_password123");
myProp.put("loginTimeout", "35");
myProp.put("ConnectionLoadBalance", "1");
Connection conn;
for (int x = 1; x <= 4; x++) {
try {
System.out.print("Connect attempt #" + x + "...");
conn = DriverManager.getConnection(
"jdbc:vertica://node01.example.com:5433/vmart", myProp);
Statement stmt = conn.createStatement();
// Set the load balance policy to round robin before testing the database's load balancing.
stmt.execute("SELECT SET_LOAD_BALANCE_POLICY('ROUNDROBIN');");
// Query system to table to see what node we are connected to. Assume a single row
// in response set.
ResultSet rs = stmt.executeQuery("SELECT node_name FROM v_monitor.current_session;");
rs.next();
System.out.println("Connected to node " + rs.getString(1).trim());
conn.close();
} catch (SQLTransientConnectionException connException) {
// There was a potentially temporary network error
// Could automatically retry a number of times here, but
// instead just report error and exit.
System.out.print("Network connection issue: ");
System.out.print(connException.getMessage());
System.out.println(" Try again later!");
return;
} catch (SQLInvalidAuthorizationSpecException authException) {
// Either the username or password was wrong
System.out.print("Could not log into database: ");
System.out.print(authException.getMessage());
System.out.println(" Check the login credentials and try again.");
return;
} catch (SQLException e) {
// Catch-all for other exceptions
e.printStackTrace();
}
}
}
}
运行上述示例得到了以下输出:
Connect attempt #1...Connected to node v_vmart_node0002
Connect attempt #2...Connected to node v_vmart_node0003
Connect attempt #3...Connected to node v_vmart_node0001
Connect attempt #4...Connected to node v_vmart_node0002
基于主机名的负载均衡
您还可以通过将单个主机名解析为多个 IP 地址来对工作负载进行负载均衡。当您为 DriverManager.getConnection()
方法指定主机名时,主机名会解析为来自每个连接的随机列出的 IP 地址。
例如,主机名 verticahost.example.com
在 etc/hosts
中有以下条目:
192.0.2.0 verticahost.example.com
192.0.2.1 verticahost.example.com
192.0.2.2 verticahost.example.com
将 verticahost.example.com
指定为 DriverManager.getConnection()
的连接会随机解析为列出的 IP 地址之一。
3.3.2.9 - JDBC 连接故障转移
如果客户端应用程序尝试连接到 Vertica 群集中处于关闭状态的主机,则使用默认连接配置时连接尝试将会失败。此故障通常会向用户返回一个错误。用户必须等待主机恢复并重试连接,或者手动编辑连接设置以选择其他主机。
由于 Vertica 分析型数据库采用分布式架构,通常您不会关注哪个数据库主机处理客户端应用程序的连接。可以使用客户端驱动程序的连接故障转移功能来防止用户在无法访问连接设置中所指定主机的情况下收到连接错误。JDBC 驱动程序可通过几种方式让客户端驱动程序在无法访问连接参数中所指定的主机时自动尝试连接到其他主机:
-
将 DNS 服务器配置为返回一个主机名的多个 IP 地址。在连接设置中使用此主机名时,客户端会尝试连接到 DNS 找到的第一个 IP 地址。如果位于该 IP 地址的主机无法访问,则客户端会尝试连接到第二个 IP,依此类推,直到其成功地连接到某个主机或试过所有 IP 地址为止。
-
提供当您在连接参数中指定的主要主机无法访问时客户端驱动程序尝试连接的备份主机列表。
-
(仅限 JDBC)在尝试连接到下一个节点之前,使用特定于驱动程序的连接属性来管理超时。
在所有方法中,故障转移过程对于客户端应用程序是透明的(除了在选择使用列表故障转移方法时需指定备份主机列表之外)。如果主要主机无法访问,客户端驱动程序会自动尝试连接到其他主机。
故障转移仅适用于初次建立客户端连接的情况。如果连接断开,驱动程序不会自动尝试重新连接到数据库中的其他主机。
通常可选择上述两种故障转移方法中的一种。但它们可以一起使用。如果您的 DNS 服务器返回多个 IP 地址,并且您提供了备份主机列表,则客户端会首先尝试连接 DNS 服务器返回的所有 IP,然后再尝试连接备份列表中的主机。
注意
如果备份主机列表中的主机名解析为多个 IP 地址,客户端不会尝试连接所有这些地址,而只会尝试连接列表中的第一个 IP 地址。
DNS 故障转移方法可集中处理配置客户端故障转移。在向 Vertica 分析型数据库群集添加新节点时,可以选择通过编辑 DNS 服务器设置来将这些节点添加到故障转移列表中。所有使用 DNS 服务器连接到 Vertica 分析型数据库的客户端系统都会自动采用连接故障转移,而无需更改任何设置。但此方法需要对所有客户端用于连接到 Vertica 分析型数据库群集的 DNS 服务器具有管理访问权限。这在贵组织中可能无法实现。
使用备份服务器列表要比编辑 DNS 服务器设置更加容易。但此方法不能集中处理故障转移功能。如果更改了 Vertica 分析型数据库群集,您可能需要在每个客户端系统上更新应用程序设置。
使用 DNS 故障转移
若要使用 DNS 故障转移,您需要更改 DNS 服务器的设置,将单一主机名映射为 Vertica 分析型数据库群集中主机的多个 IP 地址。然后让所有客户端应用程序都使用该主机名连接到 Vertica 分析型数据库。
您可以选择让 DNS 服务器为主机名返回任何所需数量的 IP 地址。在小型群集中,您可以选择让其返回群集中所有主机的 IP 地址。但对于大型群集,应考虑选择返回一部分主机。否则可能会导致长时间延迟,因为客户端驱动程序尝试连接到数据库中处于关闭状态的每个主机会失败。
使用备份主机列表
若要启用基于备份列表的连接故障转移,需要在客户端应用程序的 BackupServerNode
参数中指定主机的至少一个 IP 地址或主机名。可以视情况在主机名或 IP 后面使用冒号和端口号。如果未提供端口号,驱动程序会默认使用标准 Vertica 端口号 (5433)。若要列出多个主机,请用逗号分隔这些主机。
以下示例演示了如何通过设置 BackupServerNode
连接参数来指定可尝试连接的其他主机。由于有意在连接字符串中使用了一个不存在的节点,因此初始连接失败。客户端驱动程序必须尝试通过备份主机来与 Vertica 建立连接。
import java.sql.*;
import java.util.Properties;
public class ConnectionFailoverExample {
public static void main(String[] args) {
// Assume using JDBC 4.0 driver on JVM 6+. No driver loading needed.
Properties myProp = new Properties();
myProp.put("user", "dbadmin");
myProp.put("password", "vertica");
// Set two backup hosts to be used if connecting to the first host
// fails. All of these hosts will be tried in order until the connection
// succeeds or all of the connections fail.
myProp.put("BackupServerNode", "VerticaHost02,VerticaHost03");
Connection conn;
try {
// The connection string is set to try to connect to a known
// bnad host (in this case, a host that never existed).
conn = DriverManager.getConnection(
"jdbc:vertica://BadVerticaHost:5433/vmart", myProp);
System.out.println("Connected!");
// Query system to table to see what node we are connected to.
// Assume a single row in response set.
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT node_name FROM v_monitor.current_session;");
rs.next();
System.out.println("Connected to node " + rs.getString(1).trim());
// Done with connection.
conn.close();
} catch (SQLException e) {
// Catch-all for other exceptions
e.printStackTrace();
}
}
}
运行以上示例后,将在系统控制台上生成类似于以下内容的输出:
Connected!
Connected to node v_vmart_node0002
请注意,系统会与备份列表中的第一个节点建立连接(节点 2)。
指定连接超时
LoginTimeout 控制 JDBC 与节点建立 TCP 连接并登录 Vertica 的超时时间。
LoginNodeTimeout 控制 JDBC 登录 Vertica 数据库的超时时间。在指定的超时之后,JDBC 尝试连接到由连接属性 BackupServerNode 或 DNS 解析确定的“下一个”节点。这在节点已启动的情况下很有用,但 Vertica 进程会出现问题。
LoginNetworkTimeout 控制 JDBC 与 Vertica 节点建立 TCP 连接的超时时间。如果您未设置此连接属性,且 JDBC 客户端尝试连接的节点已关闭,则 JDBC 客户端将“无限期”等待,但实际上使用的是系统默认的 70 秒超时。LoginNetworkTimeout 的一个典型用例是,如果当前 Vertica 节点因维护而停机并且无法修改 JDBC 应用程序的连接字符串,则让 JDBC 连接到另一个节点。
NetworkTimeout 控制 Vertica 在建立连接并登录到数据库后响应客户端请求的超时时间。
要在连接字符串中设置这些参数:
# LoginTimeout is 30 seconds, LoginNodeTimeout is 10 seconds, LoginNetworkTimeout is 2 seconds, NetworkTimeout is 0.5 seconds
Connection conn = DriverManager.getConnection("jdbc:vertica://VerticaHost:5433/verticadb?user=dbadmin&loginTimeout=30&loginNodeTimeout=10"&loginNetworkTimeout=2&networkTimeout=500");
要将这些参数设置为连接属性:
Properties myProp = new Properties();
myProp.put("user", "dbadmin");
myProp.put("loginTimeout", "30"); // overall connection timeout is 30 seconds to make sure it is not too small for failover
myProp.put("loginNodeTimeout", "10"); // JDBC waits 10 seconds before attempting to connect to the next node if the Vertica process is running but does not respond
myProp.put("loginNetworkTimeout", "2"); // node connection timeout is 2 seconds
myProp.put("networkTimeout", "500"); // after the client has logged in, Vertica has 0.5 seconds to respond to each request
Connection conn = DriverManager.getConnection("jdbc:vertica://VerticaHost:5433/verticadb", myProp);
与负载均衡的交互
启用本机连接负载均衡时,在 BackupServerNode 连接参数中指定的其他服务器将只用于指向 Vertica 主机的初始连接。如果主机将客户端重定向至数据库群集中的其他主机来处理其连接请求,则第二个连接将不使用备份节点列表。这很少出现问题,因为本机连接负载均衡知道数据库中的哪个节点目前正处于运行状态。
有关详细信息,请参阅JDBC 中的负载均衡。
3.3.3 - JDBC 数据类型
JDBC 驱动程序以透明方式将大部分 Vertica 数据类型转换为相应的 Java 数据类型。在少数情况下,Vertica 数据类型无法直接转换为 Java 数据类型;本节将介绍这些例外。
3.3.3.1 - VerticaTypes 类
JDBC 不支持 Vertica 所支持的所有数据类型。Vertica JDBC 客户端驱动程序包含一个名为VerticaTypes
的附加类,可帮助您处理识别这些特定于Vertica 的数据类型。它包含可在代码中用于指定 Vertica 数据类型的常量。此类定义了两种不同类别的数据类型:
有关此类的详细信息,请参阅
JDBC 文档。
3.3.3.2 - 数值数据别名转换
Vertica 服务器支持对整数、浮点和数字类型使用数据类型别名。JDBC 驱动程序会将这些数据类型报告为基本数据类型(BIGINT、DOUBLE PRECISION 和 NUMERIC),如下所示:
如果客户端应用程序将值检索为较小数据类型,Vertica JDBC 驱动程序不会检查溢出。以下示例演示了此溢出的结果。
import java.sql.*;
import java.util.Properties;
public class JDBCDataTypes {
public static void main(String[] args) {
// If running under a Java 5 JVM, use you need to load the JDBC driver
// using Class.forname here
Properties myProp = new Properties();
myProp.put("user", "ExampleUser");
myProp.put("password", "password123");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:5433/VMart",
myProp);
Statement statement = conn.createStatement();
// Create a table that will hold a row of different types of
// numeric data.
statement.executeUpdate(
"DROP TABLE IF EXISTS test_all_types cascade");
statement.executeUpdate("CREATE TABLE test_all_types ("
+ "c0 INTEGER, c1 TINYINT, c2 DECIMAL, "
+ "c3 MONEY, c4 DOUBLE PRECISION, c5 REAL)");
// Add a row of values to it.
statement.executeUpdate("INSERT INTO test_all_types VALUES("
+ "111111111111, 444, 55555555555.5555, "
+ "77777777.77, 88888888888888888.88, "
+ "10101010.10101010101010)");
// Query the new table to get the row back as a result set.
ResultSet rs = statement
.executeQuery("SELECT * FROM test_all_types");
// Get the metadata about the row, including its data type.
ResultSetMetaData md = rs.getMetaData();
// Loop should only run once...
while (rs.next()) {
// Print out the data type used to defined the column, followed
// by the values retrieved using several different retrieval
// methods.
String[] vertTypes = new String[] {"INTEGER", "TINYINT",
"DECIMAL", "MONEY", "DOUBLE PRECISION", "REAL"};
for (int x=1; x<7; x++) {
System.out.println("\n\nColumn " + x + " (" + vertTypes[x-1]
+ ")");
System.out.println("\tgetColumnType()\t\t"
+ md.getColumnType(x));
System.out.println("\tgetColumnTypeName()\t"
+ md.getColumnTypeName(x));
System.out.println("\tgetShort()\t\t"
+ rs.getShort(x));
System.out.println("\tgetLong()\t\t" + rs.getLong(x));
System.out.println("\tgetInt()\t\t" + rs.getInt(x));
System.out.println("\tgetByte()\t\t" + rs.getByte(x));
}
}
rs.close();
statement.executeUpdate("drop table test_all_types cascade");
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
运行以上示例后,将在控制台上输出以下内容:
Column 1 (INTEGER)
getColumnType() -5
getColumnTypeName() BIGINT
getShort() 455
getLong() 111111111111
getInt() -558038585
getByte() -57
Column 2 (TINYINT)
getColumnType() -5
getColumnTypeName() BIGINT
getShort() 444
getLong() 444
getInt() 444
getByte() -68
Column 3 (DECIMAL)
getColumnType() 2
getColumnTypeName() NUMERIC
getShort() -1
getLong() 55555555555
getInt() 2147483647
getByte() -1
Column 4 (MONEY)
getColumnType() 2
getColumnTypeName() NUMERIC
getShort() -13455
getLong() 77777777
getInt() 77777777
getByte() 113
Column 5 (DOUBLE PRECISION)
getColumnType() 8
getColumnTypeName() DOUBLE PRECISION
getShort() -1
getLong() 88888888888888900
getInt() 2147483647
getByte() -1
Column 6 (REAL)
getColumnType() 8
getColumnTypeName() DOUBLE PRECISION
getShort() 8466
getLong() 10101010
getInt() 10101010
getByte() 18
3.3.3.3 - 将时间间隔与 JDBC 配合使用
JDBC 标准不包含用于时间间隔(两个时间点之间的持续时间)的数据类型。若要处理 Vertica 的 INTERVAL 数据类型,您必须使用 JDBC 特定于数据库的对象类型。
从结果集中读取时间间隔值时,使用 ResultSet.getObject()
方法检索该值,然后将其强制转换为 Vertica 时间间隔类之一: VerticaDayTimeInterval
(代表所有十种日期/时间的时间间隔)或 VerticaYearMonthInterval
(代表所有三种年/月时间间隔)。
在批量插入中使用时间间隔
将多个批插入到包含时间间隔数据的表时,您必须创建 VerticaDayTimeInterval
或 VerticaYearMonthInterval
类的实例以用于存放要插入的数据。您可以在调用类的构造函数时设置值,或者也可以在使用 setter 之后设置值。然后,可以使用 PreparedStatement.setObject()
方法插入时间间隔值。您还可以使用 .setString()
方法,并向其传递采用 "
DD ``HH:
MM:
SS"
或 "
YY-
MM"
格式的字符串。
以下示例演示了将数据插入到包含日/时间间隔和年/月时间间隔的表:
import java.sql.*;
import java.util.Properties;
// You need to import the Vertica JDBC classes to be able to instantiate
// the interval classes.
import com.vertica.jdbc.*;
public class IntervalDemo {
public static void main(String[] args) {
// If running under a Java 5 JVM, use you need to load the JDBC driver
// using Class.forname here
Properties myProp = new Properties();
myProp.put("user", "ExampleUser");
myProp.put("password", "password123");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:5433/VMart", myProp);
// Create table for interval values
Statement stmt = conn.createStatement();
stmt.execute("DROP TABLE IF EXISTS interval_demo");
stmt.executeUpdate("CREATE TABLE interval_demo("
+ "DayInt INTERVAL DAY TO SECOND, "
+ "MonthInt INTERVAL YEAR TO MONTH)");
// Insert data into interval columns using
// VerticaDayTimeInterval and VerticaYearMonthInterval
// classes.
PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO interval_demo VALUES(?,?)");
// Create instances of the Vertica classes that represent
// intervals.
VerticaDayTimeInterval dayInt = new VerticaDayTimeInterval(10, 0,
5, 40, 0, 0, false);
VerticaYearMonthInterval monthInt = new VerticaYearMonthInterval(
10, 6, false);
// These objects can also be manipulated using setters.
dayInt.setHour(7);
// Add the interval values to the batch
((VerticaPreparedStatement) pstmt).setObject(1, dayInt);
((VerticaPreparedStatement) pstmt).setObject(2, monthInt);
pstmt.addBatch();
// Set another row from strings.
// Set day interval in "days HH:MM:SS" format
pstmt.setString(1, "10 10:10:10");
// Set year to month value in "MM-YY" format
pstmt.setString(2, "12-09");
pstmt.addBatch();
// Execute the batch to insert the values.
try {
pstmt.executeBatch();
} catch (SQLException e) {
System.out.println("Error message: " + e.getMessage());
}
读取时间间隔值
可以使用 ResultSet.getObject()
方法从结果集读取时间间隔值,然后将对象转换为适当的 Vertica 对象类: VerticaDayTimeInterval
(代表日期/时间的时间间隔)或 VerticaYearMonthInterval
(代表年/月时间间隔)。如果您知道列包含时间间隔并且知道该时间间隔的类型,则可以轻松完成此操作。如果应用程序无法确定从中读取的结果集的数据结构,您可以测试列是否包含特定于数据库的对象类型,如果是的话,则可以确定该对象属于 VerticaDayTimeInterval
类还是 VerticaYearMonthInterval
类。
// Retrieve the interval values inserted by previous demo.
// Query the table to get the row back as a result set.
ResultSet rs = stmt.executeQuery("SELECT * FROM interval_demo");
// If you do not know the types of data contained in the result set,
// you can read its metadata to determine the type, and use
// additional information to determine the interval type.
ResultSetMetaData md = rs.getMetaData();
while (rs.next()) {
for (int x = 1; x <= md.getColumnCount(); x++) {
// Get data type from metadata
int colDataType = md.getColumnType(x);
// You can get the type in a string:
System.out.println("Column " + x + " is a "
+ md.getColumnTypeName(x));
// Normally, you'd have a switch statement here to
// handle all sorts of column types, but this example is
// simplified to just handle database-specific types
if (colDataType == Types.OTHER) {
// Column contains a database-specific type. Determine
// what type of interval it is. Assuming it is an
// interval...
Object columnVal = rs.getObject(x);
if (columnVal instanceof VerticaDayTimeInterval) {
// We know it is a date time interval
VerticaDayTimeInterval interval =
(VerticaDayTimeInterval) columnVal;
// You can use the getters to access the interval's
// data
System.out.print("Column " + x + "'s value is ");
System.out.print(interval.getDay() + " Days ");
System.out.print(interval.getHour() + " Hours ");
System.out.println(interval.getMinute()
+ " Minutes");
} else if (columnVal instanceof VerticaYearMonthInterval) {
VerticaYearMonthInterval interval =
(VerticaYearMonthInterval) columnVal;
System.out.print("Column " + x + "'s value is ");
System.out.print(interval.getYear() + " Years ");
System.out.println(interval.getMonth() + " Months");
} else {
System.out.println("Not an interval.");
}
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
该示例将在控制台上输出以下内容:
Column 1 is a INTERVAL DAY TO SECOND
Column 1's value is 10 Days 7 Hours 5 Minutes
Column 2 is a INTERVAL YEAR TO MONTH
Column 2's value is 10 Years 6 Months
Column 1 is a INTERVAL DAY TO SECOND
Column 1's value is 10 Days 10 Hours 10 Minutes
Column 2 is a INTERVAL YEAR TO MONTH
Column 2's value is 12 Years 9 Months
另一个选项是使用数据库元数据来查找包含时间间隔的列。
// Determine the interval data types by examining the database
// metadata.
DatabaseMetaData dbmd = conn.getMetaData();
ResultSet dbMeta = dbmd.getColumns(null, null, "interval_demo", null);
int colcount = 0;
while (dbMeta.next()) {
// Get the metadata type for a column.
int javaType = dbMeta.getInt("DATA_TYPE");
System.out.println("Column " + ++colcount + " Type name is " +
dbMeta.getString("TYPE_NAME"));
if(javaType == Types.OTHER) {
// The SQL_DATETIME_SUB column in the metadata tells you
// Specifically which subtype of interval you have.
// The VerticaDayTimeInterval.isDayTimeInterval()
// methods tells you if that value is a day time.
//
int intervalType = dbMeta.getInt("SQL_DATETIME_SUB");
if(VerticaDayTimeInterval.isDayTimeInterval(intervalType)) {
// Now you know it is one of the 10 day/time interval types.
// When you select this column you can cast to
// VerticaDayTimeInterval.
// You can get more specific by checking intervalType
// against each of the 10 constants directly, but
// they all are represented by the same object.
System.out.println("column " + colcount + " is a " +
"VerticaDayTimeInterval intervalType = "
+ intervalType);
} else if(VerticaYearMonthInterval.isYearMonthInterval(
intervalType)) {
//now you know it is one of the 3 year/month intervals,
//and you can select the column and cast to
// VerticaYearMonthInterval
System.out.println("column " + colcount + " is a " +
"VerticaDayTimeInterval intervalType = "
+ intervalType);
} else {
System.out.println("Not an interval type.");
}
}
}
3.3.3.4 - UUID 值
UUID 是 Vertica 中的核心数据类型。不过,它不是核心 Java 数据类型。您必须使用 java.util.UUID
类来表示 Java 代码中的 UUID 值。JDBC 驱动程序不会将 Vertica 中的值转换为非核心 Java 数据类型。因此,您必须使用通用对象方法(例如 PreparedStatement.setObject()
)将 UUID 值发送到 Vertica。您还可以使用通用对象方法(例如 ResultSet.getObject()
)从 Vertica 检索 UUID 值。然后,将检索到的对象转换为 java.util.UUID
类的成员。
以下示例代码演示如何将 UUID 值插入 Vertica 以及从 Vertica 中检索 UUID 值。
package jdbc_uuid_example;
import java.sql.*;
import java.util.Properties;
public class VerticaUUIDExample {
public static void main(String[] args) {
Properties myProp = new Properties();
myProp.put("user", "dbadmin");
myProp.put("password", "");
Connection conn;
try {
conn = DriverManager.getConnection("jdbc:vertica://doch01:5433/VMart",
myProp);
Statement stmt = conn.createStatement();
// Create a table with a UUID column and a VARCHAR column.
stmt.execute("DROP TABLE IF EXISTS UUID_TEST CASCADE;");
stmt.execute("CREATE TABLE UUID_TEST (id UUID, description VARCHAR(25));");
// Prepare a statement to insert a UUID and a string into the table.
PreparedStatement ps = conn.prepareStatement("INSERT INTO UUID_TEST VALUES(?,?)");
java.util.UUID uuid; // Holds the UUID value.
for (Integer x = 0; x < 10; x++) {
// Generate a random uuid
uuid = java.util.UUID.randomUUID();
// Set the UUID value by calling setObject.
ps.setObject(1, uuid);
// Set the String value to indicate which UUID this is.
ps.setString(2, "UUID #" + x);
ps.execute();
}
// Query the uuid
ResultSet rs = stmt.executeQuery("SELECT * FROM UUID_TEST ORDER BY description ASC");
while (rs.next()) {
// Cast the object from the result set as a UUID.
uuid = (java.util.UUID) rs.getObject(1);
System.out.println(rs.getString(2) + " : " + uuid.toString());
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
以上示例生成类似如下的输出:
UUID #0 : 67b6dcb6-c28c-4965-b9f7-5c830a04664d
UUID #1 : 485d3835-2887-4233-b003-392254fa97e0
UUID #2 : 81421f51-c803-473d-8cfc-2c184582a117
UUID #3 : bec8b86a-b650-47b0-852c-8229155332d9
UUID #4 : 8ae5e3ec-d143-4ef7-8901-24f6d0483abf
UUID #5 : 669696ce-5e86-4e87-b8d0-a937f5fc18d7
UUID #6 : 19609ec9-ec56-4444-9cfe-ad2b8de537dd
UUID #7 : 97182e1d-5c7e-4da1-9922-67e804fde173
UUID #8 : c76c3a2b-a9ef-4d65-b2fb-7c637f872b3c
UUID #9 : 3cbbcd26-c177-4277-b3df-bf4d9389f69d
确定列是否具有 UUID 数据类型
JDBC 不支持 UUID 数据类型。此限制意味着您不能使用通常的 ResultSetMetaData.getColumnType()
方法来确定列的数据类型是否为 UUID。在 UUID 列上调用此方法会返回 Types.OTHER
。该值也用于标识时间间隔列。可以使用两种方法来确定列是否包含 UUID:
下面的示例演示了全部两种方法:
// This example assumes you already have a database connection
// and result set from a query on a table that may contain a UUID.
// Get the metadata of the result set to get the column definitions
ResultSetMetaData meta = rs.getMetaData();
int colcount;
int maxcol = meta.getColumnCount();
System.out.println("Using column metadata:");
for (colcount = 1; colcount < maxcol; colcount++) {
// .getColumnType() always returns "OTHER" for UUID columns.
if (meta.getColumnType(colcount) == Types.OTHER) {
// To determine that it is a UUID column, test the name of the column type.
if (meta.getColumnTypeName(colcount).equalsIgnoreCase("uuid")) {
// It's a UUID column
System.out.println("Column "+ colcount + " is UUID");
}
}
}
// You can also query the table's metadata to find its column types and compare
// it to the VerticaType.UUID constant to see if it is a UUID column.
System.out.println("Using table metadata:");
DatabaseMetaData dbmd = conn.getMetaData();
// Get the metdata for the previously-created test table.
ResultSet tableMeta = dbmd.getColumns(null, null, "UUID_TEST", null);
colcount = 0;
// Each row in the result set has metadata that describes a single column.
while (tableMeta.next()) {
colcount++;
// The SQL_DATA_TYPE column holds the Vertica database data type. You compare
// this value to the VerticvaTypes.UUID constant to see if it is a UUID.
if (tableMeta.getInt("SQL_DATA_TYPE") == VerticaTypes.UUID) {
// Column is a UUID data type...
System.out.println("Column " + colcount + " is a UUID column.");
}
}
如果在运行上面的示例之后运行此示例,则会将以下内容输出到控制台:
Using column metadata:
Column 1 is UUID
Using table metadata:
Column 1 is a UUID column.
3.3.3.5 - JDBC 中的复杂类型
java.sql
查询的结果存储在 ResultSet
中。如果 ResultSet
包含复杂类型的列,您可以使用以下方法之一检索它:
类型转换表
java.sql.Array
和 java.sql.Struct
对象都有其自己的用于访问复杂类型数据的 API。在每种情况下,数据都以 java.lang.Object
的形式返回,其类型需要强制转换为 Java 类型。预期的确切 Java 类型取决于复杂类型定义中使用的 Vertica 类型,如下面的类型转换表所示:
ARRAY、SET 和 MAP 列
例如,以下方法运行返回某些 Vertica 类型的 ARRAY 的查询,然后在使用 getArray()
检索时这些类型由 JDBC 驱动程序强制转换为其对应 Java 类型的数组。此特定示例以 ARRAY[INT] 和 ARRAY[FLOAT] 开头,因此其类型分别强制转换为由类型转换表确定的 Long[]
和 Double[]
。
-
getArrayResultSetExample()
显示了如何将 ARRAY 处理为 java.sql.ResultSet
。此示例使用 getResultSet()
将底层数组作为另一个 ResultSet
返回。您可以使用此底层 ResultSet
执行以下操作:
-
getArrayObjectExample()
展示了如何将 ARRAY 作为原生 Java 数组进行处理。此示例使用 getArray()
将底层数组作为 Object
数组(而不是 ResultSet
数组)返回。它具有以下含义:
package com.vertica.jdbc.test.samples;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.Array;
import java.sql.Struct;
public class ComplexTypesArraySamples
{
/**
* Executes a query and gets a java.sql.Array from the ResultSet. It then uses the Array#getResultSet
* method to get a ResultSet containing the contents of the array.
* @param conn A Connection to a Vertica database
* @throws SQLException
*/
public static void getArrayResultSetExample (Connection conn) throws SQLException {
Statement stmt = conn.createStatement();
final String queryText = "SELECT ARRAY[ARRAY[1,2,3],ARRAY[4,5,6],ARRAY[7,8,9]]::ARRAY[ARRAY[INT]] as array";
final String targetColumnName = "array";
System.out.println ("queryText: " + queryText);
ResultSet rs = stmt.executeQuery(queryText);
int targetColumnId = rs.findColumn (targetColumnName);
while (rs.next ()) {
Array currentSqlArray = rs.getArray (targetColumnId);
ResultSet level1ResultSet = currentSqlArray.getResultSet();
if (level1ResultSet != null) {
while (level1ResultSet.next ()) {
// The first column of the result set holds the row index
int i = level1ResultSet.getInt(1) - 1;
Array level2SqlArray = level1ResultSet.getArray (2);
Object level2Object = level2SqlArray.getArray ();
// For this ARRAY[INT], the driver returns a Long[]
assert (level2Object instanceof Long[]);
Long [] level2Array = (Long [])level2Object;
System.out.println (" level1Object [" + i + "]: " + level2SqlArray.toString () + " (" + level2SqlArray.getClass() + ")");
for (int j = 0; j < level2Array.length; j++) {
System.out.println (" Value [" + i + ", " + j + "]: " + level2Array[j] + " (" + level2Array[j].getClass() + ")");
}
}
}
}
}
/**
* Executes a query and gets a java.sql.Array from the ResultSet. It then uses the Array#getArray
* method to get the contents of the array as a Java Object [].
* @param conn A Connection to a Vertica database
* @throws SQLException
*/
public static void getArrayObjectExample (Connection conn) throws SQLException {
Statement stmt = conn.createStatement();
final String queryText = "SELECT ARRAY[ARRAY[0.0,0.1,0.2],ARRAY[1.0,1.1,1.2],ARRAY[2.0,2.1,2.2]]::ARRAY[ARRAY[FLOAT]] as array";
final String targetColumnName = "array";
System.out.println ("queryText: " + queryText);
ResultSet rs = stmt.executeQuery(queryText);
int targetColumnId = rs.findColumn (targetColumnName);
while (rs.next ()) {
// Get the java.sql.Array from the result set
Array currentSqlArray = rs.getArray (targetColumnId);
// Get the internal Java Object implementing the array
Object level1ArrayObject = currentSqlArray.getArray ();
if (level1ArrayObject != null) {
// All returned instances are Object[]
assert (level1ArrayObject instanceof Object[]);
Object [] level1Array = (Object [])level1ArrayObject;
System.out.println ("Vertica driver returned a: " + level1Array.getClass());
for (int i = 0; i < level1Array.length; i++) {
Object level2Object = level1Array[i];
// For this ARRAY[FLOAT], the driver returns a Double[]
assert (level2Object instanceof Double[]);
Double [] level2Array = (Double [])level2Object;
for (int j = 0; j < level2Array.length; j++) {
System.out.println (" Value [" + i + ", " + j + "]: " + level2Array[j] + " (" + level2Array[j].getClass() + ")");
}
}
}
}
}
}
getArrayResultSetExample()
的输出显示 Vertica 列类型 ARRAY[INT] 强制转换为 Long[]
:
queryText: SELECT ARRAY[ARRAY[1,2,3],ARRAY[4,5,6],ARRAY[7,8,9]]::ARRAY[ARRAY[INT]] as array
level1Object [0]: [1,2,3] (class com.vertica.jdbc.jdbc42.S42Array)
Value [0, 0]: 1 (class java.lang.Long)
Value [0, 1]: 2 (class java.lang.Long)
Value [0, 2]: 3 (class java.lang.Long)
level1Object [1]: [4,5,6] (class com.vertica.jdbc.jdbc42.S42Array)
Value [1, 0]: 4 (class java.lang.Long)
Value [1, 1]: 5 (class java.lang.Long)
Value [1, 2]: 6 (class java.lang.Long)
level1Object [2]: [7,8,9] (class com.vertica.jdbc.jdbc42.S42Array)
Value [2, 0]: 7 (class java.lang.Long)
Value [2, 1]: 8 (class java.lang.Long)
Value [2, 2]: 9 (class java.lang.Long)
getArrayObjectExample()
的输出显示 Vertica 列类型 ARRAY[FLOAT] 强制转换为 Double[]
:
queryText: SELECT ARRAY[ARRAY[0.0,0.1,0.2],ARRAY[1.0,1.1,1.2],ARRAY[2.0,2.1,2.2]]::ARRAY[ARRAY[FLOAT]] as array
Vertica driver returned a: class [Ljava.lang.Object;
Value [0, 0]: 0.0 (class java.lang.Double)
Value [0, 1]: 0.1 (class java.lang.Double)
Value [0, 2]: 0.2 (class java.lang.Double)
Value [1, 0]: 1.0 (class java.lang.Double)
Value [1, 1]: 1.1 (class java.lang.Double)
Value [1, 2]: 1.2 (class java.lang.Double)
Value [2, 0]: 2.0 (class java.lang.Double)
Value [2, 1]: 2.1 (class java.lang.Double)
Value [2, 2]: 2.2 (class java.lang.Double)
ROW 列
如果在包含 ROW 类型的列的 java.sql.ResultSet
上调用 getObject()
,会将该列检索为 java.sql.Struct
,其中包含 Object[]
(本身可通过 getAttributes()
检索)。
Object[]
的每个元素代表该结构中的一个属性,每个属性都有对应的 Java 类型,如上面的类型转换表所示。
此示例定义具有以下属性的 ROW:
Name | Value | Vertica Type | Java Type
-----------------------------------------------------------
name | Amy | VARCHAR | String
date | '07/10/2021' | DATE | java.sql.Date
id | 5 | INT | java.lang.Long
current | false | BOOLEAN | java.lang.Boolean
package com.vertica.jdbc.test.samples;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.Array;
import java.sql.Struct;
public class ComplexTypesSamples
{
/**
* Executes a query and gets a java.sql.Struct from the ResultSet. It then uses the Struct#getAttributes
* method to get the contents of the struct as a Java Object [].
* @param conn A Connection to a Vertica database
* @throws SQLException
*/
public static void getRowExample (Connection conn) throws SQLException {
Statement stmt = conn.createStatement();
final String queryText = "SELECT ROW('Amy', '07/10/2021'::Date, 5, false) as rowExample(name, date, id, current)";
final String targetColumnName = "rowExample";
System.out.println ("queryText: " + queryText);
ResultSet rs = stmt.executeQuery(queryText);
int targetColumnId = rs.findColumn (targetColumnName);
while (rs.next ()) {
// Get the java.sql.Array from the result set
Object currentObject = rs.getObject (targetColumnId);
assert (currentObject instanceof Struct);
Struct rowStruct = (Struct)currentObject;
Object[] attributes = rowStruct.getAttributes();
// attributes.length should be 4 based on the queryText
assert (attributes.length == 4);
assert (attributes[0] instanceof String);
assert (attributes[1] instanceof java.sql.Date);
assert (attributes[2] instanceof java.lang.Long);
assert (attributes[3] instanceof java.lang.Boolean);
System.out.println ("attributes[0]: " + attributes[0] + " (" + attributes[0].getClass().getName() +")");
System.out.println ("attributes[1]: " + attributes[1] + " (" + attributes[1].getClass().getName() +")");
System.out.println ("attributes[2]: " + attributes[2] + " (" + attributes[2].getClass().getName() +")");
System.out.println ("attributes[3]: " + attributes[3] + " (" + attributes[3].getClass().getName() +")");
}
}
}
getRowExample()
的输出显示了每个元素的属性及其对应的 Java 类型:
queryText: SELECT ROW('Amy', '07/10/2021'::Date, 5, false) as rowExample(name, date, id, current)
attributes[0]: Amy (java.lang.String)
attributes[1]: 2021-07-10 (java.sql.Date)
attributes[2]: 5 (java.lang.Long)
attributes[3]: false (java.lang.Boolean)
3.3.3.6 - JDBC 中的日期类型
将日期转换为字符串
就本页而言,大日期是指年份超过 9999 的日期。
如果您的数据库不包含任何大日期,则可以可靠地调用 toString()
以将日期转换为字符串。
否则,如果您的数据库包含大日期,您应当使用 java.text.SimpleDateFormat
及其 format()
方法:
-
使用 java.text.SimpleDateFormat
定义字符串格式。yyyy
格式的字符数定义了日期中使用的最小字符数。
-
调用 SimpleDateFormat.format()
以将 java.sql.Date
对象转换为字符串。
示例
例如,以下方法在将 java.sql.Date
对象作为实参传递时返回一个字符串。这里,格式的年份部分 YYYY
表示该格式兼容所有在年份中至少包含四个字符的日期。
#import java.sql.Date;
private String convertDate (Date date) {
SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyy-MM-dd");
return dateFormat.format (date);
}
3.3.4 - 通过 JDBC 执行查询
若要通过 JDBC 运行查询,请执行下列操作:
-
连接到 Vertica 数据库。请参阅创建和配置连接。
-
运行查询。
用于运行查询的方法取决于要运行的查询的类型:
-
不返回结果集的 DDL 查询。
-
将返回结果集的 DDL 查询。
-
DML 查询。
执行 DDL(数据定义语言)查询
若要运行诸如 CREATE TABLE 和 COPY 等 DDL 查询,请使用 Statement.execute()
方法。可以通过调用连接对象的 createStatement
方法获取此类的实例。
以下示例将创建 Statement
类的实例,并使用该实例执行 CREATE TABLE 和 COPY 查询:
Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE address_book (Last_Name char(50) default ''," +
"First_Name char(50),Email char(50),Phone_Number char(50))");
stmt.execute("COPY address_book FROM 'address.dat' DELIMITER ',' NULL 'null'");
执行将返回结果集的查询
可以使用 Statement
类的executeQuery
方法执行将返回结果集的查询(例如 SELECT)。若要从结果集获取数据,请根据结果集中的列的数据类型使用诸如 getInt
、getString
和 getDouble
等方法访问列值。使用 ResultSet.next
可以前进到数据集的下一行。
ResultSet rs = null;
rs = stmt.executeQuery("SELECT First_Name, Last_Name FROM address_book");
int x = 1;
while(rs.next()){
System.out.println(x + ". " + rs.getString(1).trim() + " "
+ rs.getString(2).trim());
x++;
}
注意
Vertica JDBC 驱动程序不支持可滚动光标。您只能在结果集中向前读取。
使用 executeUpdate 执行 DML(数据操作语言)查询
可以对用于更改数据库中的数据的 DML SQL 查询(例如,INSERT、UPDATE 和 DELETE)使用 executeUpdate
方法,这些查询不会返回结果集。
stmt.executeUpdate("INSERT INTO address_book " +
"VALUES ('Ben-Shachar', 'Tamar', 'tamarrow@example.com'," +
"'555-380-6466')");
stmt.executeUpdate("INSERT INTO address_book (First_Name, Email) " +
"VALUES ('Pete','pete@example.com')");
注意
Vertica JDBC 驱动程序的 Statement
类支持在传递给 execute
方法的 SQL 字符串中执行多个语句。PreparedStatement
类不支持在一次执行中使用多个语句。
执行存储过程
您可以使用 CallableStatement 来创建和执行存储过程。
要创建存储过程:
Statement st = conn.createStatement();
String createSimpleSp = "CREATE OR REPLACE PROCEDURE raiseInt(IN x INT) LANGUAGE PLvSQL AS $$ " +
"BEGIN" +
"RAISE INFO 'x = %', x;" +
"END;" +
"$$;";
st.execute(createSimpleSp);
要调用存储过程:
String spCall = "CALL raiseInt (?)";
CallableStatement stmt = conn.prepareCall(spCall);
stmt.setInt(1, 42);
存储过程尚不支持 OUT 参数。相反,您可以分别使用 RAISE 和 getWarnings() 返回和检索执行信息:
System.out.println(stmt.getWarnings().toString());
3.3.5 - 取消 JDBC 查询
您可以使用 Statement.cancel()
方法取消 JDBC 查询。
以下示例创建一个表 jdbccanceltest
并运行两个查询,取消第一个查询:
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.Array;
import java.sql.Struct;
public class CancelSamples
{
/**
* Sets up a large test table, queries its contents and cancels the query.
* @param conn A connection to a Vertica database
* @throws SQLException
*/
public static void sampleCancelTest(Connection conn) throws SQLException
{
setup(conn);
try
{
runQueryAndCancel(conn);
runSecondQuery(conn);
}
finally
{
cleanup(conn);
}
}
// Set up table used in test.
private static void setup(Connection conn) throws SQLException
{
System.out.println("Creating and loading table...");
Statement stmt = conn.createStatement();
String queryText = "DROP TABLE IF EXISTS jdbccanceltest";
stmt.execute(queryText);
queryText = "CREATE TABLE jdbccanceltest(id INTEGER, time TIMESTAMP)";
stmt.execute(queryText);
queryText = "INSERT INTO jdbccanceltest SELECT row_number() OVER(), slice_time "
+ "FROM(SELECT slice_time FROM("
+ "SELECT '2021-01-01'::timestamp s UNION ALL SELECT '2022-01-01'::timestamp s"
+ ") sq TIMESERIES slice_time AS '1 second' OVER(ORDER BY s)) sq2";
stmt.execute(queryText);
}
/**
* Execute a long-running query and cancel it.
* @param conn A connection to a Vertica database
* @throws SQLException
*/
private static void runQueryAndCancel(Connection conn) throws SQLException
{
System.out.println("Running and canceling query...");
Statement stmt = conn.createStatement();
String queryText = "select id, time from jdbccanceltest";
ResultSet rs = stmt.executeQuery(queryText);
int i=0;
stmt.cancel();
try
{
while (rs.next()) ;
i++;
}
catch (SQLException e)
{
System.out.println("Query canceled after retrieving " + i + " rows");
System.out.println(e.getMessage());
}
}
/**
* Run a simple query to demonstrate that it can be run after
* the previous query was canceled.
* @param conn A connection to a Vertica database
* @throws SQLException
*/
private static void runSecondQuery(Connection conn) throws SQLException
{
String queryText = "select 1 from dual";
Statement stmt = conn.createStatement();
try
{
ResultSet rs = stmt.executeQuery(queryText);
while (rs.next()) ;
}
catch (SQLException e)
{
System.out.println(e.getMessage());
System.out.println("warning: no exception should have been thrown on query after cancel");
}
}
/**
* Clean up table used in test.
* @param conn A connetion to a Vertica database
* @throws SQLException
*/
private static void cleanup(Connection conn) throws SQLException
{
String queryText = "drop table if exists jdbccanceltest";
Statement stmt = conn.createStatement();
stmt.execute(queryText);
}
}
3.3.6 - 通过 JDBC 加载数据
可以使用以下任意方法通过 JDBC 接口加载数据:
以下几节详细介绍如何使用 JDBC 加载数据。
3.3.6.1 - 使用单行插入
将数据插入到表中的最简单方法是使用 SQL INSERT 语句。可以将 Statement
类的成员实例化以使用此语句,并使用其 executeUpdate()
方法以运行 SQL 语句。
以下代码片段演示了如何创建 Statement
对象并使用该对象将数据插入到名为 address_book 的表中:
Statement stmt = conn.createStatement();
stmt.executeUpdate("INSERT INTO address_book " +
"VALUES ('Smith', 'John', 'jsmith@example.com', " +
"'555-123-4567')");
此方法有几个缺点:您需要将数据转换为字符串并对数据中的特殊字符进行转义。插入数据的更好方法是使用预定义的语句。请参阅使用 JDBC 预定义的语句批量插入。
3.3.6.2 - 使用 JDBC 预定义的语句批量插入
可以使用预定义的 INSERT 语句(仅需设置一次即可重复调用的服务器端语句)将数据批量加载到 Vertica 中。您可以使用包含表示数据的问号占位符的 SQL 语句将 PreparedStatement
类的成员实例化。例如:
PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO customers(last, first, id) VALUES(?,?,?)");
然后,可以对 PreparedStatement
对象使用特定于数据类型的方法(例如 setString()
和 setInt()
)来设置参数。设置参数后,调用 addBatch()
方法以将行添加到批中。整个数据批准备就绪后,调用 executeBatch()
方法以执行批量插入。
在后台,批量插入会转换为 COPY 语句。如果已禁用连接的 AutoCommit 参数,则 Vertica 会保持打开 COPY 语句并使用该语句加载后续的批,直至事务已提交或者游标已关闭或应用程序执行任何其他操作(或者使用其他 Statement
或 PreparedStatement
对象执行任何语句)为止。使用单个 COPY 语句进行多个批量插入可以更高效地加载数据。如果要加载多个批,应禁用数据库的 AutoCommit 属性以提高效率。
执行批量插入时,可以试验各个批大小和行大小以确定可提供最佳性能的设置。
以下示例演示了使用预定义的语句批量插入数据。
import java.sql.*;
import java.util.Properties;
public class BatchInsertExample {
public static void main(String[] args) {
Properties myProp = new Properties();
myProp.put("user", "ExampleUser");
myProp.put("password", "password123");
//Set streamingBatchInsert to True to enable streaming mode for batch inserts.
//myProp.put("streamingBatchInsert", "True");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:5433/ExampleDB",
myProp);
// establish connection and make a table for the data.
Statement stmt = conn.createStatement();
// Set AutoCommit to false to allow Vertica to reuse the same
// COPY statement
conn.setAutoCommit(false);
// Drop table and recreate.
stmt.execute("DROP TABLE IF EXISTS customers CASCADE");
stmt.execute("CREATE TABLE customers (CustID int, Last_Name"
+ " char(50), First_Name char(50),Email char(50), "
+ "Phone_Number char(12))");
// Some dummy data to insert.
String[] firstNames = new String[] { "Anna", "Bill", "Cindy",
"Don", "Eric" };
String[] lastNames = new String[] { "Allen", "Brown", "Chu",
"Dodd", "Estavez" };
String[] emails = new String[] { "aang@example.com",
"b.brown@example.com", "cindy@example.com",
"d.d@example.com", "e.estavez@example.com" };
String[] phoneNumbers = new String[] { "123-456-7890",
"555-444-3333", "555-867-5309",
"555-555-1212", "781-555-0000" };
// Create the prepared statement
PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO customers (CustID, Last_Name, " +
"First_Name, Email, Phone_Number)" +
" VALUES(?,?,?,?,?)");
// Add rows to a batch in a loop. Each iteration adds a
// new row.
for (int i = 0; i < firstNames.length; i++) {
// Add each parameter to the row.
pstmt.setInt(1, i + 1);
pstmt.setString(2, lastNames[i]);
pstmt.setString(3, firstNames[i]);
pstmt.setString(4, emails[i]);
pstmt.setString(5, phoneNumbers[i]);
// Add row to the batch.
pstmt.addBatch();
}
try {
// Batch is ready, execute it to insert the data
pstmt.executeBatch();
} catch (SQLException e) {
System.out.println("Error message: " + e.getMessage());
return; // Exit if there was an error
}
// Commit the transaction to close the COPY command
conn.commit();
// Print the resulting table.
ResultSet rs = null;
rs = stmt.executeQuery("SELECT CustID, First_Name, "
+ "Last_Name FROM customers ORDER BY CustID");
while (rs.next()) {
System.out.println(rs.getInt(1) + " - "
+ rs.getString(2).trim() + " "
+ rs.getString(3).trim());
}
// Cleanup
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
运行此示例代码后的结果如下所示:
1 - Anna Allen
2 - Bill Brown
3 - Cindy Chu
4 - Don Dodd
5 - Eric Estavez
流式批量插入
默认情况下,Vertica 通过缓存每个行并在用户调用 executeBatch()
方法时插入缓存来执行批量插入。Vertica 也支持流式批量插入。流式批量插入在用户每次调用 addBatch()
时将行添加到数据库中。流式批量插入可提高数据库性能,因为它能够进行并行处理并降低内存需求。
注意
开始流式批量插入后,您无法在已执行该批或者已关闭或回退连接之前发出需要进行客户端-服务器通信的其他 JDBC 调用。
若要启用流式批量插入,请将 streamingBatchInsert
属性设置为 True。以上代码示例包含一个行,该行启用了 streamingBatchInsert
模式。移除 // 注释标记可启用该行并激活流式批量插入。
下表介绍了各种批量插入方法及其默认批量插入模式和流式批量插入模式之间的行为差异。
注意
3.3.6.2.1 - 批量加载过程中的错误处理
加载各个批时,您可以确定已接受的行数和已拒绝了哪些行(有关详细信息,请参阅确定已接受的行和已拒绝的行)。如果禁用了 AutoCommit 连接设置,则插入各个批时不会发生其他错误(例如磁盘空间错误)。此行为是由单个 SQL COPY 语句对多个连续批执行加载(这样可提高加载过程的效率)导致的。只有 COPY 语句关闭时,才会提交批量数据,而且 Vertica 会报告其他类型错误。
因此,应使批量加载应用程序准备好在 COPY 语句关闭时检查错误。可以通过以下方法促使 COPY 语句关闭:
-
通过调用 Connection.commit()
来结束批量加载事务
-
通过使用 Statement.close()
来关闭该语句
-
在加载中插入最后一批之前将连接的 AutoCommit 属性设置为 true
注意
如果您执行任何非插入语句或者使用不同的 Statement
或 PreparedStatement
对象执行任何语句,COPY 语句也会关闭。使用以上任一方法结束 COPY 语句会造成混乱并导致更难以维护应用程序,因为您需要处理非批量加载语句中的批量加载错误。您应当在批量加载结束时显式终止 COPY 语句,同时处理任何错误。
3.3.6.2.2 - 确定已接受和已拒绝行 (JDBC)
PreparedStatement.executeBatch
的返回值是一个整数数组,其中包含每个行的插入操作的成功或失败状态。值 1 表示行已被接受,而值 -3 表示行已被拒绝。如果在批处理执行期间出现了异常,您还可以使用 BatchUpdateException.getUpdateCounts()
获取该数组。
以下示例延伸了 使用 JDBC 预定义的语句批量插入 中所示的示例,以检索该数组并显示批量加载的结果。
import java.sql.*;
import java.util.Arrays;
import java.util.Properties;
public class BatchInsertErrorHandlingExample {
public static void main(String[] args) {
Properties myProp = new Properties();
myProp.put("user", "ExampleUser");
myProp.put("password", "password123");
Connection conn;
// establish connection and make a table for the data.
try {
conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:5433/ExampleDB",
myProp);
// Disable auto commit
conn.setAutoCommit(false);
// Create a statement
Statement stmt = conn.createStatement();
// Drop table and recreate.
stmt.execute("DROP TABLE IF EXISTS customers CASCADE");
stmt.execute("CREATE TABLE customers (CustID int, Last_Name"
+ " char(50), First_Name char(50),Email char(50), "
+ "Phone_Number char(12))");
// Some dummy data to insert. The one row won't insert because
// the phone number is too long for the phone column.
String[] firstNames = new String[] { "Anna", "Bill", "Cindy",
"Don", "Eric" };
String[] lastNames = new String[] { "Allen", "Brown", "Chu",
"Dodd", "Estavez" };
String[] emails = new String[] { "aang@example.com",
"b.brown@example.com", "cindy@example.com",
"d.d@example.com", "e.estavez@example.com" };
String[] phoneNumbers = new String[] { "123-456-789",
"555-444-3333", "555-867-53093453453",
"555-555-1212", "781-555-0000" };
// Create the prepared statement
PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO customers (CustID, Last_Name, " +
"First_Name, Email, Phone_Number)" +
" VALUES(?,?,?,?,?)");
// Add rows to a batch in a loop. Each iteration adds a
// new row.
for (int i = 0; i < firstNames.length; i++) {
// Add each parameter to the row.
pstmt.setInt(1, i + 1);
pstmt.setString(2, lastNames[i]);
pstmt.setString(3, firstNames[i]);
pstmt.setString(4, emails[i]);
pstmt.setString(5, phoneNumbers[i]);
// Add row to the batch.
pstmt.addBatch();
}
// Integer array to hold the results of inserting
// the batch. Will contain an entry for each row,
// indicating success or failure.
int[] batchResults = null;
try {
// Batch is ready, execute it to insert the data
batchResults = pstmt.executeBatch();
} catch (BatchUpdateException e) {
// We expect an exception here, since one of the
// inserted phone numbers is too wide for its column. All of the
// rest of the rows will be inserted.
System.out.println("Error message: " + e.getMessage());
// Batch results isn't set due to exception, but you
// can get it from the exception object.
//
// In your own code, you shouldn't assume the a batch
// exception occurred, since exceptions can be thrown
// by the server for a variety of reasons.
batchResults = e.getUpdateCounts();
}
// You should also be prepared to catch SQLExceptions in your own
// application code, to handle dropped connections and other general
// problems.
// Commit the transaction
conn.commit();
// Print the array holding the results of the batch insertions.
System.out.println("Return value from inserting batch: "
+ Arrays.toString(batchResults));
// Print the resulting table.
ResultSet rs = null;
rs = stmt.executeQuery("SELECT CustID, First_Name, "
+ "Last_Name FROM customers ORDER BY CustID");
while (rs.next()) {
System.out.println(rs.getInt(1) + " - "
+ rs.getString(2).trim() + " "
+ rs.getString(3).trim());
}
// Cleanup
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
运行以上示例后,将在控制台上生成以下输出:
Error message: [Vertica][VJDBC](100172) One or more rows were rejected by the server.Return value from inserting batch: [1, 1, -3, 1, 1]
1 - Anna Allen
2 - Bill Brown
4 - Don Dodd
5 - Eric Estavez
请注意,第三行插入失败,因为其电话号码对 Phone_Number
列来说太长。该批中其余的所有行(包括该错误之后的行)已正确插入。
注意
应确保要插入的数据的数据类型和宽度符合将插入到的表列的要求,这样比在既成事实之后处理异常更高效。
3.3.6.2.3 - 在服务器中回退批量加载
即使一个或多个行被拒绝,批量加载也始终插入所有数据。只有某个批中导致发生错误的行不会加载。如果数据库连接的 AutoCommit 属性为 true,各个批会在完成后自动提交其事务,因此在该批完成加载后,将提交数据。
在某些情况下,您可能希望成功插入某个批中的所有数据(在发生错误时不应提交任何数据)。若要实现此目的,最佳方法是关闭数据库连接的 AutoCommit 属性以防止各个批自动提交其自身。然后,如果某个批遇到错误,您可以在捕获由插入错误导致的 BatchUpdateException
之后回退事务。
以下示例演示了在加载某个批期间发生任何错误时执行回退。
import java.sql.*;
import java.util.Arrays;
import java.util.Properties;
public class RollbackBatchOnError {
public static void main(String[] args) {
Properties myProp = new Properties();
myProp.put("user", "ExampleUser");
myProp.put("password", "password123");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:5433/ExampleDB",
myProp);
// Disable auto-commit. This will allow you to roll back a
// a batch load if there is an error.
conn.setAutoCommit(false);
// establish connection and make a table for the data.
Statement stmt = conn.createStatement();
// Drop table and recreate.
stmt.execute("DROP TABLE IF EXISTS customers CASCADE");
stmt.execute("CREATE TABLE customers (CustID int, Last_Name"
+ " char(50), First_Name char(50),Email char(50), "
+ "Phone_Number char(12))");
// Some dummy data to insert. The one row won't insert because
// the phone number is too long for the phone column.
String[] firstNames = new String[] { "Anna", "Bill", "Cindy",
"Don", "Eric" };
String[] lastNames = new String[] { "Allen", "Brown", "Chu",
"Dodd", "Estavez" };
String[] emails = new String[] { "aang@example.com",
"b.brown@example.com", "cindy@example.com",
"d.d@example.com", "e.estavez@example.com" };
String[] phoneNumbers = new String[] { "123-456-789",
"555-444-3333", "555-867-53094535", "555-555-1212",
"781-555-0000" };
// Create the prepared statement
PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO customers (CustID, Last_Name, " +
"First_Name, Email, Phone_Number) "+
"VALUES(?,?,?,?,?)");
// Add rows to a batch in a loop. Each iteration adds a
// new row.
for (int i = 0; i < firstNames.length; i++) {
// Add each parameter to the row.
pstmt.setInt(1, i + 1);
pstmt.setString(2, lastNames[i]);
pstmt.setString(3, firstNames[i]);
pstmt.setString(4, emails[i]);
pstmt.setString(5, phoneNumbers[i]);
// Add row to the batch.
pstmt.addBatch();
}
// Integer array to hold the results of inserting
// the batch. Will contain an entry for each row,
// indicating success or failure.
int[] batchResults = null;
try {
// Batch is ready, execute it to insert the data
batchResults = pstmt.executeBatch();
// If we reach here, we inserted the batch without errors.
// Commit it.
System.out.println("Batch insert successful. Committing.");
conn.commit();
} catch (BatchUpdateException e) {
System.out.println("Error message: " + e.getMessage());
// Batch results isn't set due to exception, but you
// can get it from the exception object.
batchResults = e.getUpdateCounts();
// Roll back the batch transaction.
System.out.println("Rolling back batch insertion");
conn.rollback();
}
catch (SQLException e) {
// General SQL errors, such as connection issues, throw
// SQLExceptions. Your application should do something more
// than just print a stack trace,
e.printStackTrace();
}
System.out.println("Return value from inserting batch: "
+ Arrays.toString(batchResults));
System.out.println("Customers table contains:");
// Print the resulting table.
ResultSet rs = null;
rs = stmt.executeQuery("SELECT CustID, First_Name, "
+ "Last_Name FROM customers ORDER BY CustID");
while (rs.next()) {
System.out.println(rs.getInt(1) + " - "
+ rs.getString(2).trim() + " "
+ rs.getString(3).trim());
}
// Cleanup
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
运行以上示例后,将在系统控制台上输出以下内容:
Error message: [Vertica][VJDBC](100172) One or more rows were rejected by the server.Rolling back batch insertion
Return value from inserting batch: [1, 1, -3, 1, 1]
Customers table contains:
返回值指示是否已成功插入每个行。值 1 表示已插入该行并且未出现任何问题,而值 -3 表示插入该行失败。
客户表为空,因为已回退该批量插入,原因是第三列导致发生错误。
3.3.6.3 - 使用 COPY 语句进行批量加载
一次将大量数据加载(批量加载)到 Vertica 的最快方法之一是使用 COPY 语句。此语句可将存储在 Vertica 主机上的文件中的数据(或数据流中的数据)加载到数据库中的表。可以向 COPY 语句传递定义了以下设置的参数:文件中的数据的格式、加载数据时对数据的转换方式、对错误的处理方式以及加载数据的方式。有关详细信息,请参阅 COPY 文档。
重要
在 Vertica ≤ 9.2 版本中创建的数据库中,COPY 支持 DIRECT 选项,该选项指定将数据直接加载到
ROS 而不是 WOS。将大型 (>100MB) 文件加载到数据库时使用此选项;否则,负载可能会填满 WOS。发生这种情况时, Tuple Mover 必须对 WOS 数据执行
移出 操作。直接加载到 ROS 更高效,并且可避免强制执行 moveout。
在 Vertica 9.3 中创建的数据库中,Vertica 忽略加载选项和提示,并始终使用 DIRECT 加载方法。≥ 10.0 版本创建的数据库不再支持 WOS 和移出操作;所有数据总是直接加载到 ROS 中。
只有
超级用户可以使用 COPY 语句复制存储在主机上的文件,因此您必须使用超级用户帐户连接到数据库。如果要以非超级用户身份批量加载数据,您可以使用 COPY 从主机上的流(例如 STDIN)而非文件加载数据,或者也可以从客户端以流式传输数据(请参阅通过 JDBC 进行流式数据传输)。您还可以执行使用预定义的语句的批量插入(此为标准方式),这种方式在后台使用 COPY 语句来加载数据。
注意
使用 COPY 参数
ON ANY NODE
时,请确认所有节点上的源文件均相同。使用不同的文件会导致生成的结果不一致。
以下示例演示了通过 JDBC 使用 COPY 语句将名为 customers.txt
的文件加载到新的数据库表。该文件必须存储在应用程序连接到的数据库主机(在此示例中是名为 VerticaHost 的主机)上。
import java.sql.*;
import java.util.Properties;
import com.vertica.jdbc.*;
public class COPYFromFile {
public static void main(String[] args) {
Properties myProp = new Properties();
myProp.put("user", "ExampleAdmin"); // Must be superuser
myProp.put("password", "password123");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:5433/ExampleDB",myProp);
// Disable AutoCommit
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
// Create a table to hold data.
stmt.execute("DROP TABLE IF EXISTS customers;");
stmt.execute("CREATE TABLE IF NOT EXISTS customers (Last_Name char(50) "
+ "NOT NULL, First_Name char(50),Email char(50), "
+ "Phone_Number char(15))");
// Use the COPY command to load data. Use ENFORCELENGTH to reject
// strings too wide for their columns.
boolean result = stmt.execute("COPY customers FROM "
+ " '/data/customers.txt' ENFORCELENGTH");
// Determine if execution returned a count value, or a full result
// set.
if (result) {
System.out.println("Got result set");
} else {
// Count will usually return the count of rows inserted.
System.out.println("Got count");
int rowCount = stmt.getUpdateCount();
System.out.println("Number of accepted rows = " + rowCount);
}
// Commit the data load
conn.commit();
} catch (SQLException e) {
System.out.print("Error: ");
System.out.println(e.toString());
}
}
}
运行此示例后,将在系统控制台上输出以下内容(假设 customers.txt
文件两百万个有效的行):
Number of accepted rows = 2000000
3.3.6.4 - 通过 JDBC 进行流式数据传输
以下两个选项可用于将客户端上的文件中的数据以流式传输到 Vertica 数据库:
此部分中的主题介绍了使用这些选项的方法。
3.3.6.4.1 - 使用 VerticaCopyStream
使用
VerticaCopyStream 类可以将数据从客户端系统流式传输到 Vertica 数据库。它允许您直接使用 COPY,而无需先将数据复制到数据库群集中的主机。使用 COPY 命令从主机加载数据时,需要拥有超级用户权限才能访问主机的文件系统。用于从流加载数据的 COPY 语句不需要超级用户权限,因此客户端可以使用对目标表拥有 INSERT 权限的任何用户帐户进行连接。
若要将流复制到数据库,请执行下列操作:
-
禁用数据库连接的 AutoCommit 连接参数。
-
将 VerticaCopyStreamObject
实例化,并至少向其传递数据库连接对象和包含用于加载数据的 COPY 语句的字符串。该语句必须将 STDIN 中的数据复制到表。您可以使用适用于数据加载的任何参数。
注意
VerticaCopyStreamObject
构造函数可以选择使用单个 InputStream
对象或 InputStream
对象的 List
。此选项可让您预填充要复制到数据库中的流的列表。
-
调用 VerticaCopyStreamObject.start()
,以启动 COPY 语句并开始以流式传输已添加到 VerticaCopyStreamObject
的任何流中的数据。
-
调用 VerticaCopyStreamObject.addStream()
,以将其他流添加到要发送到数据库的流的列表。然后,可以调用 VerticaCopyStreamObject.execute()
以将它们以流式传输到服务器。
-
(可选)调用 VerticaCopyStreamObject.getRejects()
以从上一次 .execute()
调用获取拒绝的行的列表。对 .execute()
或 .finish()
的每次调用会重置拒绝列表。
注意
如果在步骤 2 中传递到 VerticaCopyStreamObject
对象的 COPY 语句中使用了 REJECTED DATA 或 EXCEPTIONS 选项,.getRejects()
将返回空列表。一次只能使用一种方法来跟踪拒绝的行。
-
完成添加流后,调用 VerticaCopyStreamObject.finish()
以将其余任何流发送到数据库并关闭 COPY 语句。
-
调用 Connection.commit()
以提交已加载的数据。
获取拒绝的行
VerticaCopyStreamObject.getRejects()
方法将返回一个列表,其中包含上一次 .execute()
方法调用之后拒绝的行的数量。对 .execute()
的每次调用会清除拒绝的行的列表,因此您需要在每次调用 .execute()
之后调用 .getRejects()
。由于 .start()
和 .finish()
也会调用 .execute()
以将任何挂起的流发送到服务器,因此您还应在调用这些方法之后调用 .getRejects()
。
以下示例演示了将存储在客户端系统上的五个文本文件的内容加载到表中。
import java.io.File;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import com.vertica.jdbc.VerticaConnection;
import com.vertica.jdbc.VerticaCopyStream;
public class CopyMultipleStreamsExample {
public static void main(String[] args) {
// Note: If running on Java 5, you need to call Class.forName
// to manually load the JDBC driver.
// Set up the properties of the connection
Properties myProp = new Properties();
myProp.put("user", "ExampleUser"); // Must be superuser
myProp.put("password", "password123");
// When performing bulk loads, you should always disable the
// connection's AutoCommit property to ensure the loads happen as
// efficiently as possible by reusing the same COPY command and
// transaction.
myProp.put("AutoCommit", "false");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:5433/ExampleDB", myProp);
Statement stmt = conn.createStatement();
// Create a table to receive the data
stmt.execute("DROP TABLE IF EXISTS customers");
stmt.execute("CREATE TABLE customers (Last_Name char(50), "
+ "First_Name char(50),Email char(50), "
+ "Phone_Number char(15))");
// Prepare the query to insert from a stream. This query must use
// the COPY statement to load data from STDIN. Unlike copying from
// a file on the host, you do not need superuser privileges to
// copy a stream. All your user account needs is INSERT privileges
// on the target table.
String copyQuery = "COPY customers FROM STDIN "
+ "DELIMITER '|' ENFORCELENGTH";
// Create an instance of the stream class. Pass in the
// connection and the query string.
VerticaCopyStream stream = new VerticaCopyStream(
(VerticaConnection) conn, copyQuery);
// Keep running count of the number of rejects
int totalRejects = 0;
// start() starts the stream process, and opens the COPY command.
stream.start();
// If you added streams to VerticaCopyStream before calling start(),
// You should check for rejects here (see below). The start() method
// calls execute() to send any pre-queued streams to the server
// once the COPY statement has been created.
// Simple for loop to load 5 text files named customers-1.txt to
// customers-5.txt
for (int loadNum = 1; loadNum <= 5; loadNum++) {
// Prepare the input file stream. Read from a local file.
String filename = "C:\\Data\\customers-" + loadNum + ".txt";
System.out.println("\n\nLoading file: " + filename);
File inputFile = new File(filename);
FileInputStream inputStream = new FileInputStream(inputFile);
// Add stream to the VerticaCopyStream
stream.addStream(inputStream);
// call execute() to load the newly added stream. You could
// add many streams and call execute once to load them all.
// Which method you choose depends mainly on whether you want
// the ability to check the number of rejections as the load
// progresses so you can stop if the number of rejects gets too
// high. Also, high numbers of InputStreams could create a
// resource issue on your client system.
stream.execute();
// Show any rejects from this execution of the stream load
// getRejects() returns a List containing the
// row numbers of rejected rows.
List<Long> rejects = stream.getRejects();
// The size of the list gives you the number of rejected rows.
int numRejects = rejects.size();
totalRejects += numRejects;
System.out.println("Number of rows rejected in load #"
+ loadNum + ": " + numRejects);
// List all of the rows that were rejected.
Iterator<Long> rejit = rejects.iterator();
long linecount = 0;
while (rejit.hasNext()) {
System.out.print("Rejected row #" + ++linecount);
System.out.println(" is row " + rejit.next());
}
}
// Finish closes the COPY command. It returns the number of
// rows inserted.
long results = stream.finish();
System.out.println("Finish returned " + results);
// If you added any streams that hadn't been executed(),
// you should also check for rejects here, since finish()
// calls execute() to
// You can also get the number of rows inserted using
// getRowCount().
System.out.println("Number of rows accepted: "
+ stream.getRowCount());
System.out.println("Total number of rows rejected: " + totalRejects);
// Commit the loaded data
conn.commit();
} catch (Exception e) {
e.printStackTrace();
}
}
}
对部分示例数据运行以上示例后,将生成以下输出:
Loading file: C:\Data\customers-1.txtNumber of rows rejected in load #1: 3
Rejected row #1 is row 3
Rejected row #2 is row 7
Rejected row #3 is row 51
Loading file: C:\Data\customers-2.txt
Number of rows rejected in load #2: 5Rejected row #1 is row 4143
Rejected row #2 is row 6132
Rejected row #3 is row 9998
Rejected row #4 is row 10000
Rejected row #5 is row 10050
Loading file: C:\Data\customers-3.txt
Number of rows rejected in load #3: 9
Rejected row #1 is row 14142
Rejected row #2 is row 16131
Rejected row #3 is row 19999
Rejected row #4 is row 20001
Rejected row #5 is row 20005
Rejected row #6 is row 20049
Rejected row #7 is row 20056
Rejected row #8 is row 20144
Rejected row #9 is row 20236
Loading file: C:\Data\customers-4.txt
Number of rows rejected in load #4: 8
Rejected row #1 is row 23774
Rejected row #2 is row 24141
Rejected row #3 is row 25906
Rejected row #4 is row 26130
Rejected row #5 is row 27317
Rejected row #6 is row 28121
Rejected row #7 is row 29321
Rejected row #8 is row 29998
Loading file: C:\Data\customers-5.txt
Number of rows rejected in load #5: 1
Rejected row #1 is row 39997
Finish returned 39995
Number of rows accepted: 39995
Total number of rows rejected: 26
注意
以上示例显示了以 Vertica 群集中的一个节点作为目标的简单加载过程。同时将多个流加载到多个数据库节点更高效。这样做可以显著提高性能,因为这样会使对加载的处理分布到群集中。
3.3.6.4.2 - 将 COPY LOCAL 与 JDBC 配合使用
若要将 COPY LOCAL 与 JDBC 结合使用,只需执行 COPY LOCAL 语句并指定客户端系统上源文件的路径即可。此方法比使用 VerticaCopyStream
类更简单(有关该类的详细信息,请参阅
JDBC 文档)。但是,如果有许多文件要复制到数据库,或者如果数据来自某个文件以外的其他源(例如,通过网络连接进行流式传输),您可能倾向于使用 VerticaCopyStream
。
可以在多语句查询中使用 COPY LOCAL。但是,您应当始终将其作为查询中的第一条语句。不应当在同一个查询中多次使用它。
以下示例代码演示了使用 COPY LOCAL 将文件从客户端复制到数据库。除了在 COPY 语句中使用 LOCAL 选项,并且数据文件的路径位于客户端系统而非服务器上,此示例与使用 COPY 语句进行批量加载中所示的代码相同。
注意
当在 copy local 命令中指定了例外和拒绝的数据修饰符时,将在客户端计算机上创建例外/拒绝文件。从驱动程序执行 COPY LOCAL 查询时,请为这些修饰符指定本地路径和文件名。
import java.sql.*;
import java.util.Properties;
public class COPYLocal {
public static void main(String[] args) {
// Note: If using Java 5, you must call Class.forName to load the
// JDBC driver.
Properties myProp = new Properties();
myProp.put("user", "ExampleUser"); // Do not need to superuser
myProp.put("password", "password123");
Connection conn;
try {
conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:5433/ExampleDB",myProp);
// Disable AutoCommit
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
// Create a table to hold data.
stmt.execute("DROP TABLE IF EXISTS customers;");
stmt.execute("CREATE TABLE IF NOT EXISTS customers (Last_Name char(50) "
+ "NOT NULL, First_Name char(50),Email char(50), "
+ "Phone_Number char(15))");
// Use the COPY command to load data. Load directly into ROS, since
// this load could be over 100MB. Use ENFORCELENGTH to reject
// strings too wide for their columns.
boolean result = stmt.execute("COPY customers FROM LOCAL "
+ " 'C:\\Data\\customers.txt' DIRECT ENFORCELENGTH");
// Determine if execution returned a count value, or a full result
// set.
if (result) {
System.out.println("Got result set");
} else {
// Count will usually return the count of rows inserted.
System.out.println("Got count");
int rowCount = stmt.getUpdateCount();
System.out.println("Number of accepted rows = " + rowCount);
}
conn.close();
} catch (SQLException e) {
System.out.print("Error: ");
System.out.println(e.toString());
}
}
}
运行此代码后的结果如下所示。在此示例中,customers.txt 文件包含 10000 行,其中七个行被拒绝,因为这些行所包含的数据的宽度太大而无法适应其数据库列。
Got countNumber of accepted rows = 9993
3.3.7 - 处理错误
当 Vertica JDBC 驱动程序遇到错误时,它会引发 SQLException
或其子类之一。所引发的特定子类取决于已发生的错误的类型。大部分 JDBC 方法调用会导致发生多种不同类型的错误,JDBC 驱动程序将引发特定的 SQLException
子类以响应这些错误。客户端应用程序可以根据 JDBC 驱动程序已引发的特定异常选择如何对错误做出响应。
注意
JDBC 4.0 标准中引入了特定的 SQLException
子类。如果客户端应用程序在 Java 5 JVM 中运行,它将使用符合 JDBC 3.0 的较旧版本驱动程序,而这些驱动程序缺少前述子类。在这种情况下,所有错误都将引发 SQLException
。
SQLException
子类的层次结构已进行了排列,可帮助客户端应用程序确定采取哪些措施以对错误条件做出响应。例如:
-
当错误原因可能是临时条件(例如,超时错误 (SQLTimeoutException
) 或连接问题 (SQLTransientConnectionIssue
))时,JDBC 驱动程序将引发 SQLTransientException
子类。客户端应用程序可以选择重试操作而不进行任何尝试以纠正错误,因为该错误可能不会再次发生。
-
当客户端需要采取某种措施以便能够重试操作时,JDBC 驱动程序将引发 SQLNonTransientException
子类。例如,执行包含 SQL 语法错误的语句会导致 JDBC 驱动程序引发 SQLSyntaxErrorException
(SQLNonTransientException
的子类)。通常,客户端应用程序只需要将这些错误报告回给用户并让用户解决错误。例如,如果用户向应用程序提供的 SQL 语句触发了 SQLSyntaxErrorException
,则应用程序会提示用户修复 SQL 错误。
有关由 JDBC 驱动程序引发的 Java 异常的列表,请参阅 SQLState 和 Java 异常类的映射。
3.3.7.1 - SQLState 和 Java 异常类的映射
3.3.8 - 将 JDBC 查询直接路由到单个节点
JDBC 驱动程序能够使用名为可路由连接的特殊连接将查询直接路由到单个节点。此功能适用于大量的“短”请求,此类请求将返回全部存在于单个节点上的少量结果。此功能的常见使用场景是对由唯一键标识的数据执行大量查找。与分布式查询相比,可路由查询通常提供更低延迟并使用更少系统资源。但是,必须以某种方式对要查询的数据进行分段,以使 JDBC 客户端能够确定数据驻留在哪个节点。
Vertica 典型分析查询
典型分析查询需要对群集中所有节点上的数据执行密集计算,并且通过让所有节点参与查询的规划和执行来获益。
Vertica 可路由查询 API 查询
对于返回单个或几个数据行的大量查询,在包含数据的单个节点上执行查询更高效。
为有效地将请求路由到单个节点,客户端必须确定数据所驻留在的特定节点。为使客户端能够确定正确节点,必须按一个或多个列对表进行分段。例如,如果按主键 (PK) 列对表进行分段,则客户端可以基于主键确定数据驻留在哪个节点,并直接连接到该节点以快速完成请求。
可路由查询 API 提供以下两个用于执行可路由查询的类:VerticaRoutableExecutor 和 VGet。VerticaRoutableExecutor 提供更富有表现力的基于 SQL 的 API,而 VGet 提供结构更完善的 API 用于编程访问。
-
通过 VerticaRoutableExecutor 类,您可以将传统 SQL 与减少的功能集结合使用,以在单个节点上查询数据。
对于联接,表必须按存在于要联接的每个表中的键列进行联接,并且这些表必须按该键进行分段。但是,此限制对未分段的表不适用,这种表始终可以联接(因为未分段的表中的所有数据在所有节点上均可用)。
-
VGet 类不使用传统 SQL 语法。相反,此类使用您通过定义谓词而构建的数据结构,还使用谓词表达式以及输出和输出表达式。此类适用于对单个表执行键/值类型查找。
用于查询表的数据结构必须为该表的投影中定义的每个已分段的列提供一个谓词。您至少必须为每个已分段的列提供一个具有常量值的谓词。例如,如果表仅按 id
列进行分段,请提供具有值 12234 的 id
。您还可以为表中其他未分段的列指定附加谓词。谓词的作用类似于 SQL WHERE 子句,多个谓词/谓词表达式通过 SQL AND 修饰符一起应用。必须为谓词定义常量值。谓词表达式可用于细化查询,并且可以包含任意 SQL 表达式(例如,小于和大于等)以用于表中未分段的列。
Vertica
JDBC 文档中提供了适用于 JDBC 驱动程序中所有类和方法的 Java 文档。
注意
JDBC 可路由查询 API 是只读的,并且需要 JDK 1.6 或更高版本。
3.3.8.1 - 创建与可路由查询 API 一起使用的表和投影
对于可路由查询,客户端必须确定适用于获取数据的节点。客户端通常以下方法执行此操作:比较对表可用的所有投影,然后确定可用于查找包含数据的单个节点的最佳投影。您必须在至少一个表上创建按键列分段的投影,以充分利用可路由查询 API。联接到此表的其他表必须具有未分段投影,或者必须具有按下文所属进行分段的投影。
注意
可路由查询的表必须用哈希值分段。请参阅
哈希分段子句。其他分段类型不受支持。
创建与可路由查询结合使用的表
若要创建可以与可路由查询 API 结合使用的表,请按均匀分布的列对表进行分段(使用哈希算法)。通常,可以按主键进行分段。为提高查找速度,请按用作分段依据的相同列对投影进行排序。例如,若要创建非常适用于可路由查询的表,请执行下列操作:
CREATE TABLE users (
id INT NOT NULL PRIMARY KEY,
username VARCHAR(32),
email VARCHAR(64),
business_unit VARCHAR(16))
ORDER BY id
SEGMENTED BY HASH(id)
ALL NODES;
此表基于 id
列进行分段(并按 id
进行排序以提高查找速度)。若要使用可路由查询 API 为此表构建查询,您只需要为 id
列提供单个谓词,当您查询该列时,将返回单个行。
不过,您可以向分段子句添加多个列。例如:
CREATE TABLE users2 (
id INT NOT NULL PRIMARY KEY,
username VARCHAR(32),
email VARCHAR(64),
business_unit VARCHAR(16))
ORDER BY id, business_unit
SEGMENTED BY HASH(id, business_unit)
ALL NODES;
在本例中,您需要在查询 users2
表时提供两个谓词,因为它按以下两列分段:id
和 business_unit
。但是,如果您在执行查询时知道 id
和 business_unit
,则同时按这两个列进行分段会有帮助,因为这样可使客户端更容易确定该投影是可用于确定正确节点的最佳投影。
针对单节点 JOIN 设计表
如果打算在可路由查询期间使用 VerticaRoutableExecutor 类并联接表,则您必须按同一个分段键对要联接的所有表进行分段。通常,此键是要联接的所有表上的主键/外键。例如,customer_key 可能是客户维度表中的主键,并且同一个键是销售事实数据表中的外键。使用这些表的 VerticaRoutableExecutor 查询的投影必须使用哈希算法按每个表中的客户键进行分段。
如果要与小型维度表(例如日期维度)联接,则使这些表保持未分段可能是合适做法,以使 date_dimension
数据存在于所有节点上。请务必注意,在联接未分段的表时,您仍必须在 createRoutableExecutor()
调用中指定已分段的表。
验证表的现有投影
如果已使用哈希算法对表进行分段(例如按 ID 列),则您可以通过使用 Vertica 函数
GET_PROJECTIONS
查看该表的投影,确定需要使用哪些谓词来查询该表。例如:
=> SELECT GET_PROJECTIONS ('users');
...
Projection Name: [Segmented] [Seg Cols] [# of Buddies] [Buddy Projections] [Safe] [UptoDate] [Stats]
----------------------------------------------------------------------------------------------------
public.users_b1 [Segmented: Yes] [Seg Cols: "public.users.id"] [K: 1] [public.users_b0] [Safe: Yes] [UptoDate: Yes] [Stats: RowCounts]
public.users_b0 [Segmented: Yes] [Seg Cols: "public.users.id"] [K: 1] [public.users_b1] [Safe: Yes] [UptoDate: Yes] [Stats: RowCounts]
对于每个投影,仅指定 public.users.id
列,表示您的查询谓词应包含此列。
如果表按多个列(例如 id
和 business_unit
)进行分段,则您需要将这两个列作为谓词提供给可路由查询。
3.3.8.2 - 为可路由查询创建连接
JDBC 可路由查询 API 提供了 VerticaRoutableConnection 接口(
JDBC 文档中提供了详细信息),此接口可用于连接到群集以及启用可路由查询。除了普通 VerticaConnection 所提供的功能之外,此接口还提供高级路由功能。VerticaRoutableConnection 提供对 VerticaRoutableExecutor 类和 VGet 类的访问。请分别参阅使用 VerticaRoutableExecutor 类为可路由查询定义查询和使用 VGet 类为可路由查询定义查询。
可以通过将 EnableRoutableQueries
JDBC 连接属性设置为 true 来启用对此类的访问。
VerticaRoutableConnection 维护内部连接池和表元数据缓存(由连接的 createRoutableExecutor()/prepareGet()
方法所生成的所有 VerticaRoutableExecutor/VGet 对象共享)。此接口还是一个完全开发的独立 JDBC 连接,并且支持 VerticaConnection 所支持的所有功能。当此连接关闭时,由此 VerticaRoutableConnection 管理的所有池连接和所有子对象也会关闭。连接池和元数据仅由子可路由查询操作使用。
例如:
您可以使用 JDBC 数据源创建连接:
com.vertica.jdbc.DataSource jdbcSettings = new com.vertica.jdbc.DataSource();
jdbcSettings.setDatabase("exampleDB");
jdbcSettings.setHost("v_vmart_node0001.example.com");
jdbcSettings.setUserID("dbadmin");
jdbcSettings.setPassword("password");
jdbcSettings.setEnableRoutableQueries(true);
jdbcSettings.setPort((short) 5433);
VerticaRoutableConnection conn;
conn = (VerticaRoutableConnection)jdbcSettings.getConnection();
您还可以使用连接字符串和 DriverManager.getConnection()
方法创建连接:
String connectionString = "jdbc:vertica://v_vmart_node0001.example.com:5433/exampleDB?user=dbadmin&password=&EnableRoutableQueries=true";
VerticaRoutableConnection conn = (VerticaRoutableConnection) DriverManager.getConnection(connectionString);
以上两种方法生成相同的 conn
连接对象。
注意
请避免打开许多 VerticaRoutableConnection
连接,因为此连接维护自己的专用连接池(不与其他连接共享)。相反,应用程序应使用单个连接,并通过该连接发出多个查询。
除了由可路由查询 API 添加到 Vertica JDBC 连接类的 setEnableRoutableQueries
属性之外,该 API 还添加了其他属性。完整列表如下。
-
EnableRoutableQueries
:启用可路由查询查找功能。默认值为 false。
-
FailOnMultiNodePlans
:如果计划需要多个节点,并且 FailOnMultiNodePlans 设置为 true,查询将失败。如果此属性设置为 false,则会生成警告,并且查询会继续执行。但是,延迟会显著提高,因为可路由查询必须先确定数据是否位于多个节点上,然后使用传统执行(在所有节点上)来运行普通查询。默认值为 true。请注意,仅使用谓词和常量值的简单调用不会出现此失败。
-
MetadataCacheLifetime
:保留投影元数据的时间(以秒为单位)。API 缓存有关投影的元数据以用于查询(例如投影)。后续的查询使用该缓存来缩短响应时间。默认值为 300 秒。
-
MaxPooledConnections
:保留在 VerticaRoutableConnection 内部池中的最大连接数(群集范围)。默认值 20。
-
MaxPooledConnectionsPerNode
:保留在 VerticaRoutableConnection 内部池中的最大连接数(每节点)。默认值 5。
3.3.8.3 - 使用 VerticaRoutableExecutor 类为可路由查询定义查询
使用 VerticaRoutableExecutor
类从单个节点直接访问表数据。 VerticaRoutableExecutor
仅在包含查询所需的所有数据的节点上直接查询 Vertica,从而避免产生与 Vertica 查询执行关联的分布式规划和执行成本。您可以使用 VerticaRoutableExecutor
联接表或使用 GROUP BY 子句,因为使用 VGet 无法执行这些操作。
使用 VerticaRoutableExecutor
类时,适用以下规则:
- 如果要联接表,您必须按联接谓词中引用的相同列集对要联接的所有表进行分段(使用哈希算法),除非要联接的表未分段。
- 联接 WHERE 子句中的多个条件必须使用 AND 联接在一起。在 WHERE 子句中使用 OR 会导致查询退化成多节点计划。如果数据存在于同一节点上,则可以在联接条件 outside 列上指定 OR、IN 列表或范围条件。
- 每个请求只能执行单个语句。不允许使用链接的 SQL 语句。
- 在由驱动程序生成的子查询中使用您的查询有助于确定该查询是否可以在单个节点上执行。因此,不能在语句的结尾包含分号,也不能包含使用双破折号的 SQL 注释 (
--
),因为这些会导致由驱动程序生成的查询失败。
可以通过对连接对象调用 createRoutableExecutor 方法来创建 VerticaRoutableExecutor。
createRoutableExecutor( schema‑name, table‑name )
例如:
VerticaRoutableConnection conn;
Map<String, Object> map = new HashMap<String, Object>();
map.put("customer_key", 1);
try {
conn = (VerticaRoutableConnection)
jdbcSettings.getConnection();
String table = "customers";
VerticaRoutableExecutor q = conn.createRoutableExecutor(null, table);
...
}...
如果 schema‑name 设置为空,则会使用搜索路径来查找表。
VerticaRoutableExecutor 方法
VerticaRoutableExecutor
包含以下方法:
有关此类的详细信息,请参阅
JDBC 文档。
执行
execute( query-string, { column, value | map } )
运行查询。
需要满足以下要求:
-
要执行的查询必须使用符合 VerticaRoutableExecutor
类规则的常规 SQL。例如,假如数据存在于单个节点上,则您可以添加限制和排序或使用聚合函数。
-
JDBC 客户端使用列/值或映射实参来确定应在哪个节点上执行查询。查询的内容必须使用您在列/值或映射实参中提供的相同值。
-
不能使用以下数据类型作为列值: * INTERVAL * TIMETZ * TIMESTAMPTZ
此外,如果表在具有以下数据类型的任何列上分段,则无法使用可路由查询 API 查询此表:
将查询发送到服务器之前,驱动程序不会验证查询的语法。如果表达式不正确,则查询会失败。
关闭
close()
通过释放由此 VerticaRoutableExecutor
使用的资源来关闭此 VerticaRoutableExecutor
。它不会关闭与 Vertica 的父 JDBC 连接。
getWarnings
getWarnings()
检索对此 VerticaRoutableExecutor
的调用所报告的第一个警告。其他警告均为链式,可使用 JDBC 方法 getNextWarning()
访问。
示例
以下示例显示如何使用 VerticaRoutableExecutor
执行同时使用 JOIN 子句和带有 GROUP BY 子句的聚合函数的查询。此示例还显示如何创建客户表和销售表,并对这些表进行分段,以便可以使用 VerticaRoutableExecutor
类联接这些表。此示例使用 VMart 架构中的 date_dimension
表来显示如何联接未分段的表上的数据。
-
创建 customers
表以存储客户详细信息,然后创建在表的 customer_key
列上分段的投影:
=> CREATE TABLE customers (customer_key INT, customer_name VARCHAR(128), customer_email VARCHAR(128));
=> CREATE PROJECTION cust_proj_b0 AS SELECT * FROM customers SEGMENTED BY HASH (customer_key) ALL NODES;
=> CREATE PROJECTION cust_proj_b1 AS SELECT * FROM customers SEGMENTED BY HASH (customer_key) ALL NODES OFFSET 1;
=> CREATE PROJECTION cust_proj_b2 AS SELECT * FROM customers SEGMENTED BY HASH (customer_key) ALL NODES OFFSET 2;
=> SELECT start_refresh();
-
创建 sales
表,然后创建在其 customer_key
列上分段的投影。由于 customer
和 sales
表按同一个键进行分段,因此您稍后可以使用 VerticaRoutableExecutor
可路由查询查找来联接这两个表。
=> CREATE TABLE sales (sale_key INT, customer_key INT, date_key INT, sales_amount FLOAT);
=> CREATE PROJECTION sales_proj_b0 AS SELECT * FROM sales SEGMENTED BY HASH (customer_key) ALL NODES;
=> CREATE PROJECTION sales_proj_b1 AS SELECT * FROM sales SEGMENTED BY HASH (customer_key) ALL NODES OFFSET 1;
=> CREATE PROJECTION sales_proj_b2 AS SELECT * FROM sales SEGMENTED BY HASH (customer_key) ALL NODES OFFSET 2;
=> SELECT start_refresh();
-
添加一些示例数据:
=> INSERT INTO customers VALUES (1, 'Fred', 'fred@example.com');
=> INSERT INTO customers VALUES (2, 'Sue', 'Sue@example.com');
=> INSERT INTO customers VALUES (3, 'Dave', 'Dave@example.com');
=> INSERT INTO customers VALUES (4, 'Ann', 'Ann@example.com');
=> INSERT INTO customers VALUES (5, 'Jamie', 'Jamie@example.com');
=> COMMIT;
=> INSERT INTO sales VALUES(1, 1, 1, '100.00');
=> INSERT INTO sales VALUES(2, 2, 2, '200.00');
=> INSERT INTO sales VALUES(3, 3, 3, '300.00');
=> INSERT INTO sales VALUES(4, 4, 4, '400.00');
=> INSERT INTO sales VALUES(5, 5, 5, '400.00');
=> INSERT INTO sales VALUES(6, 1, 15, '500.00');
=> INSERT INTO sales VALUES(7, 1, 15, '400.00');
=> INSERT INTO sales VALUES(8, 1, 35, '300.00');
=> INSERT INTO sales VALUES(9, 1, 35, '200.00');
=> COMMIT;
-
创建 VMart date_dimension
表的未分段投影以在此示例中使用。调用元函数 START_REFRESH 来取消现有数据的分段:
=> CREATE PROJECTION date_dim AS SELECT * FROM date_dimension UNSEGMENTED ALL NODES;
=> SELECT start_refresh();
现在,您可以使用 customer
、sales
和 date_dimension
数据创建如下可路由查询查找:使用联接和 group by 子句查询客户表并返回给定客户每天的总购买量:
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import com.vertica.jdbc.kv.*;
public class verticaKV_doc {
public static void main(String[] args) {
com.vertica.jdbc.DataSource jdbcSettings
= new com.vertica.jdbc.DataSource();
jdbcSettings.setDatabase("VMart");
jdbcSettings.setHost("vertica.example.com");
jdbcSettings.setUserID("dbadmin");
jdbcSettings.setPassword("password");
jdbcSettings.setEnableRoutableQueries(true);
jdbcSettings.setFailOnMultiNodePlans(true);
jdbcSettings.setPort((short) 5433);
VerticaRoutableConnection conn;
Map<String, Object> map = new HashMap<String, Object>();
map.put("customer_key", 1);
try {
conn = (VerticaRoutableConnection)
jdbcSettings.getConnection();
String table = "customers";
VerticaRoutableExecutor q = conn.createRoutableExecutor(null, table);
String query = "select d.date, SUM(s.sales_amount) as Total ";
query += " from customers as c";
query += " join sales as s ";
query += " on s.customer_key = c.customer_key ";
query += " join date_dimension as d ";
query += " on d.date_key = s.date_key ";
query += " where c.customer_key = " + map.get("customer_key");
query += " group by (d.date) order by Total DESC";
ResultSet rs = q.execute(query, map);
while(rs.next()) {
System.out.print("Date: " + rs.getString("date") + ": ");
System.out.println("Amount: " + rs.getString("Total"));
}
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
示例代码生成如下输出:
Date: 2012-01-15: Amount: 900.0
Date: 2012-02-04: Amount: 500.0
Date: 2012-01-01: Amount: 100.0
注意
您的输出可能会有所不同,因为 VMart 架构会在 date_dimension
表中随机生成日期。
3.3.8.4 - 使用 VGet 类为可路由查询定义查询
如果不需要联接表或使用 group by 子句,您可以使用
VGet 类从单个节点直接访问表数据。与 VerticaRoutableExecutor 一样,VGet 直接查询包含查询所需的数据的 Vertica 节点,从而避免产生与正常的 Vertica 执行关联的分布式规划和执行成本。但是,VGet 不使用 SQL。相反,您可以定义谓词和值以对单个表执行键/值类型查找。VGet 非常适用于对单个表执行键/值类型查找。
可以通过对连接对象调用 prepareGet 方法来创建 VGet。
prepareGet( schema‑name, { table‑name | projection‑name } )
例如:
VerticaRoutableConnection conn;
try {
conn = (VerticaRoutableConnection)
jdbcSettings.getConnection();
System.out.println("Connected.");
VGet get = conn.prepareGet("public", "users");
...
}...
VGet 操作跨越多个 JDBC 连接(和多个 Vertica 会话),并且不遵循父连接的事务语义。如果需要在多个执行之间保持一致性,您可以使用父 VerticaRoutableConnection 的一致读取 API 来保证所有操作在同一时期进行。
VGet 是线程安全的,但会同步所有方法,因此共享 VGet 实例的线程永远不会并行运行。为了提高并行度,每个线程应具有各自的 VGet 实例。对同一个表执行操作的不同 VGet 实例共享池连接和元数据,以便实现较高的并行度。
VGet 方法
VGet 包含以下方法:
默认情况下,VGet 提取可满足多个谓词的逻辑 AND 的所有行的所有列,这些谓词通过 addPredicate 方法传递。您可以使用以下方法进一步自定义 get 操作:addOutputColumn、addOutputExpression、addPredicateExpression、addSortColumn 和 setLimit。
addPredicate
addPredicate(string, object)
向查询添加谓词列和常量值。必须为用作表的分段依据的每个列包含一个谓词。谓词充当查询 WHERE 子句。多个 addPredicate 方法调用由 AND 修饰符联接。每次调用并执行后,VGet 都会保留此值。要移除它,请使用 clearPredicates。
不能使用以下数据类型作为列值。同样,如果表在具有以下数据类型的任何列上分段,则不能使用可路由查询 API 查询此表:
addPredicateExpression
addPredicateExpression(string)
接受对表列执行操作的任意 SQL 表达式作为查询的输入。谓词表达式和谓词由 AND 修饰符联接。可以在谓词表达式中使用已分段的列,但您必须同时使用 addPredicate 将这些谓词表达式指定为常规谓词。每次调用并执行后,VGet 都会保留此值。要移除它,请使用 clearPredicates。
驱动程序将表达式发送至服务器之前不会验证表达式的语法。如果表达式不正确,则查询会失败。
addOutputColumn
addOutputColumn(string)
添加要包含到输出的列。默认情况下,查询作为 SELECT *
运行,并且您不需要定义任何用于返回数据的输出列。如果要添加输出列,则您必须添加要返回的所有列。每次调用并执行后,VGet 都会保留此值。要移除它,请使用 clearOutputs。
addOutputExpression
addOutputExpression(string)
接受对表列执行操作的任意 SQL 表达式作为输出。每次调用并执行后,VGet 都会保留此值。要移除它,请使用 ClearOutputs。
存在以下限制:
addSortColumn
addSortColumn(string, SortOrder)
向输出列添加排序顺序。输出列既可以是默认查询 (SELECT *) 所返回的一个列,也可以是 addSortColumn 或 addOutputExpress 中定义的列之一。可以定义多个排序列。
setLimit
setLimit(int)
对返回的结果数设置限制。值为 0 的限制表示无限制。
clearPredicates
clearPredicates()
移除由 addPredicate 和 addPredicateExpression 添加的谓词。
clearOutputs
clearOutputs()
移除由 addOutputColumn 和 addOutputExpression 添加的输出。
clearSortColumns
clearSortColumns()
移除先前由 addSortColumn 添加的排序列。
执行
execute()
运行查询。必须格外小心,以确保谓词列存在于 VGet 所使用的表和投影上,并确保表达式不需要在多个节点上执行。如果表达式由于十分复杂而需要在多个节点上执行,execute 将在 FailOnMultiNodePlans 连接属性设置为 true 时引发 SQLException。
关闭
close()
通过释放由此 VGet 使用的资源来关闭此 VGet。它不会关闭与 Vertica 的父 JDBC 连接。
getWarnings
getWarnings()
检索对此 VGet 的调用所报告的第一个警告。其他警告均为链式,可使用 JDBC 方法 getNextWarning 进行访问。
示例
以下代码查询在创建与可路由查询 API 一起使用的表和投影中定义的 users
表。该表定义了一个使用哈希算法进行分段的 id
列。
import java.sql.*;
import com.vertica.jdbc.kv.*;
public class verticaKV2 {
public static void main(String[] args) {
com.vertica.jdbc.DataSource jdbcSettings
= new com.vertica.jdbc.DataSource();
jdbcSettings.setDatabase("exampleDB");
jdbcSettings.setHost("v_vmart_node0001.example.com");
jdbcSettings.setUserID("dbadmin");
jdbcSettings.setPassword("password");
jdbcSettings.setEnableRoutableQueries(true);
jdbcSettings.setPort((short) 5433);
VerticaRoutableConnection conn;
try {
conn = (VerticaRoutableConnection)
jdbcSettings.getConnection();
System.out.println("Connected.");
VGet get = conn.prepareGet("public", "users");
get.addPredicate("id", 5);
ResultSet rs = get.execute();
rs.next();
System.out.println("ID: " +
rs.getString("id"));
System.out.println("Username: "
+ rs.getString("username"));
System.out.println("Email: "
+ rs.getString("email"));
System.out.println("Closing Connection.");
conn.close();
} catch (SQLException e) {
System.out.println("Error! Stacktrace:");
e.printStackTrace();
}
}
}
该代码将生成以下输出:
Connected.
ID: 5
Username: userE
Email: usere@example.com
Closing Connection.
3.3.8.5 - 可路由查询性能和故障排除
本主题详细介绍性能注意事项和使用可路由查询 API 时可能会遇到的常见问题。
将资源池与可路由查询结合使用
各个可路由查询将快速得到处理,因为它们直接访问单个节点并返回一个或几个数据行。但在默认情况下,Vertica 资源池对 execution parallelism
参数使用 AUTO 设置。如果将此参数设置为 AUTO,则设置由可用的 CPU 核心数决定,并通常导致对资源池中的查询进行多线程执行。在服务器上创建并行线程会降低效率,因为可路由查询操作返回数据的速度太快,并且可路由查询操作仅使用单个线程查找行。若要防止服务器打开不需要的处理线程,应为可路由查询客户端创特定的资源池。请考虑用于可路由查询的资源池的以下设置:
-
将执行并行度设置为 1,以强制执行单线程查询。此设置能够提高可路由查询的性能。
-
使用 CPU 相关性将资源池限制为某个特定 CPU 或 CPU 集。此设置不但能够确保可路由查询具有可用资源,而且能够防止可路由查询对其他常规查询的系统性能造成显著影响。
-
如果不为资源池设置 CPU 相关性,可以考虑将资源池的最大并发值设置为既能确保可路由查询性能良好又不会对常规查询的性能造成负面影响的设置。
可路由查询连接的性能注意事项
由于 VerticaRoutableConnection 将打开内部连接池,因此必须根据群集大小和同时客户端连接的数量适当地配置 MaxPooledConnections
和 MaxPooledConnectionsPerNode
。如果要使用 VerticaRoutableConnection
重载群集,则您无法影响正常的数据库连接。
与发起程序节点的初始连接可发现群集中的所有其他节点。发送 VerticaRoutableExecutor 或 VGet 查询之前,不会打开内部池连接。连接对象中的所有 VerticaRoutableExecutor/VGet 均使用来自内部池的连接,并受到 MaxPooledConnections
设置的限制。连接将保持打开,直至在已达到连接限制时关闭这些连接以便在其他位置打开新连接为止。
可路由查询故障排除
可路由查询问题通常分为以下两个类别:
谓词要求
必须提供与表(使用哈希算法进行分段)的列数相同的谓词数量。要确定分段列,请调用 Vertica 函数
GET_PROJECTIONS
。必须为 Seg Cols
字段中显示的每个列提供一个谓词。
对于 VGet,这意味着您必须使用 addPredicate()
以添加每个列。对于 VerticaRoutableExecutor,这意味着您必须在发送到 execute()
的映射中提供所有谓词和值。
多节点故障
无法定义正确数量的谓词,但仍会发生故障,因为数据包含在多个节点上。发生此故障的原因是投影的数据未以某种方式进行分段,以使要查询的数据包含在单个节点上。请为连接启用日志记录并查看日志,以验证所使用的投影。如果客户端未选择正确的投影,则会通过在 create/prepare 语句中指定投影而非表来尝试直接查询投影,例如:
此外,在 vsql 中使用 EXPLAIN 命令有助于确定查询是否可以在单个节点中运行。EXPLAIN 可以帮助了解为什么查询在单个或多个节点中运行。
3.3.8.6 - 使用 VHash 对数据进行预分段
VHash 类是 Vertica 哈希函数的实施,可与 JDBC 客户端应用程序结合使用。
使用 Vertica 中的哈希分段,您可以基于内置的哈希函数对投影进行分段。内置的哈希函数可使数据平均分布到群集中的部分或全部节点,从而提供最佳的查询执行。
假设您有数百万个值行分布在几千个 CSV 文件之中。假设您已创建了一个使用哈希算法进行分段的表。将值加载到数据库之前,您可能想要确定应将特定值加载到哪个节点。因此,使用 VHash 会特别有帮助,因为此类允许您在加载数据之前对数据进行预分段。
以下示例显示了对名为“testFile.csv”的文件的第一列使用哈希算法的 VHash 类。该文件中第一列的名称为 meterId。
使用 VHash 对数据进行分段
以下示例演示了如何从本地文件系统读取 testFile.csv 文件并对 meteterId 列运行哈希函数。然后,您可以使用投影中的数据库元数据基于 meterId 的哈希值对该文件中的各个列进行预分段。
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.io.IOException;
import java.sql.*;
import com.vertica.jdbc.kv.VHash;
public class VerticaKVDoc {
final Map<String, FileOutputStream> files;
final Map<String, List<Long>> nodeToHashList;
String segmentationMetadata;
List<String> lines;
public static void main(String[] args) throws Exception {
try {
Class.forName("com.vertica.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.err.println("Could not find the JDBC driver class.");
e.printStackTrace();
return;
}
Properties myProp = new Properties();
myProp.put("user", "username");
myProp.put("password", "password");
VerticaKVDoc ex = new VerticaKVDoc();
// Read in the data from a CSV file.
ex.readLinesFromFile("C:\\testFile.csv");
try (Connection conn = DriverManager.getConnection(
"jdbc:vertica://VerticaHost:portNumber/databaseName", myProp)) {
// Compute the hashes and create FileOutputStreams.
ex.prepareForHashing(conn);
}
// Write to files.
ex.writeLinesToFiles();
}
public VerticaKVDoc() {
files = new HashMap<String, FileOutputStream>();
nodeToHashList = new HashMap<String, List<Long>>();
}
public void prepareForHashing(Connection conn) throws SQLException,
FileNotFoundException {
// Send a query to Vertica to return the projection segments.
try (ResultSet rs = conn.createStatement().executeQuery(
"SELECT get_projection_segments('public.projectionName')")) {
rs.next();
segmentationMetadata = rs.getString(1);
}
// Initialize the data files.
try (ResultSet rs = conn.createStatement().executeQuery(
"SELECT node_name FROM nodes")) {
while (rs.next()) {
String node = rs.getString(1);
files.put(node, new FileOutputStream(node + ".csv"));
}
}
}
public void writeLinesToFiles() throws UnsupportedEncodingException,
IOException {
for (String line : lines) {
long hashedValue = VHash.hashLong(getMeterIdFromLine(line));
// Write the row data to that node's data file.
String node = VHash.getNodeFor(segmentationMetadata, hashedValue);
FileOutputStream fos = files.get(node);
fos.write(line.getBytes("UTF-8"));
}
}
private long getMeterIdFromLine(String line) {
// In our file, "meterId" is the name of the first column in the file.
return Long.parseLong(line.split(",")[0]);
}
public void readLinesFromFile(String filename) throws IOException {
lines = new ArrayList<String>();
String line;
try (BufferedReader reader = new BufferedReader(
new FileReader(filename))) {
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
}
}
3.4 - JavaScript
开源 vertica-nodejs 客户端驱动程序允许您使用 JavaScript 与数据库交互。有关详细信息,请参阅 npm 上的 vertica-nodejs 包。
3.5 - Perl
Perl 脚本可以通过使用 Perl DBI 模块和 DBD::ODBC 数据库驱动程序连接到 Vertica 的 ODBC 驱动程序接口来与 Vertica 交互。
先决条件
在创建 Perl 客户端应用程序之前,必须配置 Perl 开发环境。
3.5.1 - 配置 Perl 开发环境
Perl 包含一个数据库接口 (DBI) 模块,此接口模块可为 Perl 脚本创建与数据库交互的标准接口。此接口模块依赖数据库驱动程序 (Database Driver, DBD) 模块处理所有特定于数据库的通信任务。结果是一个接口提供了一种一致的方式以供 Perl 脚本用来与许多不同类型的数据库交互。
注意
对于 Perl ODBC 客户端,Vertica 允许分叉进程(子进程)在子进程完成并退出时断开与 Vertica 服务器的父连接。无论 Perl DBI AutoInactiveDestroy 属性的设置如何,Vertica 都允许此行为。
要更改默认设置以使 Vertica 接受 Perl DBI AutoInactiveDestroy 属性的设置,请将参数 CleanupInForkChild
添加到 vertica.ini
文件中,并将其值设置为 1。如果 Perl DBI AutoInactiveDestroy 属性设置为 1 并且 Vertica 参数 CleanupInForkChild
设置为 1,则 Vertica 不会在子进程完成时断开父连接。
Perl 脚本可以通过使用 Perl DBI 模块和 DBD::ODBC 数据库驱动程序连接到 Vertica 的 ODBC 驱动程序接口来与 Vertica 交互。有关详细文档,请参阅 Perl 的 DBI 模块和 DBD::ODBC 模块的 CPAN 页面。
Perl 开发环境依赖于 Vertica ODBC 驱动程序以及 DBI 和 DBD::ODBC 模块。
-
安装和配置 ODBC。
-
使用以下命令验证是否安装了 Perl。如果此命令不返回版本信息,您必须安装 Perl。有关版本支持,请参阅 Perl 驱动程序要求。
$ perl -v
-
安装 Perl 模块 DBI 和 DBD::ODBC 的兼容版本。安装方法因环境不同而异。有关安装 Perl 模块的详细信息,请参阅 cpan 文档。
-
运行以下命令以验证是否已安装 DBI 和 DBD::ODBC。如果已安装,这些命令不应返回任何内容。否则,它们会返回错误:
$ perl -e "use DBI;"
$ perl -e "use DBD::ODBC;"
列出 DSN 并验证安装
验证安装的另一种方法是使用以下 Perl 脚本。此脚本验证是否已安装 DBI 和 DBD::ODBC 并输出您的 ODBC DSN(如果有):
#!/usr/bin/perl
use strict;
# Attempt to load the DBI module in an eval using require. Prevents
# script from erroring out if DBI is not installed.
eval
{
require DBI;
DBI->import();
};
if ($@) {
# The eval failed, so DBI must not be installed
print "DBI module is not installed\n";
} else {
# Eval was successful, so DBI is installed
print "DBI Module is installed\n";
# List the drivers that DBI knows about.
my @drivers = DBI->available_drivers;
print "Available Drivers: \n";
foreach my $driver (@drivers) {
print "\t$driver\n";
}
# See if DBD::ODBC is installed by searching driver array.
if (grep {/ODBC/i} @drivers) {
print "\nDBD::ODBC is installed.\n";
# List the ODBC data sources (DSNs) defined on the system
print "Defined ODBC Data Sources:\n";
my @dsns = DBI->data_sources('ODBC');
foreach my $dsn (@dsns) {
print "\t$dsn\n";
}
} else {
print "DBD::ODBC is not installed\n";
}
}
如果您的系统配置正确,则输出应类似如下内容:
DBI Module is installed
Available Drivers:
ADO
DBM
ExampleP
File
Gofer
ODBC
Pg
Proxy
SQLite
Sponge
mysql
DBD::ODBC is installed.
Defined ODBC Data Sources:
dbi:ODBC:dBASE Files
dbi:ODBC:Excel Files
dbi:ODBC:MS Access Database
dbi:ODBC:VerticaDSN
3.5.2 - 使用 Perl 连接到 Vertica
可以使用 Perl DBI 模块的 connect
函数连接到 Vertica。此函数使用必需的数据源字符串参数,以及用于用户名、密码和连接属性的可选参数。
数据源字符串必须以 "dbi:ODBC:" 开头,以指示 DBI 模块使用 DBD::ODBC 驱动程序来连接到 Vertica。该字符串的剩余部分由 DBD::ODBC 驱动程序解释。该字符串通常包含 DSN 的名称(包含连接到 Vertica 数据库所需的连接信息)。例如,若要指示 DBD::ODBC 驱动程序使用名为 VerticaDSN 的 DSN,您应使用如下所示的数据源字符串:
"dbi:ODBC:VerticaDSN"
用户名参数和密码参数是可选的。但是,如果不提供这两个参数(或者仅提供免密码帐户的用户名)并且未在 DSN 中设置这两个参数,则连接尝试将始终失败。
connect
函数将在连接到 Vertica 后返回数据库句柄。如果未连接,则会返回 undef
。在这种情况下,您可以访问 DBI 模块的错误字符串属性 ($DBI::errstr
) 以获取错误消息。
注意
默认情况下,只要遇到错误,DBI 模块就会将错误消息输出到 STDERR。如果您倾向于显示自己的错误消息或以某种其他方式处理错误,您可能需要通过将 DBI 的 PrintError 连接属性设置为 false 来禁用这些自动消息。有关详细信息,请参阅
设置 Perl DBI 连接属性。否则,用户可能会看到两条错误消息:一条是 DBI 自动输出的错误消息,另一条是脚本自己输出的错误消息。
以下示例演示了使用名为 VerticaDSN 的 DSN 连接到 Vertica。对 connect
的调用提供了用户名和密码。连接后,此示例将调用数据库句柄的 disconnect
函数以关闭连接:
#!/usr/bin/perl -w
use strict;
use DBI;
# Open a connection using a DSN. Supply the username and password.
my $dbh = DBI->connect("dbi:ODBC:VerticaDSN","ExampleUser","password123");
unless (defined $dbh) {
# Conection failed.
die "Failed to connect: $DBI::errstr";
}
print "Connected!\n";
$dbh->disconnect();
3.5.2.1 - 在 Perl 中设置 ODBC 连接参数
若要设置 ODBC 连接参数,请在源数据字符串中将 DSN 名称替换为参数名称/值对的分号分隔列表。可以使用 DSN 参数向 DBD::ODBC 说明应使用哪个 DSN,然后添加要设置的其他 ODBC 参数。例如,以下代码使用名为 VerticaDSN 的 DSN 进行连接并将该连接的区域设置设置为 en_GB。
#!/usr/bin/perl -w
use strict;
use DBI;
# Instead of just using the DSN name, use name and value pairs.
my $dbh = DBI->connect("dbi:ODBC:DSN=VerticaDSN;Locale=en_GB@collation=binary","ExampleUser","password123");
unless (defined $dbh) {
# Conection failed.
die "Failed to connect: $DBI::errstr";
}
print "Connected!\n";
$dbh->disconnect();
有关可在源数据字符串中设置的连接参数的列表,请参阅 ODBC DSN 连接属性。
3.5.2.2 - 设置 Perl DBI 连接属性
Perl DBI 模块包含一些属性,您可以使用这些属性控制其数据库连接的行为。这些属性与 ODBC 连接参数相似(在某些情况下,它们复制彼此的功能)。DBI 连接属性是一种用于控制数据库连接的行为的跨平台方法。
可以在建立连接时通过向 DBI connect
函数传递包含属性/值对的哈希值来设置 DBI 连接属性。例如,若要将 DBI 连接属性 AutoCommit 设置为 false,您应使用以下语句:
# Create a hash that holds attributes for the connection
my $attr = {AutoCommit => 0};
# Open a connection using a DSN. Supply the username and password.
my $dbh = DBI->connect("dbi:ODBC:VerticaDSN","ExampleUser","password123",
$attr);
有关可在数据库连接上设置的属性的完整描述,请参阅 DBI 文档的 Database Handle Attributes(“数据库句柄属性”)一节。
脚本已连接之后,它可以通过将数据库句柄用作哈希引用来访问和修改连接属性。例如:
print "The AutoCommit attribute is: " . $dbh->{AutoCommit} . "\n";
以下示例演示了设置两个连接属性:
-
RaiseError 控制 DBI 驱动程序是否在遇到数据库错误时生成 Perl 错误。通常,如果希望 Perl 脚本在遇到数据库错误时退出,您可以将此属性设置为 true (1)。
-
AutoCommit 控制语句是否在完成后自动提交其事务。DBI 默认设置为 Vertica 的 AutoCommit 默认值 (true)。批量加载数据时,始终应将 AutoCommit 设置为 false (0) 以提高数据库效率。
#!/usr/bin/perl
use strict;
use DBI;
# Create a hash that holds attributes for the connection
my $attr = {
RaiseError => 1, # Make database errors fatal to script
AutoCommit => 0, # Prevent statements from committing
# their transactions.
};
# Open a connection using a DSN. Supply the username and password.
my $dbh = DBI->connect("dbi:ODBC:VerticaDSN","ExampleUser","password123",
$attr);
if (defined $dbh->err) {
# Connection failed.
die "Failed to connect: $DBI::errstr";
}
print "Connected!\n";
# The database handle lets you access the connection attributes directly:
print "The AutoCommit attribute is: " . $dbh->{AutoCommit} . "\n";
print "The RaiseError attribute is: " . $dbh->{RaiseError} . "\n";
# And you can change values, too...
$dbh->{AutoCommit} = 1;
print "The AutoCommit attribute is now: " . $dbh->{AutoCommit} . "\n";
$dbh->disconnect();
运行该示例后,将输出以下内容:
Connected!The AutoCommit attribute is: 0
The RaiseError attribute is: 1
The AutoCommit attribute is now: 1
3.5.2.3 - 不使用 DSN 从 Perl 进行连接
如果不想为数据库设置数据源名称 (DSN),您可以在数据源字符串中提供 Perl 的 DBD::ODBC 驱动程序连接到 Vertica 数据库所需的所有信息。此源字符串必须包含 DRIVER=
参数,以向 DBD::ODBC 说明应使用哪个驱动程序库进行连接。此参数的值是客户端系统的驱动程序管理器分配给驱动程序的名称。
-
在 Windows 上,驱动程序管理器分配给 Vertica ODBC 驱动程序的名称是 Vertica。
-
在 Linux 和其他类 UNIX 操作系统上,Vertica ODBC 驱动程序的名称在系统的 odbcinst.ini
文件中分配。例如,如果 /etc/odbcint.ini
包含以下内容:
[Vertica]
Description = Vertica ODBC Driver
Driver = /opt/vertica/lib64/libverticaodbc.so
您应使用名称 Vertica。有关 odbcinst.ini
文件的详细信息,请参阅为 Linux 创建 ODBC DSN。
可以在字符串中利用 Perl 的变量扩展,以将变量用于大部分连接属性,如以下示例所示。
#!/usr/bin/perl
use strict;
use DBI;
my $server='VerticaHost';
my $port = '5433';
my $database = 'VMart';
my $user = 'ExampleUser';
my $password = 'password123';
# Connect without a DSN by supplying all of the information for the connection.
# The DRIVER value on UNIX platforms depends on the entry in the odbcinst.ini
# file.
my $dbh = DBI->connect("dbi:ODBC:DRIVER={Vertica};Server=$server;" .
"Port=$port;Database=$database;UID=$user;PWD=$password")
or die "Could not connect to database: " . DBI::errstr;
print "Connected!\n";
$dbh->disconnect();
注意
在源字符串中用大括号({ 和 })将驱动程序名称括起来是可选的。
3.5.3 - 使用 Perl 执行语句
Perl 脚本已连接到 Vertica(请参阅使用 Perl 连接到 Vertica)之后,它可以使用 Perl DBI 模块的 do
函数执行将返回值而非结果集的简单语句。通常可以使用此函数执行 DDL 语句或诸如 COPY 等数据加载语句(请参阅在 Perl 中使用 COPY LOCAL 加载数据)。
#!/usr/bin/perl
use strict;
use DBI;
# Disable autocommit
my $attr = {AutoCommit => 0};
# Open a connection using a DSN.
my $dbh = DBI->connect("dbi:ODBC:VerticaDSN","ExampleUser","password123",
$attr);
unless (defined $dbh) {
# Conection failed.
die "Failed to connect: $DBI::errstr";
}
# You can use the do function to perform DDL commands.
# Drop any existing table.
$dbh->do("DROP TABLE IF EXISTS TEST CASCADE;");
# Create a table to hold data.
$dbh->do("CREATE TABLE TEST( \
C_ID INT, \
C_FP FLOAT,\
C_VARCHAR VARCHAR(100),\
C_DATE DATE, C_TIME TIME,\
C_TS TIMESTAMP,\
C_BOOL BOOL)");
# Commit changes and exit.
$dbh->commit();
$dbh->disconnect();
注意
do
函数将返回受语句影响的行数(如果行数不适用或不可用,则返回 -1)。通常,只有在删除了一些行或使用了诸如
COPY 等批量加载命令之后,您才需要参考此值。您使用其他 DBI 函数而不是
do
来执行批量插入和选择(有关详细信息,请参阅
使用 Perl 批量加载数据和
使用 Perl 执行查询)。
3.5.4 - 使用 Perl 批量加载数据
要使用 Perl 将大批量数据加载到 Vertica:
-
将 DBI 的 AutoCommit 连接属性设置为 false,以提高批量加载速度。有关禁用 AutoCommit 的示例,请参阅设置 Perl DBI 连接属性。
-
调用数据库句柄的 prepare
函数以准备 SQL INSERT 语句(包含要插入的数据值的占位符)。例如:
# Prepare an INSERT statement for the test table
$sth = $dbh->prepare("INSERT into test values(?,?,?,?,?,?,?)");
prepare
函数将返回一个语句句柄,您可以使用此句柄来插入数据。
-
为占位符分配数据。有几种方法可用于执行此操作。最简单的方法是使用 INSERT 语句中每个占位符的值来填充数组。
-
调用该语句句柄的 execute
函数以将数据行插入到 Vertica。此函数调用的返回值指示 Vertica 已接受还是已拒绝该行。
-
重复步骤 3 和 4,直至已加载所需的所有数据为止。
-
调用数据库句柄的 commit
函数以提交已插入的数据。
以下示例演示了通过以下方法插入小批量数据:使用数据填充一系列数组,然后在其中循环并插入每个行。
#!/usr/bin/perl
use strict;
use DBI;
# Create a hash reference that holds a hash of parameters for the
# connection.
my $attr = {AutoCommit => 0, # Turn off autocommit
PrintError => 0 # Turn off automatic error printing.
# This is handled manually.
};
# Open a connection using a DSN. Supply the username and password.
my $dbh = DBI->connect("dbi:ODBC:VerticaDSN","ExampleUser","password123",
$attr);
if (defined DBI::err) {
# Conection failed.
die "Failed to connect: $DBI::errstr";
}
print "Connection AutoCommit state is: " . $dbh->{AutoCommit} . "\n";
# Create table to hold inserted data
$dbh->do("DROP TABLE IF EXISTS TEST CASCADE;") or die "Could not drop table";
$dbh->do("CREATE TABLE TEST( \
C_ID INT, \
C_FP FLOAT,\
C_VARCHAR VARCHAR(100),\
C_DATE DATE, C_TIME TIME,\
C_TS TIMESTAMP,\
C_BOOL BOOL)") or die "Could not create table";
# Populate an array of arrays with values. One of these rows contains
# data that will not be sucessfully inserted. Another contains an
# undef value, which gets inserted into the database as a NULL.
my @data = (
[1,1.111,'Hello World!','2001-01-01','01:01:01'
,'2001-01-01 01:01:01','t'],
[2,2.22222,'How are you?','2002-02-02','02:02:02'
,'2002-02-02 02:02:02','f'],
['bad value',2.22222,'How are you?','2002-02-02','02:02:02'
,'2002-02-02 02:02:02','f'],
[4,4.22222,undef,'2002-02-02','02:02:02'
,'2002-02-02 02:02:02','f'],
);
# Create a prepared statement to use parameters for inserting values.
my $sth = $dbh->prepare_cached("INSERT into test values(?,?,?,?,?,?,?)");
my $rowcount = 0; # Count # of rows
# Loop through the arrays to insert values
foreach my $tuple (@data) {
$rowcount++;
# Insert the row
my $retval = $sth->execute(@$tuple);
# See if the row was successfully inserted.
if ($retval == 1) {
# Value of 1 means the row was inserted (1 row was affected by insert)
print "Row $rowcount successfully inserted\n";
} else {
print "Inserting row $rowcount failed";
# Error message is not set on some platforms/versions of DBUI. Check to
# ensure a message exists to avoid getting an unitialized var warning.
if ($sth->err()) {
print ": " . $sth->errstr();
}
print "\n";
}
}
# Commit changes. With AutoCommit off, you need to use commit for batched
# data to actually be committed into the database. If your Perl script exits
# without committing its data, Vertica rolls back the transaction and the
# data is not committed.
$dbh->commit();
$dbh->disconnect();
成功运行以上示例后,将显示以下内容:
Connection AutoCommit state is: 0
Row 1 successfully inserted
Row 2 successfully inserted
Inserting row 3 failed with error 01000 [Vertica][VerticaDSII] (20) An
error occurred during query execution: Row rejected by server; see
server log for details (SQL-01000)
Row 4 successfully inserted
请注意,未插入其中一个行,因为该行包含无法存储在整数列中的字符串值。有关与 Vertica 通信的 Perl 脚本中数据类型处理的详细信息,请参阅 Perl 数据类型和 Vertica 数据类型之间的转换。
3.5.5 - 在 Perl 中使用 COPY LOCAL 加载数据
您可以使用 COPY LOCAL 将客户端系统上的分隔文件(例如具有逗号分隔值的文件)加载到 Vertica 中。COPY LOCAL 不是使用 Perl 读取、解析并批量插入文件数据,而是直接将文件数据从本地文件系统加载到 Vertica 中。执行完成后,COPY LOCAL 返回它成功插入的行数。
注意
COPY LOCAL 必须是查询中的第一条语句,否则 Vertica 会返回错误。
以下示例使用 COPY LOCAL 加载到 Vertica 本地文件 data.txt
中,该文件与 Perl 文件位于同一目录中。
#!/usr/bin/perl
use strict;
use DBI;
# Filesystem path handling module
use File::Spec;
# Create a hash reference that holds a hash of parameters for the
# connection.
my $attr = {AutoCommit => 0}; # Turn off AutoCommit
# Open a connection using a DSN. Supply the username and password.
my $dbh = DBI->connect("dbi:ODBC:VerticaDSN","ExampleUser","password123",
$attr) or die "Failed to connect: $DBI::errstr";
print "Connected!\n";
# Drop any existing table.
$dbh->do("DROP TABLE IF EXISTS Customers CASCADE;");
# Create a table to hold data.
$dbh->do("CREATE TABLE Customers( \
ID INT, \
FirstName VARCHAR(100),\
LastName VARCHAR(100),\
Email VARCHAR(100),\
Birthday DATE)");
# Find the absolute path to the data file located in the current working
# directory and named data.txt
my $currDir = File::Spec->rel2abs(File::Spec->curdir());
my $dataFile = File::Spec->catfile($currDir, 'data.txt');
print "Loading file $dataFile\n";
# Load local file using copy local. Return value is the # of rows affected
# which equates to the number of rows inserted.
my $rows = $dbh->do("COPY Customers FROM LOCAL '$dataFile' DIRECT")
or die $dbh->errstr;
print "Copied $rows rows into database.\n";
$dbh->commit();
# Prepare a query to get the first 15 rows of the results
my $sth = $dbh->prepare("SELECT * FROM Customers WHERE ID < 15 \
ORDER BY ID");
$sth->execute() or die "Error querying table: " . $dbh->errstr;
my @row; # Pre-declare variable to hold result row used in format statement.
# Use Perl formats to pretty print the output. Declare the heading for the
# form.
format STDOUT_TOP =
ID First Last EMail Birthday
== ===== ==== ===== ========
.
# The Perl write statement will output a formatted line with values from the
# @row array. See http://perldoc.perl.org/perlform.html for details.
format STDOUT =
@> @<<<<<<<<<<<<< @<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<
@row
.
# Loop through result rows while we have them
while (@row = $sth->fetchrow_array()) {
write; # Format command does the work of extracting the columns from
# the @row array and writing them out to STDOUT.
}
# Call commit to prevent Perl from complaining about uncommitted transactions
# when disconnecting
$dbh->commit();
$dbh->disconnect();
data.txt
是一个文本文件,其中的每一行都包含一行数据。列由管线字符 (|
) 分隔。这是命令接受的默认 COPY 分隔符,它简化了 COPY LOCAL 语句。
下面是文件内容的示例:
1|Georgia|Gomez|Rhiannon@magna.us|1937-10-03
2|Abdul|Alexander|Kathleen@ipsum.gov|1941-03-10
3|Nigel|Contreras|Tanner@et.com|1955-06-01
4|Gray|Holt|Thomas@Integer.us|1945-12-06
5|Candace|Bullock|Scott@vitae.gov|1932-05-27
6|Matthew|Dotson|Keith@Cras.com|1956-09-30
7|Haviva|Hopper|Morgan@porttitor.edu|1975-05-10
8|Stewart|Sweeney|Rhonda@lectus.us|2003-06-20
9|Allen|Rogers|Alexander@enim.gov|2006-06-17
10|Trevor|Dillon|Eagan@id.org|1988-11-27
11|Leroy|Ashley|Carter@turpis.edu|1958-07-25
12|Elmo|Malone|Carla@enim.edu|1978-08-29
13|Laurel|Ball|Zelenia@Integer.us|1989-09-20
14|Zeus|Phillips|Branden@blandit.gov|1996-08-08
15|Alexis|Mclean|Flavia@Suspendisse.org|2008-01-07
对大型示例文件运行以上示例代码后,将生成以下输出:
Connected!
Loading file /home/dbadmin/Perl/data.txt
Copied 1000000 rows into database.
ID First Last EMail Birthday
== ===== ==== ===== ========
1 Georgia Gomez Rhiannon@magna.us 1937-10-03
2 Abdul Alexander Kathleen@ipsum.gov 1941-03-10
3 Nigel Contreras Tanner@et.com 1955-06-01
4 Gray Holt Thomas@Integer.us 1945-12-06
5 Candace Bullock Scott@vitae.gov 1932-05-27
6 Matthew Dotson Keith@Cras.com 1956-09-30
7 Haviva Hopper Morgan@porttitor.edu 1975-05-10
8 Stewart Sweeney Rhonda@lectus.us 2003-06-20
9 Allen Rogers Alexander@enim.gov 2006-06-17
10 Trevor Dillon Eagan@id.org 1988-11-27
11 Leroy Ashley Carter@turpis.edu 1958-07-25
12 Elmo Malone Carla@enim.edu 1978-08-29
13 Laurel Ball Zelenia@Integer.us 1989-09-20
14 Zeus Phillips Branden@blandit.gov 1996-08-08
注意
与并行将多个小型文件加载到多个节点相比,通过单个数据连接将单个大型数据文件加载到 Vertica 更低效。加载到多个节点可防止任何节点成为瓶颈。
3.5.6 - 使用 Perl 执行查询
要使用 Perl 查询 Vertica:
-
使用 Perl DBI 模块的 prepare
函数准备查询语句。此函数将返回一个语句句柄,您可以使用此句柄来执行查询和获取结果集。
-
通过对该语句句柄调用 execute
函数来执行预定义的语句。
-
使用多种方法之一从语句句柄检索查询结果,例如,调用语句句柄的 fetchrow_array
函数以检索数据行,或者调用 fetchall_array
以获取包含整个结果集的一系列数组(如果结果集可能非常大,则此方法不太适用!)。
以下示例演示了查询由使用 Perl 批量加载数据中所示的示例创建的表。此示例将执行查询以检索该表的所有内容,然后重复调用 fetchrow_array
函数以获取数组中的数据行。此示例将重复上述过程直至 fetchrow_array
返回 undef 为止,该返回值表示没有更多数据行可供读取。
#!/usr/bin/perl
use strict;
use DBI;
my $attr = {RaiseError => 1 }; # Make errors fatal to the Perl script.
# Open a connection using a DSN. Supply the username and password.
my $dbh = DBI->connect("dbi:ODBC:VerticaDSN","ExampleUser","password123",
$attr);
# Prepare a query to get the content of the table
my $sth = $dbh->prepare("SELECT * FROM TEST ORDER BY C_ID ASC");
# Execute the query by calling execute on the statement handle
$sth->execute();
# Loop through result rows while we have them, getting each row as an array
while (my @row = $sth->fetchrow_array()) {
# The @row array contains the column values for this row of data
# Loop through the column values
foreach my $column (@row) {
if (!defined $column) {
# NULLs are signaled by undefs. Set to NULL for clarity
$column = "NULL";
}
print "$column\t"; # Output the column separated by a tab
}
print "\n";
}
$dbh->disconnect();
该示例在运行后输出了以下内容:
1 1.111 Hello World! 2001-01-01 01:01:01 2001-01-01 01:01:01 1
2 2.22222 How are you? 2002-02-02 02:02:02 2002-02-02 02:02:02 0
4 4.22222 NULL 2002-02-02 02:02:02 2002-02-02 02:02:02 0
将变量绑定到列值
检索查询结果的另一种方法是使用语句句柄的 bind_columns
函数将变量绑定到结果集中的列。如果需要对返回的数据执行大量处理,您可能会这种方法很方便,因为代码可以使用变量而非数组引用来访问数据。以下示例演示了将变量绑定到结果集而非在行值和列值中循环。
#!/usr/bin/perl
use strict;
use DBI;
my $attr = {RaiseError => 1 }; # Make SQL errors fatal to the Perl script.
# Open a connection using a DSN. Supply the username and password.
my $dbh = DBI->connect("dbi:ODBC:VerticaDSN32","ExampleUser","password123",
$attr);
# Prepare a query to get the content of the table
my $sth = $dbh->prepare("SELECT * FROM TEST ORDER BY C_ID ASC");
$sth->execute();
# Create a set of variables to bind to the column values.
my ($C_ID, $C_FP, $C_VARCHAR, $C_DATE, $C_TIME, $C_TS, $C_BOOL);
# Bind the variable references to the columns in the result set.
$sth->bind_columns(\$C_ID, \$C_FP, \$C_VARCHAR, \$C_DATE, \$C_TIME,
\$C_TS, \$C_BOOL);
# Now, calling fetch() to get a row of data updates the values of the bound
# variables. Continue calling fetch until it returns undefined.
while ($sth->fetch()) {
# Note, you should always check that values are defined before using them,
# since NULL values are translated into Perl as undefined. For this
# example, just check the VARCHAR column for undefined values.
if (!defined $C_VARCHAR) {
$C_VARCHAR = "NULL";
}
# Just print values separated by tabs.
print "$C_ID\t$C_FP\t$C_VARCHAR\t$C_DATE\t$C_TIME\t$C_TS\t$C_BOOL\n";
}
$dbh->disconnect();
此示例的输出与上一个示例的输出相同。
准备、查询和返回单个行
如果希望查询结果是单个行(例如在执行 COUNT (*) 查询时),您可以使用 DBI 模块的 selectrow_array
函数将语句执行和数组检索合并为单个结果。
以下示例显示了使用 selectrow_array
执行 SHOW LOCALE 语句并获取其结果。此示例还演示了使用 do
函数更改区域设置。
#!/usr/bin/perl
use strict;
use DBI;
my $attr = {RaiseError => 1 }; # Make SQL errors fatal to the Perl script.
# Open a connection using a DSN. Supply the username and password.
my $dbh = DBI->connect("dbi:ODBC:VerticaDSN","ExampleUser","password123",
$attr);
# Demonstrate setting/getting locale.
# Use selectrow_array to combine preparing a statement, executing it, and
# getting an array as a result.
my @localerv = $dbh->selectrow_array("SHOW LOCALE;");
# The locale name is the 2nd column (array index 1) in the result set.
print "Locale: $localerv[1]\n";
# Use do() to execute a SQL statement to set the locale.
$dbh->do("SET LOCALE TO en_GB");
# Get the locale again.
@localerv = $dbh->selectrow_array("SHOW LOCALE;");
print "Locale is now: $localerv[1]\n";
$dbh->disconnect();
运行此示例后的结果如下所示:
Locale: en_US@collation=binary (LEN_KBINARY)
Locale is now: en_GB (LEN)
执行查询和 ResultBufferSize 设置
当您在准备好的语句上调用 execute()
函数时,客户端库会一直检索结果,直到达到结果缓冲区大小。结果缓冲区大小是使用 ODBC 的 ResultBufferSize 设置进行设置的。
Vertica 不允许每个连接有多个活动查询。不过,您可以模拟多个活动查询,方法是将结果缓冲区设置得大到足以容纳第一个查询的全部结果。为了确保 ODBC 客户端驱动程序的缓冲区大到足以存储第一个查询的结果集,您可以将 ResultBufferSize 设置为 0。将此参数设置为 0 会使结果缓冲区大小不受限制。ODBC 驱动程序分配足够的内存来读取整个结果集。将第一个查询的整个结果集存储在结果集缓冲区后,数据库连接便可以自由地执行另一个查询。您的客户端可以执行第二个查询,即使它尚未处理第一个查询中的整个结果集。
但是,如果您将 ResultBufferSize 设置为 0,您可能会发现对 execute()
的调用会导致操作系统终止您的 Perl 客户端脚本。如果 ODBC 驱动程序分配了太多内存来存储大型结果集,操作系统可能会终止您的脚本。
此行为的解决方法是限制查询返回的行数。然后,您可以将 ResultBufferSize 设置为适合此有限结果集的值。例如,您可以估计为了存储单行查询结果而所需的内存量。然后,使用 LIMIT 和 OFFSET 子句来获得适合您借助于 ResultBufferSize 所分配空间的具体行数。如果查询结果能够适合有限结果集缓冲区,则您可以使用相同的数据库连接来执行其他查询。此解决方案使您的代码更加复杂,因为您需要执行多个查询才能获得整个结果集。此外,如果您需要一次对整个结果集进行操作,而不是一次只对其一部分进行操作,那么此解决方案也不适合。
更好的解决方案是针对要执行的每个查询使用单独的数据库连接。与处理大型数据集所需的资源相比,建立额外数据库连接所需的开销很小。
3.5.7 - Perl 数据类型和 Vertica 数据类型之间的转换
Perl 是一种弱类型编程语言,这种语言不为值分配特定的数据类型。这种语言基于对值执行的操作在字符串和数值之间转换。因此,Perl 在从 Vertica 提取大部分字符串和数值数据类型时遇到的问题较小。所有时间间隔数据类型(DATE 和 TIMESTAMP 等)会转换为字符串。可以使用多个不同的 Perl 日期和时间处理模块在脚本中处理这些值。
Vertica NULL 值会转换为 Perl 的未定义的值 (undef
)。从包含 NULL 值的列读取数据时,始终应在使用某个值之前测试是否已定义该值。
将数据插入到 Vertica 时,Perl DBI 模块会尝试将数据强制转换为正确格式。默认情况下,该模块假设列值为 VARCHAR,除非它能够确定这些列值为某种其他数据类型。如果提供了一个字符串值以插入到具有整数或数值数据类型的列,DBI 会尝试将该字符串的内容转换为正确的数据类型。如果整个字符串可以转换为适当数据类型的值,该模块会将值插入到列中。否则,插入数据行将失败。
当插入到 FLOAT、NUMERIC 或类似数据类型的列时,DBI 以透明方式将整数值转换为数值或浮点值。仅当不会降低精度(小数点右边的值为 0)时,该模块才会将数值或浮点值转换为整数。例如,该模块可以将值 3.0 插入到 INTEGER 列中,因为将该值转换为整数时不会降低精度。该模块无法将 3.1 插入到 INTEGER 列中,因为这样会降低精度。该模块将返回错误,而非将该值截断为 3。
以下示例演示了 DBI 模块在将数据插入到 Vertica 时执行的一些转换。
#!/usr/bin/perl
use strict;
use DBI;
# Create a hash reference that holds a hash of parameters for the
# connection.
my $attr = {AutoCommit => 0, # Turn off autocommit
PrintError => 0 # Turn off print error. Manually handled
};
# Open a connection using a DSN. Supply the username and password.
my $dbh = DBI->connect("dbi:ODBC:VerticaDSN","ExampleUser","password123",
$attr);
if (defined DBI::err) {
# Conection failed.
die "Failed to connect: $DBI::errstr";
}
print "Connection AutoCommit state is: " . $dbh->{AutoCommit} . "\n";
# Create table to hold inserted data
$dbh->do("DROP TABLE IF EXISTS TEST CASCADE;");
$dbh->do("CREATE TABLE TEST( \
C_ID INT, \
C_FP FLOAT,\
C_VARCHAR VARCHAR(100),\
C_DATE DATE, C_TIME TIME,\
C_TS TIMESTAMP,\
C_BOOL BOOL)");
# Populate an array of arrays with values.
my @data = (
# Start with matching data types
[1,1.111,'Matching datatypes','2001-01-01','01:01:01'
,'2001-01-01 01:01:01','t'],
# Force floats -> int and int -> float.
[2.0,2,"Ints <-> floats",'2002-02-02','02:02:02'
,'2002-02-02 02:02:02',1],
# Float -> int *only* works when there is no loss of precision.
# this row will fail to insert:
[3.1,3,"float -> int with trunc?",'2003-03-03','03:03:03'
,'2003-03-03 03:03:03',1],
# String values are converted into numbers
["4","4.4","Strings -> numbers", '2004-04-04','04:04:04',
,'2004-04-04 04:04:04',0],
# String -> numbers only works if the entire string can be
# converted into a number
["5 and a half","5.5","Strings -> numbers", '2005-05-05',
'05:05:05', ,'2005-05-05 05:05:05',0],
# Number are converted into string values automatically,
# assuming they fit into the column width.
[6,6.6,3.14159, '2006-06-06','06:06:06',
,'2006-06-06 06:06:06',0],
# There are some variations in the accepted date strings
[7,7.7,'Date/time formats', '07/07/2007','07:07:07',
,'07-07-2007 07:07:07',1],
);
# Create a prepared statement to use parameters for inserting values.
my $sth = $dbh->prepare_cached("INSERT into test values(?,?,?,?,?,?,?)");
my $rowcount = 0; # Count # of rows
# Loop through the arrays to insert values
foreach my $tuple (@data) {
$rowcount++;
# Insert the row
my $retval = $sth->execute(@$tuple);
# See if the row was successfully inserted.
if ($retval == 1) {
# Value of 1 means the row was inserted (1 row was affected by insert)
print "Row $rowcount successfully inserted\n";
} else {
print "Inserting row $rowcount failed with error " .
$sth->state . " " . $sth->errstr . "\n";
}
}
# Commit the data
$dbh->commit();
# Prepare a query to get the content of the table
$sth = $dbh->prepare("SELECT * FROM TEST ORDER BY C_ID ASC");
$sth->execute() or die "Error: " . $dbh->errstr;
my @row; # Need to pre-declare to use in the format statement.
# Use Perl formats to pretty print the output.
format STDOUT_TOP =
Int Float VarChar Date Time Timestamp Bool
=== ===== ================== ========== ======== ================ ====
.
format STDOUT =
@>> @<<<< @<<<<<<<<<<<<<<<<< @<<<<<<<<< @<<<<<<< @<<<<<<<<<<<<<<< @<<<<
@row
.
# Loop through result rows while we have them
while (@row = $sth->fetchrow_array()) {
write; # Format command does the work of extracting the columns from
# the array.
}
# Commit to stop Perl complaining about in-progress transactions.
$dbh->commit();
$dbh->disconnect();
运行该示例后,将生成以下输出:
Connection AutoCommit state is: 0
Row 1 successfully inserted
Row 2 successfully inserted
Inserting row 3 failed with error 01000 [Vertica][VerticaDSII] (20) An error
occurred during query execution: Row rejected by server; see server log for
details (SQL-01000)
Row 4 successfully inserted
Inserting row 5 failed with error 01000 [Vertica][VerticaDSII] (20) An error
occurred during query execution: Row rejected by server; see server log for
details (SQL-01000)
Row 6 successfully inserted
Row 7 successfully inserted
Int Float VarChar Date Time Timestamp Bool
=== ===== ================== ========== ======== ================ ====
1 1.111 Matching datatypes 2001-01-01 01:01:01 2001-01-01 01:01 1
2 2 Ints <-> floats 2002-02-02 02:02:02 2002-02-02 02:02 1
4 4.4 Strings -> numbers 2004-04-04 04:04:04 2004-04-04 04:04 0
6 6.6 3.14159 2006-06-06 06:06:06 2006-06-06 06:06 0
7 7.7 Date/time formats 2007-07-07 07:07:07 2007-07-07 07:07 1
3.5.8 - Perl Unicode 支持
Perl 支持 Unicode 数据,但具有一些限制。有关详细信息,请参阅 perlunicode 手册页和 perlunitut(Perl Unicode 教程)手册页。(务必参阅随客户端系统上安装的 Perl 版本附带提供的这些手册页副本,因为 Unicode 支持在最新版本的 Perl 中已更改。)Perl DBI 和 DBD::ODBC 也支持 Unicode,但必须为 DBD::ODBC 编译 Unicode 支持。有关详细信息,请参阅 DBD::ODBC 文档。可以检查名为 odbc_has_unicode
的特定于 DBD::ODBC 的连接属性,以查看是否在该驱动程序中启用了 Unicode 支持。
以下示例 Perl 脚本演示了直接将 UTF-8 字符串插入到 Vertica 再从中读回这些字符串。此示例将输出写入到文本文件中,因为在终端窗口或控制台中显示 Unicode 字符时出现许多问题。
#!/usr/bin/perl
use strict;
use DBI;
# Open a connection using a DSN.
my $dbh = DBI->connect("dbi:ODBC:VerticaDSN","ExampleUser","password123");
unless (defined $dbh) {
# Conection failed.
die "Failed to connect: $DBI::errstr";
}
# Output to a file. Displaying Unicode characters to a console or terminal
# window has many problems. This outputs a UTF-8 text file that can
# be handled by many Unicode-aware text editors:
open OUTFILE, '>:utf8', "unicodeout.txt";
# See if the DBD::ODBC driver was compiled with Unicode support. If this returns
# 1, your Perl script will get get strings from the driver with the UTF-8
# flag set on them, ensuring that Perl handles them correctly.
print OUTFILE "Was DBD::ODBC compiled with Unicode support? " .
$dbh->{odbc_has_unicode} . "\n";
# Create a table to hold VARCHARs
$dbh->do("DROP TABLE IF EXISTS TEST CASCADE;");
# Create a table to hold data. Remember that the width of the VARCHAR column
# is the number of bytes set aside to store strings, which often does not equal
# the number of characters it can hold when it comes to Unicode!
$dbh->do("CREATE TABLE test( C_VARCHAR VARCHAR(100) )");
print OUTFILE "Inserting data...\n";
# Use Do to perform simple inserts
$dbh->do("INSERT INTO test VALUES('Hello')");
# This string contains several non-latin accented characters and symbols, encoded
# with Unicode escape notation. They are converted by Perl into UTF-8 characters
$dbh->do("INSERT INTO test VALUES('My favorite band is " .
"\N{U+00DC}ml\N{U+00E4}\N{U+00FC}t \N{U+00D6}v\N{U+00EB}rk\N{U+00EF}ll" .
" \N{U+263A}')");
# Some Chinese (Simplified) characters. This again uses escape sequence
# that Perl translates into UTF-8 characters.
$dbh->do("INSERT INTO test VALUES('\x{4F60}\x{597D}')");
print OUTFILE "Getting data...\n";
# Prepare a query to get the content of the table
my $sth = $dbh->prepare_cached("SELECT * FROM test");
# Execute the query by calling execute on the statement handle
$sth->execute();
# Loop through result rows while we have them
while (my @row = $sth->fetchrow_array()) {
# Loop through the column values
foreach my $column (@row) {
print OUTFILE "$column\t";
}
print OUTFILE "\n";
}
close OUTFILE;
$dbh->disconnect();
在支持 UTF-8 的文本编辑器或查看器中查看 unicodeout.txt 文件时,显示以下内容:
Was DBD::ODBC compiled with Unicode support? 1
Inserting data...
Getting data...
My favorite band is Ümläüt Övërkïll ☺
你好
Hello
注意
终端窗口和控制台通常无法正常显示 Unicode 字符。正因为如此,此示例将输出写入到文本文件中。使用某些文本编辑器时,您可能需要手动将文本文件的编码设置为 UTF-8 以便正常显示字符(并且用于显示文本的字体必须具有完整 Unicode 字符集)。如果字符仍无法正常显示,原因可能是您的 DBD::ODBC 版本中未编译 UTF-8 支持。
另请参阅
3.6 - Python
Vertica Python 驱动程序为 Python 客户端应用程序提供与数据库交互的接口。
先决条件
在创建 Python 客户端应用程序之前,必须配置 Python 开发环境。
注意
Vertica Python 客户端和 Vertica ODBC 驱动程序(pyodbc 与之交互)都不支持原生 Vertica UUID 数据类型。借助于这些驱动程序从 UUID 列检索的值将转换为字符串。当您的客户端在元数据中查询 UUID 列时,驱动程序将 UUID 列的数据类型报告为字符串。将您想要插入到 UUID 列的任何 UUID 值转换为字符串。Vertica 会在将这些值插入表之前自动将它们转换为原生 UUID 数据类型。
3.6.1 - 在 Linux 中配置 ODBC 运行时环境,请执行下列操作:
若要在 Linux 中配置 ODBC 运行时环境,请执行下列操作:
-
创建 odbc.ini
文件(如果此文件尚未存在)。
-
将 ODBC 驱动程序目录添加到 LD_LIBRARY_PATH 系统环境变量:
export LD_LIBRARY_PATH=/path-to-vertica-odbc-driver:$LD_LIBRARY_PATH
重要
如果跳过步骤 2,则 ODBC 管理器无法找到以加载驱动程序。
这些步骤仅与 unixODBC 和 iODBC 相关。有关 odbc.ini
的详细信息,请参阅其相应文档。
另请参阅
3.6.2 - 使用 pyodbc 查询数据库
以下示例会话将 pyodbc 与 Vertica ODBC 驱动程序结合使用,以将 Python 连接到 Vertica 数据库。
注意
SQLFetchScroll 函数和 SQLFetch 函数不能在 iODBC 代码中一起使用。将 pyodbc 与 iODBC 驱动程序管理器结合使用时,skip 不能与 fetchall、fetchone 和 fetchmany 函数一起使用。
示例脚本
以下示例脚本显示了如何使用 Python 3、pyodbc 和 ODBC DSN 查询 Vertica。
import pyodbc
cnxn = pyodbc.connect("DSN=VerticaDSN", ansi=True)
cursor = cnxn.cursor()
# create table
cursor.execute("CREATE TABLE TEST("
"C_ID INT,"
"C_FP FLOAT,"
"C_VARCHAR VARCHAR(100),"
"C_DATE DATE, C_TIME TIME,"
"C_TS TIMESTAMP,"
"C_BOOL BOOL)")
cursor.execute("INSERT INTO test VALUES(1,1.1,'abcdefg1234567890','1901-01-01','23:12:34','1901-01-01 09:00:09','t')")
cursor.execute("INSERT INTO test VALUES(2,3.4,'zxcasdqwe09876543','1991-11-11','00:00:01','1981-12-31 19:19:19','f')")
cursor.execute("SELECT * FROM TEST")
rows = cursor.fetchall()
for row in rows:
print(row, end='\n')
cursor.execute("DROP TABLE TEST CASCADE")
cursor.close()
cnxn.close()
生成的输出如下所示:
(2, 3.4, 'zxcasdqwe09876543', datetime.date(1991, 11, 11), datetime.time(0, 0, 1), datetime.datetime(1981, 12, 31, 19, 19, 19), False)
(1, 1.1, 'abcdefg1234567890', datetime.date(1901, 1, 1), datetime.time(23, 12, 34), datetime.datetime(1901, 1, 1, 9, 0, 9), True)
注意
对于未命名的主键约束,SQLPrimaryKeys 将返回主键 (pk_name
) 列中的表名。例如:
-
未命名主键:
CREATE TABLE schema.test(c INT PRIMARY KEY);
SQLPrimaryKeys
"TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "KEY_SEQ", "PK_NAME" <Null>, "SCHEMA", "TEST", "C", 1, "TEST"
-
已命名主键:
CREATE TABLE schema.test(c INT CONSTRAINT pk_1 PRIMARY KEY);
SQLPrimaryKeys
"TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "KEY_SEQ", "PK_NAME" <Null>, "SCHEMA", "TEST", "C", 1, "PK_1"
Micro Focus 建议对约束进行命名。
另请参阅
3.7 - PHP
设置
在通过 PHP 连接到 Vertica 之前,必须阅读 C/C++。以下示例 ODBC 配置条目详细介绍了 PHP ODBC 连接所需的典型设置。驱动程序位置假设您已将 Vertica 驱动程序复制到 /usr/lib64。
示例 odbc.ini
[ODBC Data Sources]
VerticaDSNunixodbc = exampledb
VerticaDNSiodbc = exampledb2
[VerticaDSNunixodbc]
Description = VerticaDSN Unix ODBC driver
Driver = /usr/lib64/libverticaodbc.so
Database = Telecom
Servername = localhost
UserName = dbadmin
Password =
Port = 5433
[VerticaDSNiodbc]
Description = VerticaDSN iODBC driver
Driver = /usr/lib64/libverticaodbc.so
Database = Telecom
Servername = localhost
UserName = dbadmin
Password =
Port = 5433
示例 odbcinst.ini
# Vertica
[VerticaDSNunixodbc]
Description = VerticaDSN Unix ODBC driver
Driver = /usr/lib64/libverticaodbc.so
[VerticaDNSiodbc]
Description = VerticaDSN iODBC driver
Driver = /usr/lib64/libverticaodbc.so
[ODBC]
Threading = 1
验证 Vertica UnixODBC 或 iODBC 库
可以使用以下命令验证 Vertica UnixODBC 库是否能够加载所有从属库(假设您已将这些库复制到 /usr/lib64):
例如:
ldd /usr/lib64/libverticaodbc.so
您必须先解决找不到任何库的问题才能继续操作。
测试 ODBC 连接
使用以下命令测试 ODBC 连接。
isql -v VerticaDSN
3.7.1 - 配置 PHP 开发环境
要配置 PHP 开发环境:
-
安装和配置 ODBC。
-
安装 PHP。
-
安装 PDO 和 ODBC PHP 扩展。在 Linux 上,它们可以作为以下软件包使用:
3.7.2 - PHP Unicode 支持
PHP 不支持本机 Unicode 支持。PHP 仅支持 256 位字符集。但是,PHP 提供了 UTF-8 函数
utf8_encode()
和
utf8_decode()
,从而提供某种基本 Unicode 功能。
有关 PHP 和 Unicode 的更多详细信息,请参阅 PHP 手册的 strings 字符串一节。
3.7.3 - 使用 PHP 查询数据库
以下示例脚本详细介绍了使用 PHP ODBC 函数连接到 Vertica Analytics Platform。
<?php
# Turn on error reporting
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
# A simple function to trap errors from queries
function odbc_exec_echo($conn, $sql) {
if(!$rs = odbc_exec($conn,$sql)) {
echo "<br/>Failed to execute SQL: $sql<br/>" . odbc_errormsg($conn);
} else {
echo "<br/>Success: " . $sql;
}
return $rs;
}
# Connect to the Database
$dsn = "VerticaDSNunixodbc";
$conn = odbc_connect($dsn,'','') or die ("<br/>CONNECTION ERROR");
echo "<p>Connected with DSN: $dsn</p>";
# Create a table
$sql = "CREATE TABLE TEST(
C_ID INT,
C_FP FLOAT,
C_VARCHAR VARCHAR(100),
C_DATE DATE, C_TIME TIME,
C_TS TIMESTAMP,
C_BOOL BOOL)";
$result = odbc_exec_echo($conn, $sql);
# Insert data into the table with a standard SQL statement
$sql = "INSERT into test values(1,1.1,'abcdefg1234567890','1901-01-01','23:12:34
','1901-01-01 09:00:09','t')";
$result = odbc_exec_echo($conn, $sql);
# Insert data into the table with odbc_prepare and odbc_execute
$values = array(2,2.28,'abcdefg1234567890','1901-01-01','23:12:34','1901-01-01 0
9:00:09','t');
$statement = odbc_prepare($conn,"INSERT into test values(?,?,?,?,?,?,?)");
if(!$result = odbc_execute($statement, $values)) {
echo "<br/>odbc_execute Failed!";
} else {
echo "<br/>Success: odbc_execute.";
}
# Get the data from the table and display it
$sql = "SELECT * FROM TEST";
if($result = odbc_exec_echo($conn, $sql)) {
echo "<pre>";
while($row = odbc_fetch_array($result) ) {
print_r($row);
}
echo "</pre>";
}
# Drop the table and projection
$sql = "DROP TABLE TEST CASCADE";
$result = odbc_exec_echo($conn, $sql);
# Close the ODBC connection
odbc_close($conn);
?>
示例输出
以下是脚本生成的示例输出。
Success: CREATE TABLE TEST( C_ID INT, C_FP FLOAT, C_VARCHAR VARCHAR(100), C_DATE DATE, C_TIME TIME, C_TS TIMESTAMP, C_BOOL BOOL)
Success: INSERT into test values(1,1.1,'abcdefg1234567890','1901-01-01','23:12:34 ','1901-01-01 09:00:09','t')
Success: odbc_execute.
Success: SELECT * FROM TEST
Array
(
[C_ID] => 1
[C_FP] => 1.1
[C_VARCHAR] => abcdefg1234567890
[C_DATE] => 1901-01-01
[C_TIME] => 23:12:34
[C_TS] => 1901-01-01 09:00:09
[C_BOOL] => 1
)
Array
(
[C_ID] => 2
[C_FP] => 2.28
[C_VARCHAR] => abcdefg1234567890
[C_DATE] => 1901-01-01
[C_TIME] => 23:12:34
[C_TS] => 1901-01-01 23:12:34
[C_BOOL] => 1
)
Success: DROP TABLE TEST CASCADE
4 - 管理客户端和 Vertica 之间的查询执行
以下主题介绍了可帮助您管理客户端和 Vertica 数据库之间的查询执行的技术。
4.1 - ResultBufferSize
默认情况下,Vertica 使用 ResultBufferSize 参数来确定客户端可从服务器检索的结果集的最大大小(以字节为单位)。如果启用了 ResultBufferSize,则 Vertica 会直接将数据行发送到发出查询的客户端。每次数据提取返回到客户端的行数取决于 ResultBufferSize 参数的大小(以字节为单位)。
有时,客户端请求的结果集大小会大于 ResultBufferSize 参数所允许的大小。在这种情况下,Vertica 一次只会检索结果集的一部分。每次数据提取将返回等于 ResultBufferSize 参数所设置的大小的数据量。最后,随着客户端循环访问各次数据提取,将返回整个结果集。
ResultBufferSize 的优点
如果您担心查询对网络延迟造成的影响,ResultBufferSize 可提供比 MARS 更有利的优势。MARS 要求客户端等待直至所有数据行都已写入到服务器,然后才能检索数据。由于需要等待存储结果,此延迟可能会造成网络延迟问题。
此外,MARS 要求发送两个单独的请求以返回数据行。第一个请求完成查询执行,此步骤会将结果集存储在服务器上。第二个请求检索存储在服务器上的数据行。使用 ResultBufferSize 时,您只需要发送一个请求。此请求会执行并检索相关数据行。
使用 ResultBufferSize 的查询执行
下图显示了已启用 ResultBufferSize 时 Vertica 如何将数据库中的数据行返回到客户端:
查询执行将完成以下步骤:
-
客户端将查询(例如 SELECT 语句)发送到服务器。在上图中,第一个查询名为查询 1。
-
服务器接收客户端的请求,并开始将结果集的描述和请求的数据行发送回客户端。
-
所有可能的行均已返回到客户端之后,执行便已完成。返回的数据集的大小等于已请求的数据的大小,或者等于 ResultBufferSize 参数可检索的最大数据量。如果尚未达到 ResultBufferSize 最大大小,则 Vertica 可以执行查询 2。
服务器可以接受查询 2,并执行与查询 1 相同的步骤。如果查询 1 的结果已达到 ResultBufferSize 可允许的最大大小,则在客户端释放查询 1 的结果之前,Vertica 无法执行查询 2。
查询 2 运行之后,您无法查看为查询 1 检索的结果,除非再次执行查询 1。
设置无限的缓冲区大小
将 ResultBufferSize 设置为 0 会告知客户端驱动程序使用无限的结果集缓冲区。使用此设置,客户端库将分配尽可能多的内存来读取查询的整个结果集。您可以选择将 ResultBufferSize 设置为 0,以模拟通过单个数据库连接同时进行多个活动查询。由于缓冲区大小不受限制,您的客户端可以运行查询并将其整个结果集存储在内存中。这将结束第一个查询,因此您的客户端可以在完全处理第一个查询的结果之前执行第二个查询。
此方法的一个缺点是,如果您的查询返回较大的结果集,您的查询可能会消耗过多的内存。这种内存过度分配可能会导致操作系统终止您的客户端。由于存在这种风险,请考虑使用多个数据库连接,而不是尝试针对多个查询重用单个连接。与处理大型数据集所需的资源总量相比,多个数据库连接的开销很小。
4.2 - 多重活动结果集 (MARS)
只有在使用 JDBC 客户端连接与 Vertica 连接时才能启用 MARS。MARS 允许在单个连接中执行多个查询。ResultBufferSize 将查询的结果直接发送到客户端,而 MARS 先将结果存储在服务器上。完成查询和存储所有结果之后,您可向服务器发送检索请求以将行返回到客户端。
MARS 在会话级别进行设置,并且必须为每个新的会话启用。如果启用了 MARS,则会禁用 ResultBufferSize。不会返回任何错误,但会忽略 ResultBufferSize 参数。
MARS 的优点
与 ResultBufferSize 相比,MARS 允许同时存储来自不同查询的多个结果集。您还可以在前一个结果集的所有结果都已返回到客户端之前发送新的查询。这样可使应用程序能够将查询执行与结果检索分析,以便您可以在单个连接上同时处理不同的结果。
当启用 ResultBufferSize 时,您必须等待直至所有结果集都已返回到客户端,然后才能发送新的查询。
MARS 的另一个优点是,它释放资源的速度比 ResultBufferSize 更快。当查询正在运行时,资源由该查询会话占有。如果启用了 ResultBufferSize,性能低下的客户端可能会读取结果集的单个行,然后必须停下来以检索下一行。这样会导致查询无法快速完成,从而导致无法释放已用资源以用于其他应用程序。使用 MARS 时,客户端的速度与行的读取无关。结果写入到 MARS 存储后,将立即释放资源,而且客户端检索行的速度不再会成为问题。
使用 MARS 的查询执行
下图演示了已启用 MARS 时如何处理发送到服务器的多个查询:
查询 1:
-
查询 1 已发送到服务器。
-
查询 1 的行描述及其结果集状态已返回给客户端。但是,此时没有将结果返回给客户端。
-
查询 1 将完成,并且其结果会保存在服务器上。
- 现在,您可以发送命令以检索查询 1 的结果集的行。这些行存储在服务器上。已检索的行会和结果集的状态一起发送到客户端。通过跟踪结果集的状态,Vertica 能够跟踪已从服务器检索了哪些行。
-
查询 1 已成功完成,并且其结果集正存储在服务器上,现在可以执行查询 2 了。
查询 2:
-
查询 2 已发送到服务器。
-
查询 2 的行描述及其结果集状态已返回给客户端。但是,此时没有将结果返回给客户端。
-
查询 2 将完成,并且其结果会存储在服务器上。现在,查询 1 和查询 2 的结果集都已存储在服务器上。
-
现在,您可以将检索请求发送到查询 1 和查询 2 已存储在服务器上的结果集。每次对查询 1 中的行发出请求时,将发送请求,并且行和结果集状态会发送到客户端。查询 2 也会出现相同。
客户端已读取所有行后,服务器上的 MARS 存储会关闭活动的结果会话。然后会释放服务器上的 MARS 存储,以便存储更多数据。会话完成后,MARS 存储也会关闭并释放。
启用和禁用 MARS
可以通过以下两种不同方法启用和禁用 MARS:
-
要使用 JDBC 客户端连接属性启用 MARS,请参阅 JDBC 连接属性。
-
要使用 SET SESSION 命令启用 MARS,请参阅 SET SESSION MULTIPLEACTIVERESULTSETS。
另请参阅