这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

将 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 文档。

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 表时提供两个谓词,因为它按以下两列分段:idbusiness_unit。但是,如果您在执行查询时知道 idbusiness_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 列,表示您的查询谓词应包含此列。

如果表按多个列(例如 idbusiness_unit)进行分段,则您需要将这两个列作为谓词提供给可路由查询。

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 连接对象。

除了由可路由查询 API 添加到 Vertica JDBC 连接类的 setEnableRoutableQueries 属性之外,该 API 还添加了其他属性。完整列表如下。

  • EnableRoutableQueries:启用可路由查询查找功能。默认值为 false。

  • FailOnMultiNodePlans:如果计划需要多个节点,并且 FailOnMultiNodePlans 设置为 true,查询将失败。如果此属性设置为 false,则会生成警告,并且查询会继续执行。但是,延迟会显著提高,因为可路由查询必须先确定数据是否位于多个节点上,然后使用传统执行(在所有节点上)来运行普通查询。默认值为 true。请注意,仅使用谓词和常量值的简单调用不会出现此失败。

  • MetadataCacheLifetime:保留投影元数据的时间(以秒为单位)。API 缓存有关投影的元数据以用于查询(例如投影)。后续的查询使用该缓存来缩短响应时间。默认值为 300 秒。

  • MaxPooledConnections:保留在 VerticaRoutableConnection 内部池中的最大连接数(群集范围)。默认值 20。

  • MaxPooledConnectionsPerNode:保留在 VerticaRoutableConnection 内部池中的最大连接数(每节点)。默认值 5。

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 包含以下方法:

  • [execute](#execute()

  • [关闭](#close())

  • getWarnings

有关此类的详细信息,请参阅 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 表来显示如何联接未分段的表上的数据。

  1. 创建 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();
    
  2. 创建 sales 表,然后创建在其 customer_key 列上分段的投影。由于 customersales 表按同一个键进行分段,因此您稍后可以使用 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();
    
  3. 添加一些示例数据:

    => 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;
    
  4. 创建 VMart date_dimension 表的未分段投影以在此示例中使用。调用元函数 START_REFRESH 来取消现有数据的分段:

    => CREATE PROJECTION date_dim AS SELECT * FROM date_dimension UNSEGMENTED ALL NODES;
    => SELECT start_refresh();
    

现在,您可以使用 customersalesdate_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

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 操作:addOutputColumnaddOutputExpressionaddPredicateExpressionaddSortColumnsetLimit

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。

存在以下限制:

  • 驱动程序将表达式发送至服务器之前不会验证表达式的语法。如果表达式不正确,则查询会失败。

  • addOutputExpression 在查询 Flex 表时不受支持。如果尝试对 Flex 表查询使用 addOutputExpression,则会引发 SQLFeatureNotSupportedException

addSortColumn

addSortColumn(string, SortOrder)

向输出列添加排序顺序。输出列既可以是默认查询 (SELECT *) 所返回的一个列,也可以是 addSortColumn 或 addOutputExpress 中定义的列之一。可以定义多个排序列。

setLimit

setLimit(int)

对返回的结果数设置限制。值为 0 的限制表示无限制。

clearPredicates

clearPredicates()

移除由 addPredicateaddPredicateExpression 添加的谓词。

clearOutputs

clearOutputs()

移除由 addOutputColumnaddOutputExpression 添加的输出。

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.

5 - 可路由查询性能和故障排除

本主题详细介绍性能注意事项和使用可路由查询 API 时可能会遇到的常见问题。

将资源池与可路由查询结合使用

各个可路由查询将快速得到处理,因为它们直接访问单个节点并返回一个或几个数据行。但在默认情况下,Vertica 资源池对 execution parallelism 参数使用 AUTO 设置。如果将此参数设置为 AUTO,则设置由可用的 CPU 核心数决定,并通常导致对资源池中的查询进行多线程执行。在服务器上创建并行线程会降低效率,因为可路由查询操作返回数据的速度太快,并且可路由查询操作仅使用单个线程查找行。若要防止服务器打开不需要的处理线程,应为可路由查询客户端创特定的资源池。请考虑用于可路由查询的资源池的以下设置:

  • 将执行并行度设置为 1,以强制执行单线程查询。此设置能够提高可路由查询的性能。

  • 使用 CPU 相关性将资源池限制为某个特定 CPU 或 CPU 集。此设置不但能够确保可路由查询具有可用资源,而且能够防止可路由查询对其他常规查询的系统性能造成显著影响。

  • 如果不为资源池设置 CPU 相关性,可以考虑将资源池的最大并发值设置为既能确保可路由查询性能良好又不会对常规查询的性能造成负面影响的设置。

可路由查询连接的性能注意事项

由于 VerticaRoutableConnection 将打开内部连接池,因此必须根据群集大小和同时客户端连接的数量适当地配置 MaxPooledConnectionsMaxPooledConnectionsPerNode。如果要使用 VerticaRoutableConnection 重载群集,则您无法影响正常的数据库连接。

与发起程序节点的初始连接可发现群集中的所有其他节点。发送 VerticaRoutableExecutor 或 VGet 查询之前,不会打开内部池连接。连接对象中的所有 VerticaRoutableExecutor/VGet 均使用来自内部池的连接,并受到 MaxPooledConnections 设置的限制。连接将保持打开,直至在已达到连接限制时关闭这些连接以便在其他位置打开新连接为止。

可路由查询故障排除

可路由查询问题通常分为以下两个类别:

  • 提供的谓词数量不足够。

  • 查询必须跨越多个节点。

谓词要求

必须提供与表(使用哈希算法进行分段)的列数相同的谓词数量。要确定分段列,请调用 Vertica 函数 GET_PROJECTIONS。必须为 Seg Cols 字段中显示的每个列提供一个谓词。

对于 VGet,这意味着您必须使用 addPredicate() 以添加每个列。对于 VerticaRoutableExecutor,这意味着您必须在发送到 execute() 的映射中提供所有谓词和值。

多节点故障

无法定义正确数量的谓词,但仍会发生故障,因为数据包含在多个节点上。发生此故障的原因是投影的数据未以某种方式进行分段,以使要查询的数据包含在单个节点上。请为连接启用日志记录并查看日志,以验证所使用的投影。如果客户端未选择正确的投影,则会通过在 create/prepare 语句中指定投影而非表来尝试直接查询投影,例如:

  • 使用 VerticaRoutableExecutor:

    conn.createRoutableExecutor(schema, table/projection);
    
  • 使用 VGet:

    conn.prepareGet('schema','table/projection')
    

此外,在 vsql 中使用 EXPLAIN 命令有助于确定查询是否可以在单个节点中运行。EXPLAIN 可以帮助了解为什么查询在单个或多个节点中运行。

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);
            }
        }
    }

}