Ignite SQL网格

内存SQL网格

内存中的SQL网格为Apache ignite增加了内存分布式数据库功能。它是水平可伸缩、容错和SQL ANSI-99兼容的。

SQL网格完全支持所有SQL和DML命令,包括SELECT、UPDATE、INSERT、MERGE和DELETE查询。
这里写图片描述

内存中的SQL网格允许用户与ignite平台交互,而不仅仅是使用本机开发的Java api .NET和c++,还可以使用标准的SQL命令通过igniite JDBC或ODBC api来进行交互。这为PHP、Ruby等语言提供了一个真正的跨平台连接。

1. Distributed Queries(分布式查询)

1.1 概述

ignite支持自由格式的SQL查询,没有任何限制。SQL语法是ANSI-99兼容的,这意味着您可以使用任何类型的SQL函数、聚合、分组或连接(由规范定义)作为SQL查询的一部分。而且,查询是完全分布的。SQL引擎不仅能够将查询映射到特定的节点,还能将它们的响应规约到最终的结果集,它还能够将存储在不同节点上的不同缓存中的数据集连接起来。此外,SQL引擎以容错的方式执行,保证不会出现不完整或错误的结果,以防新节点加入集群,或者旧的节点离开它。

1.2 How SQL Queries Work

Apache ignite SQL网格组件与H2数据库紧密耦合的,而H2数据库是一个快速的内存和基于磁盘的数据库,它是Java编写的,并且在一些开放源码许可下可用。

当将ignite-indexing模块添加到节点的类路径时,一个嵌入的H2实例总是作为Apache启动节点过程的一部分启动。如果一个节点是在终端使用ignite.sh{bat}脚本启动起来的,然后就复制{apache_ignite}\libs\optional\ignite-indexing文件夹到{apache_ignite}\libs\。如果使用Maven,则将依赖项添加到pom.xml文件:

<dependency>
    <groupId>org.apache.ignite</groupId>
    <artifactId>ignite-indexing</artifactId>
    <version>${ignite.version}</version>
</dependency>

Apache ignite利用H2的SQL查询解析器和优化器以及执行计划程序。最后,H2在一个特定的节点上执行本地查询(将一个分布式查询映射到节点,或者在本地模式中执行查询),并将本地结果传递给一个分布式ignite SQL引擎以进行进一步处理。

但是,数据和索引总是存储在ignite数据网格中。此外,ignite以分布式和容错方式执行查询,而这是H2所不支持的。

ignite SQL网格以两种方式执行查询:

首先,如果在部署缓存的节点上对REPLICATED 的缓存执行查询,那么ignite假设所有数据都是本地可用的,并运行一个简单的本地SQL查询,直接将其传递给H2数据库引擎。对于LOCAL缓存来说,也是相同的执行流程。
其次,如果在PARTITIONED缓存上执行查询,则执行流如下:

  • 查询将被解析并分为多个map查询和一个reduce查询。
  • 所有的map查询都在缓存数据所在的所有数据节点上执行。
  • 所有的节点都为查询发起者(规约者)提供了本地执行的结果集,反过来,通过适当地合并提供的结果集来完成reduce阶段。

PS:跨缓存查询的执行流
跨缓存查询或与连接的查询的执行流与上面的PARTITIONED缓存所描述的并不不同,,稍后将作为该文档的一部分进行讨论。

PS:以order by和group by处理结果集
使用ORDER BY子句的SQL查询不需要将整个结果集加载到查询发起者(规约)节点,以完成排序。相反,将映射到查询的每个节点将对其自己的整体结果集进行排序,而reducer(规约器)将以流媒体方式进行合并。对于GROUP BY查询实现了相同的优化–这里没有必要去为规约加载整个结果集,,以便在将其用于应用程序之前进行分组。在apache ignite中,单个节点的部分结果集可以进行流、合并、聚合,并逐步返回应用程序。

1.3 Query Types

在Java API级别有两种常见的SQL查询类型——SqlQuery和SqlFieldsQuery

PS:替代api
Apache ignite内存中的SQL网格不绑定到Java api。您可以以通过.net和c++使用ODBC/JDBC驱动连接到连接到一个ignite集群,并执行sql查询。从下面列出的页面中了解更多的api:(参考英文版找吧)

1.3.1 SqlQuery

当你需要在查询执行结束时返回整个对象,存储在缓存中(键和值都缓存),返回最终结果集,那么sqlQuery是很有用的。下面的代码片段展示了如何做到这一点。

IgniteCache<Long, Person> cache = ignite.cache("personCache");

SqlQuery sql = new SqlQuery(Person.class, "salary > ?");

// Find all persons earning more than 1,000.
try (QueryCursor<Entry<Long, Person>> cursor = cache.query(sql.setArgs(1000))) {
  for (Entry<Long, Person> e : cursor)
    System.out.println(e.getValue().toString());
}

1.3.2 SqlFieldsQueries

您可以选择只选择特定的字段,以最小化网络和序列化开销,而不是选择整个对象。为此,ignite实现了字段查询的概念。SqlFieldsQuery接受传统的ANSI-99 SQL查询作为其构造函数参数并执行它,如下面的示例所示。

IgniteCache<Long, Person> cache = ignite.cache("personCache");

// Execute query to get names of all employees.
SqlFieldsQuery sql = new SqlFieldsQuery(
  "select concat(firstName, ' ', lastName) from Person");

// Iterate over the result set.
try (QueryCursor<List<?>> cursor = cache.query(sql) {
  for (List<?> row : cursor)
    System.out.println("personName=" + row.get(0));
}

ps:可查询的字段定义
在SqlQuery或SqlFieldsQuery中可以访问特定字段之前,他们必须在POJO级别上进行注释或者定义在一个QueryEntity,这样sql引擎才会感知到他们。

PS:访问条目的键和值
在SQL查询中使用_key和_value关键字来比较条目的完整键或值,而不是单个字段。如果需要返回键或值作为SQL查询执行的结果,则应用相同的关键字。 另一方面,如果一个原始类型的键或值(整数、字符串、日期等),那么它将自动添加到查询的结果集,就像SELECT * FROM …

1.4 跨缓存查询

可以从多个缓存查询数据,作为单个SqlQuery或SqlFieldsQuery查询的一部分。在这种情况下,缓存名称作为常规RDBMS中的模式名,就像如SQL查询中那样。用于创建IgniteCache实例的缓存的名称,用于执行查询,将用作默认的模式名称,不需要显式指定。存储在不同的缓存中的将被查询的其余的对象,必须使用它们的缓存的名称(额外的模式名称)进行前缀。

// In this example, suppose Person objects are stored in a 
// cache named 'personCache' and Organization objects 
// are stored in a cache named 'orgCache'.
IgniteCache<Long, Person> personCache = ignite.cache("personCache");

// Select with join between Person and Organization to 
// get the names of all the employees of a specific organization.
SqlFieldsQuery sql = new SqlFieldsQuery(
    "select Person.name  "
        + "from Person as p, \"orgCache\".Organization as org where "
        + "p.orgId = org.id "
        + "and org.name = ?");

// Execute the query and obtain the query result cursor.
try (QueryCursor<List<?>> cursor =  personCache.query(sql.setArgs("Ignite"))) {
    for (List<?> row : cursor)
        System.out.println("Person name=" + row.get(0));
}

在上面的样例中,SqlFieldsQuery 实例从personCache缓存中创建出来的,它的名字就被认为是默认的。这就是为什么Person对象没有显式指定的模式名访问(就是代码里没有写from Person as p这样的句子)。至于Organization 对象,因为它存储在一个命名为orgCache的单独的缓存中,此缓存的名称必须在查询中显式地设置为模式名称(就是必须要写"orgCache".Organization as org这句话)

PS:改变模式名称
如果你喜欢使用模式名称不同于一个缓存的名字,那么你可以利用CacheConfiguration.setSqlSchema(…)方法。

1.5 Distributed Joins

ignite支持collocated 和non-collocated分布式sql joins。此外,如果数据驻留在不同的缓存,ignite允许跨缓存join。

IgniteCache<Long, Person> cache = ignite.cache("personCache");

// SQL join on Person and Organization.
SqlQuery sql = new SqlQuery(Person.class,
  "from Person as p, \"orgCache\".Organization as org"
  + "where p.orgId = org.id "
  + "and lower(org.name) = lower(?)");

// Find all persons working for Ignite organization.
try (QueryCursor<Entry<Long, Person>> cursor = cache.query(sql.setArgs("Ignite"))) {
  for (Entry<Long, Person> e : cursor)
    System.out.println(e.getValue().toString());
}

PARTITIONED(分区) 和REPLICATED (复制)缓存之间的join可以运行,而没有任何局限性。

但是,如果您在至少两个PARTITIONED (分区)数据集之间进行连接,那么你必须确保你所joining的键都是collocated 的,或者您必须启用查询的non-collocated联接参数。
PS:Data Collocation

1.5.1 Distributed Collocated Joins(分布式并置连接)

默认情况下,如果一个SQL join 必须要在多个ignite缓存上进行操作,那么所有的缓存需要被collocated。否则,在查询执行结束时,您将得到一个不完整的结果,因为在联接阶段,节点使用的数据仅在本地可用。在下面的图一中你可以看到,首先,一个SQL 查询将会被发送到需要join进来的数据所在的所有的节点(Q),在此之后,每一个节点(E(Q))对本地数据集执行查询,最后,整个执行结果聚合在客户端®上。
这里写图片描述

1.5.2 Distributed Non-Collocated Joins

尽管affinity collocation(关联并置)是一个强大的概念,一旦设置应了用程序的业务实体(缓存),将允许您以最优的方式执行跨缓存连接,以返回一个完整且一致的结果集,但还有一种可能就是,无法并置所有的数据。因此这时,就可能无法执行满足需求的所有SQL查询了。

Non-Collocated 的分布式联接在Apache ignite里被设计和支持以下情形----当它非常困难或不可能对所有数据进行关联并置时,但是您仍然需要在Non-Collocated的缓存上执行一些SQL查询。

在实践中不要过度使用基于非并置的分布式关联的方式,因为这种关联方式的性能差于基于关系并置的关联,因为要完成这个查询,要有更多的网络开销和节点间的数据移动。

当通过SqlQuery.setDistributedJoins(boolean)参数为一个SQL查询启用了非并置的分布式关联之后,查询映射的节点就会从远程节点通过发送广播或者单播请求的方式获取缺失的数据(本地不存在的数据),正如图2所示,有一个潜在的数据移动步骤(D(Q))。潜在的单播请求只会在关联在主键(缓存键)或者关系键上完成之后才会发送,因为执行关联的节点知道缺失数据的位置,其他所有的情况都会发送广播请求。
这里写图片描述

PS:不管是广播还是单播请求,都是由一个节点发送到另一个节点来获取缺失的数据,是按照顺序执行的。SQL引擎将所有请求合并成批。可以使用SqlQuery.setPageSize(int)参数来管理这个批大小。

下面的代码片段来自于在ignite发行版的cachequeryExample。

IgniteCache<Long, Person> cache = ignite.cache("personCache");

// SQL clause query with join over non-collocated data.
String joinSql =
	"from Person, \"orgCache\".Organization as org " +
  "where Person.orgId = org.id " +
  "and lower(org.name) = lower(?)";

SqlQuery qry = new SqlQuery<Long, Person>(Person.class, joinSql).setArgs("ApacheIgnite");

// Enable distributed joins for the query.
qry.setDistributedJoins(true);

// Execute the query to find out employees for specified organization.
System.out.println("Following people are 'ApacheIgnite' employees (distributed join): ", cache.query(qry).getAll());

non-collocation分布式关联,要了解详细信息,可以参照这里

PS:查询缓存复制
如果执行SQL查询数据只存储在缓存复制,那么您可能希望设置SqlQuery.setReplicatedOnly(…)参数为true。这是对SQL引擎的特殊提示,可以为查询生成更有效的执行计划。

1.6 Known Limitations

1.6.1 Transactional SQL

目前,SQL查询仅仅支持原子模式,意味着如果有一个事务已经提交了值A而值B正在提交过程中,然后如果有一个并行的SQL查询的话,会看到A而看不到B。

PS:多版本并发控制(MVCC)
一旦Ignite SQL网格使用MVCC进行控制,SQL网格也会支持事务模式。

1.6.2 Example

一个完整的示例演示了在这个文档部分中所涵盖的使用分布式查询,作为每个Apache点火分发的一部分,并命名为CacheQueryExample。这个示例也可以在git hub中使用。
Git Hub地址

2. Local Queries

在某些场景中,SQL网格可以从分布式模式的查询执行返回到本地模式。在本地模式中,查询只需传递给底层的H2引擎,该引擎只针对一个节点的本地数据集运行它。

这些场景如下:

  • 如果一个查询在部署有REPLICATED缓存的节点上执行,那么Ignite会假定所有的数据都在本地,然后就会隐式地在本地执行一个简单的查询;
  • 查询在LOCAL缓存上执行;
  • 使用SqlQuery.setLocal(true)或者SqlFieldsQuery.setLocal(true)为查询显式地开启本地模式;

前两种场景将为您提供一个完整的、一致的结果集,无论集群拓扑的变化(新节点连接到集群,还是老的节点在执行查询时)。

但是,在第三种场景中,应用程序代码显式地启用了本地模式,应该谨慎使用。原因是,如果您决定在某些节点上运行一SQL引擎不处理这种特殊情况。如果仍然希望在REPLICATED缓存上执行本地查询,那么需要将查询作为affinityRun(…)或者affinityCall(…)方法的一部分。
参考地址

3.Schema and Indexes

在Apache ignite中设置schema模式和索引index.

3.1 概述

目前,ignite支持基于注解和QueryEntities实体的方式来实现模式的定义。每个特定的模式都绑定到一个ignite缓存实例上,在默认情况下,在SQL查询中使用该缓存实例的名称作为模式名。

PS:Data Definition Language Support
DDL语句计划在最近的Apache ignite版本中得到支持。完成此功能之后,可以使用CREATE/ALTER/DROP TABLE或CREATE/DROP索引等标准SQL命令来定义模式、缓存、索引和管理它们。

Apache ignite支持高级索引功能,允许您定义一个字段(aka. column)或具有各种参数的组索引,来管理在Java堆或堆外空间中放置它们的索引位置,等等。

ignite的索引与缓存数据集有着相同的分布式存储方式。存储特定数据子集的每个节点保存并维护与此数据相对应的索引。

在这个文档页面中,您将了解如何使用两种可用的方法定义包括索引以及可查询字段在内的模式,以及如何在数据结构支持的特定索引实现之间切换。

3.2 DDL Based Configuration

使用广泛使用的DDL命令在运行时创建和修改索引。请参考第5节

3.3 Annotation Based Configuration(基于注解的配置)

索引和可查询字段可以通过使用@ querysqlfield注释来配置。如下面的示例所示,需要用这个注释标记所需的字段。

//java
public class Person implements Serializable {
  /** Indexed field. Will be visible for SQL engine. */
	@QuerySqlField (index = true)
  private long id;
  
  /** Queryable field. Will be visible for SQL engine. */
  @QuerySqlField
  private String name;
  
  /** Will NOT be visible for SQL engine. */
  private int age;
  
  /**
   * Indexed field sorted in descending order. 
   * Will be visible for SQL engine.
   */
  @QuerySqlField(index = true, descending = true)
  private float salary;
}
//scala
case class Person (
  /** Indexed field. Will be visible for SQL engine. */
  @(QuerySqlField @field)(index = true) id: Long,

  /** Queryable field. Will be visible for SQL engine. */
  @(QuerySqlField @field) name: String,
  
  /** Will NOT be visisble for SQL engine. */
  age: Int
  
  /**
   * Indexed field sorted in descending order. 
   * Will be visible for SQL engine.
   */
  @(QuerySqlField @field)(index = true, descending = true) salary: Float
) extends Serializable {
  ...
}

id和scalary属性都是索引属性。id字段将按升序(默认)排序,而薪水按降序排列如果您不想索引字段,但是仍然需要在SQL查询中使用它,那么必须对字段进行注释,并省略索引= true参数。这样的字段称为可查询字段。例如,name被定义为上面的可查询字段。最后,age既不是可查询的也不是索引属性,也无法从Apache ignite的SQL查询中访问。

PS:scala注解
在Scala类中,@QuerySqlField注释必须伴随@field注释,以便在一个字段中可以看到用于ignite的字段,例如:@(QuerySqlField @ field)。另外,您还可以从ignite-scalar模块中使用@ scalarcachequerysqlfield注释,它只是@ field注释的类型别名。

3.3.1 Registering Indexed Types

在定义了索引和可查询字段之后,它们必须在SQL引擎中注册,以及它们所属的对象类型。

为了说明应该索引哪些类型,键值对可以传递给CacheConfiguration.setIndexedTypes方法,如下面的示例所示。

// Preparing configuration.
CacheConfiguration<Long, Person> ccfg = new CacheConfiguration<>();

// Registering indexed type.
ccfg.setIndexedTypes(Long.class, Person.class);

注意,这个方法只接受一对类型class作为参数——一个用于键类的class,另一个用于值类的class。原语被作为装箱类型传递。

PS:预定义的字段
除了用@ querysqlfield注释标记的所有字段外,每个表还将有两个特殊的预定义字段:_key和_val,它们代表到整个键和值对象的链接。这是有用的,例如,当其中一个是原始类型时,你想要根据它的值来过滤。为此,执行SELECT * FROM Person WHERE _key = 100的查询。

PS:
由于ignite支持二进制编组,所以不需要向集群节点的类路径添加索引类型的类。SQL查询引擎能够获取索引和可查询字段的值,以避免对象反序列化。

3.3.2 Group Indexes

要建立一个多字段索引,允许使用复杂条件加速查询,您可以使用@QuerySqlField.Group注释。可以放置多个@ querysqlfield。如果您希望字段是多个组的一部分,则可以放置多个@QuerySqlField.Group注解进orderedGroups 里。

例如,在我们下面的Person类中,有一个名为“age_salary_idx”的索引组,它的group order(分组优先级)为0,并且降序排序。同样,在同一组中,我们的字段salary,它的group order(分组优先级)为3并且升序排序。此外,字段salary本身是一个单列索引(除了orderedGroups声明之外,还有一个index = true参数)。分组的优先级不一定是一个特定的数。他只是用于分组内的字段排序

public class Person implements Serializable {
  /** Indexed in a group index with "salary". */
  @QuerySqlField(orderedGroups={@QuerySqlField.Group(
    name = "age_salary_idx", order = 0, descending = true)})
  private int age;

  /** Indexed separately and in a group index with "age". */
  @QuerySqlField(index = true, orderedGroups={@QuerySqlField.Group(
    name = "age_salary_idx", order = 3)})
  private double salary;
}

注意,在@QuerySqlField(orderedGroups={…})之外使@QuerySqlField.Group注释字段,是无效的

3.4 QueryEntity Based Configuration

可以使用org.apache.ignite.cache.QueryEntity配置索引和可查询字段。对于基于Spring XML的配置来说,QueryEntity类是很方便的。

在上面基于注解的配置涉及的所有概念,对于基于QueryEntity的方式也都有效,深入地说,通过@QuerySqlField配置的字段的类型然后通过CacheConfiguration.setIndexedTypes注册过的,在内部也会被转换为查询实体。

下面的示例显示的是如何像可查询字段那样定义一个单一字段和分组索引。

<bean class="org.apache.ignite.configuration.CacheConfiguration">
    <property name="name" value="mycache"/>
    <!-- Configure query entities -->
    <property name="queryEntities">
        <list>
            <bean class="org.apache.ignite.cache.QueryEntity">
                <!-- Setting indexed type's key class -->
                <property name="keyType" value="java.lang.Long"/>
              
                <!-- Setting indexed type's value class -->
                <property name="valueType"
                          value="org.apache.ignite.examples.Person"/>

                <!-- Defining fields that will be either indexed or queryable.
                Indexed fields are added to 'indexes' list below.-->
                <property name="fields">
                    <map>
                        <entry key="id" value="java.lang.Long"/>
                        <entry key="name" value="java.lang.String"/>
                        <entry key="salary" value="java.lang.Long "/>
                    </map>
                </property>

                <!-- Defining indexed fields.-->
                <property name="indexes">
                    <list>
                        <!-- Single field (aka. column) index -->
                        <bean class="org.apache.ignite.cache.QueryIndex">
                            <constructor-arg value="id"/>
                        </bean>
                      
                        <!-- Group index. -->
                        <bean class="org.apache.ignite.cache.QueryIndex">
                            <constructor-arg>
                                <list>
                                    <value>id</value>
                                    <value>salary</value>
                                </list>
                            </constructor-arg>
                            <constructor-arg value="SORTED"/>
                        </bean>
                    </list>
                </property>
            </bean>
        </list>
    </property>
</bean>

3.5 Indexes Tradeoffs 索引的权衡

当为您的ignite应用程序选择索引时,您应该考虑多种因素。

  1. 索引不是免费的。它们消耗内存,而且每个索引都需要分别更新,因此当设置更多索引时,缓存更新性能可能会更差。最重要的是,优化器可能会选择错误的索引来运行查询,从而犯更多错误。
  2. 索引只是排序的数据结构。如果你为字段(a、b、c)定义一个索引,那么记录将首先被a排序,然后是b,然后是c。索引只是排序的数据结构。如果你为字段(a、b、c)定义一个索引,那么记录将首先被a排序,然后是b,然后是c。

PS:排序索引的例子
| A | B | C |
| 1 | 2 | 3 |
| 1 | 4 | 2 |
| 1 | 4 | 4 |
| 2 | 3 | 5 |
| 2 | 4 | 4 |
| 2 | 4 | 5 |
任意条件,比如a = 1 and b > 3,都会被视为有界范围,在log(N)时间内两个边界在索引中可以被快速检索到,然后结果就是两者之间的任何数据。

下面的条件会使用索引:
a = ?
a = ? and b = ?
a = ? and b = ? and c = ?
从索引的角度,条件a = ?和c = ?不会好于a = ?
很明显地,半界范围a > ?可以工作得很好。

  1. 单个字段上的索引在以相同字段开始的多个字段上的组索引不比组索引好(a)的索引与(a、b、c)相同。因此,最好使用组索引。

4. Distributed DML 分布式DML

4.1 概述

apache的ignite SQL网格不仅支持使用ANSI-99语法的SQL从数据网格里查询数据,他还支持通过像INSERT,UPDATE或者DELETE这样的DML操作来很好得修改数据。利用这个优势,依赖Ignite的SQL能力完全可以将其当做分布式内存数据库。

PS:ANSI-99 SQL兼容
DML查询,和所有的SELECT查询一样,都是兼容ANSI-99 SQL标准的。

ignite将所有数据以键-值对的形式存储在内存中,因此所有DML相关操作都被转换成相应的基于像cache.put(…)或者cache.invokeAll(…)这样的命令指令。让我们深入了解一下DML语句是如何在ignite中实现的。

4.2 DML API

一般来说,所有DML语句都可以分为两组——那些将新条目添加到缓存中的(INSERT 和MERGE),以及那些修改现有数据(UPDATE 和DELETE)的组。

要在Java中执行这些语句需要使用用于SELECT查询的相同的API - SqlFieldsQuery API,DML操作使用的API与只读查询是一致的,返回结果也是QueryCursor<List<?>>。唯一的不同是作为DML语句执行的结果,QueryCursor

PS:其他的API
DML API不受限于Java,也可以使用ODBC或者JDBC驱动接入Ignite集群,然后执行DML语句。具体的请看最后几节

4.3 Basic Configuration

要开始使用DML操作,您需要使用基于QueryEntity的方法或@ querysqlfield注释配置所有可查询字段。例如:

//@QuerySqlField Annotation方式
public class Person {
  /** Field will be accessible from DML statements. */
  @QuerySqlField
	private final String firstName;
  
  /** Indexed field that will be accessible from DML statements. */
  @QuerySqlField (index = true)
  private final String lastName;
  
  /** Field will NOT be accessible from DML statements. */
  private int age;
  
  public Person(String firstName, String lastName) {
  	this.firstName = firstName;
    this.lastName = lastName;
  }
}
//QueryEntityEype方式
<bean class="org.apache.ignite.configuration.CacheConfiguration">
    <property name="name" value="personCache"/>
    <!-- Configure query entities -->
    <property name="queryEntities">
        <list>
            <bean class="org.apache.ignite.cache.QueryEntity">
                <!-- Registering key's class. -->
                <property name="keyType" value="java.lang.Long"/>
              
                <!-- Registering value's class. -->
                <property name="valueType"
                          value="org.apache.ignite.examples.Person"/>

                <!-- 
                    Defining fields that will be accessible from DML side
                -->
                <property name="fields">
                    <map>
                        <entry key="firstName" value="java.lang.String"/>
                        <entry key="lastName" value="java.lang.String"/>
                    </map>
                </property>
              
               <!-- 
                    Defining which fields, listed above, will be treated as 
                    indexed fields as well.
                -->
                <property name="indexes">
                    <list>
                        <!-- Single field (aka. column) index -->
                        <bean class="org.apache.ignite.cache.QueryIndex">
                            <constructor-arg value="lastName"/>
                        </bean>
                    </list>
                </property>
            </bean>
        </list>
    </property>
</bean>

除了所有标记为@ querysqlfield注释的字段,或者用QueryEntity定义的字段外,在SQL Grid中注册的每个对象类型都有两个特殊的预定义字段_key和_val。这些预定义字段提供对存储在缓存中的键值条目的引用,并且可以直接在DML语句中使用。

//插入键值对
//Preparing cache configuration.
CacheConfiguration<Long, Person> cacheCfg = new CacheConfiguration<>
    ("personCache");
      
//Registering indexed/queryable types.
cacheCfg.setIndexedTypes(Long.class, Person.class);

//Starting the cache.
IgniteCache<Long, Person> cache = ignite.cache(cacheCfg);

// Inserting a new key-value pair referring to prefedined `_key` and `_value`
// fields for Person type.
cache.query(new SqlFieldsQuery("INSERT INTO Person(_key, _val) VALUES(?, ?)")
	.setArgs(1L, new Person("John", "Smith")));

如果您喜欢使用具体字段而不是整个对象值,那么可以执行如下所示的查询:

IgniteCache<Long, Person> cache = ignite.cache(cacheCfg);

cache.query(new SqlFieldsQuery(
    "INSERT INTO Person(_key, firstName, lastName) VALUES(?, ?, ?)").
    setArgs(1L, "John", "Smith"));

注意,DML引擎将能够从firstName和lastName重新创建Person对象,并将其放入缓存中,但是这些字段必须使用QueryEntity或@ querysqlfield注释来定义,如上所述。

4.4 Advanced Configuration 高级配置

4.4.1 Custom Keys

如果只使用预定义的SQL数据类型作为缓存键,那么就没必要对和DML相关的配置做额外的操作,这些数据类型在GridQueryProcessor#SQL_TYPES常量中进行定义,列举如下:
PS:预定义SQL数据类型
1.所有的基本类型及其包装器,除了char和Character;
2.String;
3.BigDecimal;
4.byte[];
5.java.util.Date, java.sql.Date, java.sql.Timestamp;
6.java.util.UUID。

但是,一旦您决定引入一个自定义的复杂缓存键,并从DML语句引用其字段时,您必须:

  1. 在QueryEntity中定义这些字段,与在值对象中配置字段一样;
  2. 使用新的配置参数QueryEntitty.setKeyFields(…)来对键和值进行区分;使用新的配置参数QueryEntitty.setKeyFields(…)来对键和值进行区分;

下面的例子展示了如何实现:

//java版本
// Preparing cache configuration.
CacheConfiguration cacheCfg = new CacheConfiguration<>("personCache");

// Creating the query entity. 
QueryEntity entity = new QueryEntity("CustomKey", "Person");

// Listing all the queryable fields.
LinkedHashMap<String, String> flds = new LinkedHashMap<>();

flds.put("intKeyField", Integer.class.getName());
flds.put("strKeyField", String.class.getName());

flds.put("firstName", String.class.getName());
flds.put("lastName", String.class.getName());

entity.setFields(flds);

// Listing a subset of the fields that belong to the key.
Set<String> keyFlds = new HashSet<>();

keyFlds.add("intKeyField");
keyFlds.add("strKeyField");

entity.setKeyFields(keyFlds);

// End of new settings, nothing else here is DML related

entity.setIndexes(Collections.<QueryIndex>emptyList());

cacheCfg.setQueryEntities(Collections.singletonList(entity));

ignite.createCache(cacheCfg);
//xml版本
<bean class="org.apache.ignite.configuration.CacheConfiguration">
    <property name="name" value="personCache"/>
    <!-- Configure query entities -->
    <property name="queryEntities">
        <list>
            <bean class="org.apache.ignite.cache.QueryEntity">
                <!-- Registering key's class. -->
                <property name="keyType" value="CustomKey"/>
              
                <!-- Registering value's class. -->
                <property name="valueType"
                          value="org.apache.ignite.examples.Person"/>

                <!-- 
                    Defining all the fields that will be accessible from DML.
                -->
                <property name="fields">
                    <map>
                        <entry key="firstName" value="java.lang.String"/>
                        <entry key="lastName" value="java.lang.String"/>
                      	<entry key="intKeyField" value="java.lang.Integer"/>
                      	<entry key="strKeyField" value="java.lang.String"/>
                    </map>
                </property>
              
                <!-- Defining the subset of key's fields -->
                <property name="keyFields">
                    <set>
                      	<value>intKeyField<value/>
                      	<value>strKeyField<value/>
                    </set>
                </property>
            </bean>
        </list>
    </property>
</bean>

4.5 DML Operations

4.5.1 MERGE

MERGE是一个非常简单的操作,因为它会被翻译成cache.put(…)或者cache.putAll(…),具体是哪一个,取决于MERGE语句涉及的要插入或者要更新的记录的数量。

下面的示例显示如何通过MERGE命令来更新数据集。一个是提供了条目列表,一个是通过执行子查询注入一个结果集。

//MERGE(缓存条目列表)
cache.query(new SqlFieldsQuery("MERGE INTO Person(_key, firstName, lastName)"
					 + 	"values (1, 'John', 'Smith'), (5, 'Mary', 'Jones')"));
//MERGE(子查询)
cache.query(new SqlFieldsQuery("MERGE INTO someCache.Person(_key, firstName, lastName) (SELECT _key + 1000, firstName, lastName " +
   	"FROM anotherCache.Person WHERE _key > ? AND _key < ?)").setArgs(100, 200);

4.5.2 INSERT

MERGE和INSERT命令的不同在于,后者添加的条目必须是缓存中不存在的。 如果要把一个键值对插入缓存,那么最后,INSERT语句会被转换为cache.putIfAbsent(…)操作,否则,如果插入的是多个键值对,那么DML引擎会为每个对创建一个EntryProcessor,然后使用cache.invokeAll(…)将数据注入缓存。

下面的示例显示如何通过INSERT命令插入一个数据集,一个是提供了条目列表,一个是通过执行子查询注入一个结果集。

//INSERT(条目列表):
cache.query(new SqlFieldsQuery("INSERT INTO Person(_key, firstName, " +
         "lastName) values (1, 'John', 'Smith'), (5, 'Mary', 'Jones')"));
INSERT(子查询)
cache.query(new SqlFieldsQuery("INSERT INTO someCache.Person(_key, firstName, lastName) (SELECT _key + 1000, firstName, secondName " +
   	"FROM anotherCache.Person WHERE _key > ? AND _key < ?)").setArgs(100, 200);

4.5.3 UPDATE

这个操作会更新缓存中的值的每个字段。 开始时,SQL引擎会根据UPDATE语句的WHERE条件生成并且执行一个SELECT查询,然后会修改满足条件的已有值。 修改的执行是利用cache.invokeAll(…)实现的。基本上来说,这意味着一旦SELECT查询的结果准备好,SQL引擎就会准备一定数量的EntryProcessors然后执行cache.invokeAll(…)操作,下一步,EntryProcessors修改完数据之后,会进行额外的检查来确保在SELECT和数据实际更新之间没有其他干扰。

下面这个简单示例显示了如何执行UPDATE语句。

cache.query(new SqlFieldsQuery("UPDATE Person set lastName = ? " +
         "WHERE _key >= ?").setArgs("Jones", 2L));

PS:UPDATE语句无法更新缓存键及其字段
原因是缓存键的状态决定了内部数据的布局及其一致性(键的哈希及其关系,索引完整性),所以目前除非先将其删除,否则无法更新缓存键。比如下面的查询:

UPDATE _key = 11 where _key = 10; 

会导致下面的缓存操作:

		val = get(10); 
		put(11, val); 
		remove(10);

4.5.4 DELETE

DELETE语句的执行分为两个阶段,类似于UPDATE语句的执行。

首先,使用SELECT查询,SQL引擎收集那些满足DELETE语句中WHERE子句的键。接下来,把所有的缓存键都放好后,它创建了一些EntryProcessors并以cache.invokeAll(…)来执行它们。当数据被删除时,会执行额外的检查以确保没有人干扰到数据的SELECT和实际删除。

下面的示例展示了如何在Apache点火中执行删除查询。

cache.query(new SqlFieldsQuery("DELETE FROM Person " +
         "WHERE _key >= ?").setArgs(2L));
PS:Streaming Mode

使用ignite JDBC驱动程序,您可以在所谓的流模式中实现快速数据预加载。在JDBC驱动程序文档的流相关部分了解更多关于此模式的信息。

4.6 Modifications Order

如果DML语句inserts/updates了指向_val字段的整个值,同时尝试修改属于_val的字段,那么应用更改的顺序是:
1.首先更新/插入_val。
2.字段更新。

无论您如何在DML语句中定义它,顺序都不会改变。例如,在执行下面的语句之后,最后Person的值将是“Mike Smith”,忽略了在查询中_val位于firstName后面的事实.

cache.query(new SqlFieldsQuery("INSERT INTO Person(_key, firstName, _val)" +
           " VALUES(?, ?, ?)").setArgs(1L, "Mike", new Person("John", "Smith")));
这与下面的查询的执行类似,这里_val在前面:
cache.query(new SqlFieldsQuery("INSERT INTO Person(_key, _val, firstName)" +
           " VALUES(?, ?, ?)").setArgs(1L, new Person("John", "Smith"), "Mike"));

对于_val及其字段变更顺序的问题,INSERT、UPDATE和MERGE语句都是一样的。

4.7 Concurrent Modifications并发修改

如上所述,UPDATE和DELETE语句在内部生成SELECT查询,以便获得一组必须修改的缓存条目。来自集合的键不是锁定的,而且它们的值也有可能同时被其他查询修改。一由DML引擎实现了一种特殊的技术,首先,它可以避免锁定键,其次,保证值将在更新时被DML语句更新。

基本上,引擎检测到缓存条目的一个子集,这些缓存条目被同时修改,并重新执行SELECT语句,只将其范围限制在修改的键上。

总体而言,引擎会并发地检测要更新的缓存条目的子集,然后重新执行SELECT语句来限制要修改的键的范围。比如下面的要执行的UPDATE语句:

// Adding the cache entry.
cache.put(1, new Person("John", "Smith");
// Updating the entry.          
cache.query(new SqlFieldsQuery("UPDATE Person set firstName = ? " +
         "WHERE lastName = ?").setArgs("Mike", "Smith"));

在firstName和lastName更新之前,DML引擎会生成SELECT查询来获得符合UPDATE语句的WHERE条件的缓存条目,语句如下:

SELECT _key, _value, "Mike" from Person WHERE lastName = "Smith"

之后通过SELECT获得的条目会被并发地更新:

cache.put(1, new Person("Sarah", "Connor"))

DML引擎在UPDATE语句执行的更新阶段会检测到键为1的缓存条目要被修改,之后会暂停更新并且重新执行一个SELECT查询的修订版本来获得最新的条目值:

SELECT _key, _value, "Mike" from Person WHERE secondName = "Smith"
    AND _key IN (SELECT * FROM TABLE(KEY long = [ 1 ]))

这个查询只会为过时的键执行,本例中只有一个键1。

这个过程会一直重复,直到DML引擎确信在更新阶段所有的条目都已经更新到最新版。尝试次数的最大值是4,目前并没有配置参数来改变这个值。

PS:DML引擎不会为并发删除的条目重复执行SELECT语句,重复执行的查询只针对还在缓存中的条目。

4.8 Known Limitations

4.8.1 Subqueries in WHERE clause

INSERT和MERGE语句中的子查询和UPDATE和DELETE操作自动生成的SELECT查询一样,如有必要都会被分布化然后执行,要么是并置,要么是非并置的模式。

然而,如果WHERE语句里面有一个子查询,那么他是不会以非并置的分布式模式执行的,子查询始终都会以并置的模式在本地节点上执行。

比如,有这样一个查询:

DELETE FROM Person WHERE _key IN
    (SELECT personId FROM "salary".Salary s WHERE s.amount > 2000)

然后DML引擎会生成SELECT查询来获得要删除的条目列表,这个查询会在整个集群中分布化并且执行,如下所示:

SELECT _key, _val FROM Person WHERE _key IN
    (SELECT personId FROM "salary".Salary s WHERE s.amount > 2000)

然而,IN子句中的子查询(SELECT personId FROM “salary”.Salary …)不会被进一步分布化,只会在一个集群节点的本地数据集上执行。

4.8.2 Transactional Support事务性支持

目前,DML仅仅支持原子模式,意味着如果有一个DML查询作为Ignite事务的一部分,那么它是不会加入事务的写队列,会被立刻执行。

PS:多版本并发控制(MVCC)
一旦Ignite SQL网格使用MVCC进行控制,DML操作也会支持事务模式。

4.8.3 EXPLAIN support for DML statements

目前DML操作不支持EXPLAIN。

一个方法就是执行UPDATE或DELETE语句自动生成的SELECT语句或者DML语句使用的INSERT或MERGE语句的执行计划,这样会提供一个要执行的DML操作所使用的索引情况。

4.9 Example示例

Ignite在源代码中包含了一个可以立即执行的CacheQueryDmlExample,关于DML的使用,请参考代码吧。

5. Distributed DDL

5.1 概述

Apache ignite支持使用数据定义语言(DDL)语句在运行时创建和删除SQL索引。原生的Ignite SQL API以及JDBC和ODBC驱动都可以用于SQL模式的修改。

PS:Full-fledged DDL Support
在未来的Apache ignite发行版中,您可以期望看到对附加的广泛使用的DDL语句的支持。

5.2 CREATE INDEX创建索引

语法:
CREATE [SPATIAL] INDEX [IF NOT EXISTS] indexName ON tableName (indexColumn, …) indexColumn := columnName [ASC|DESC]

其中tableName是存储在分布式缓存中的类型名。

下面是创建一个简单有序索引的示例:

CREATE INDEX idx_person_name ON Person (name)

若要创建一个复合索引,请使用下面的命令:

CREATE INDEX idx_person_name_birth_date ON Person (name ASC, birth_date DESC)

添加SPATIAL关键字来定义地理空间索引:

CREATE SPATIAL INDEX idx_person_address ON Person (address)

5.3 DROP INDEX 删除索引

语法
DROP INDEX [IF EXISTS] indexName

DROP INDEX idx_person_name

5.4 Apache Ignite SQL API

DDL命令可以通过SqlFieldsQuery类执行,如下所示:

IgniteCache<PersonKey, Person> cache = ignite.cache("Person");

SqlFieldsQuery query = new SqlFieldsQuery(
    "CREATE INDEX idx_person_name ON Person (name)");

cache.query(query).getAll();

##5 .5 JDBC driver
下面的示例演示了如何使用Apache ignite JDBC驱动程序创建索引。

Class.forName("org.apache.ignite.IgniteJdbcDriver");

Connection conn = DriverManager.getConnection(
    "jdbc:ignite:cfg://file:///etc/config/ignite-jdbc.xml");

try (Statement stmt = conn.createStatement()) {
    stmt.execute("CREATE INDEX idx_person_name ON Person (name)");
}

6. Miscellaneous Features

SQL Grid中可用的其他特性

6.1 Query Cancellation 查询取消

有两种方法可以防止由于非最优索引配置而导致的长时间运行SQL查询。

  1. 第一种方法是为特定的SqlQuery或SqlFieldsQuery设置查询执行超时。
SqlQuery qry = new SqlQuery<AffinityKey<Long>, Person>(Person.class, joinSql);

// Setting query execution timeout
qry.setTimeout(10_000, TimeUnit.SECONDS);
  1. 关于查询取消的第二种方法是使用QueryCursor.close()停止查询。
SqlQuery qry = new SqlQuery<AffinityKey<Long>, Person>(Person.class, joinSql);

// Getting query cursor.
QueryCursor<List> cursor = cache.query(qry);
        
// Executing query.
....
        
// Halting the query that might be still in progress.
cursor.close();		

PS:查询取消API在ignite 1.8和后续版本中得到支持。

6.2 Custom SQL Functions 自定义SQL函数

Ignite的SQL引擎支持通过额外用Java编写的自定义SQL函数,来扩展ANSI-99规范定义的SQL函数集。

一个自定义SQL函数仅仅是一个加注了@QuerySqlFunction注解的公共静态方法。

// Defining a custom SQL function.
public class MyFunctions {
    @QuerySqlFunction
    public static int sqr(int x) {
        return x * x;
    }
}

持有自定义SQL函数的类需要使用setSqlFunctionClasses(…)方法在特定的CacheConfiguration中注册。

// Preparing a cache configuration.
CacheConfiguration cfg = new CacheConfiguration();
// Registering the class that contains custom SQL functions.
cfg.setSqlFunctionClasses(MyFunctions.class);

在使用上面的配置进行缓存之后,可以从SQL查询中调用定制函数,如下所示。

// Preparing the query that uses customly defined 'sqr' function.
SqlFieldsQuery query = new SqlFieldsQuery(
  "SELECT name FROM Blocks WHERE sqr(size) > 100");

// Executing the query.
cache.query(query).getAll();  

PS:在自定义SQL函数可能要执行的所有节点上,通过CacheConfiguration.setSqlFunctionClasses(…)注册的类都需要添加到类路径中。否则,您将在自定义函数的执行时间中获得ClassNotFoundException异常。

7. Configuration Parameters 参数配置

有一些SQL查询相关的属性,您可以调整以影响查询执行行为。

这些参数分为全局参数和查询级参数,全局参数在CacheConfiguration层面配置,在该缓存上执行的所有查询都会受到影响。
##7.1 Cache Configuration Properties

属性方法描述默认值
setSqlSchema(…)设置用于当前缓存的sql模式。这个名称将对应于SQL ansi-99标准。未引用的标识符不是区分大小写的。引用的标识符是区分大小写的。缓存名字
setSqlEscapeAll(…)如果设置为true,所有的sql表和字段的名字将会加上双引号,比如"tableName".“fieldsName”,这样会强制字段名区分大小写,同时也允许表名和字段名有特殊字符。false
setSqlOnheapRowCacheSize(…)定义缓存在堆内的SQL行数,来避免每次SQL索引访问的反序列化,这个参数只有在该缓存开启了堆外的时候才会起作用10240
setSnapshotableIndex(…)为存储在Java堆内的索引数据开启快照索引实现。false

7.2 SqlFields and SqlFieldsQuery Configuration Properties

属性方法描述默认值
setCollocated(…)并置标志用于以组的语法优化查询。当ignite执行一个分布式查询时,它会向单个集群成员发送子查询。如果事先知道要查询的数据是在同一个节点上并置在一起的然后又对并置键(主键或者关系键)进行分组,那么可以通过在远程节点上分组数据来实现显著的性能和网络优化。false
setDistributedJoins(…)为一个特定的查询开启非并置模式的分布式关联。false
setEnforceJoinOrder(…)配置一个标志来强制查询中的表关联顺序,如果配置为true,查询优化器就不会对join子句的表进行重新排序。false
setReplicatedOnly(…)如果SQL查询对应的数据都在复制缓存上,那么可以将该参数设置为true,这是给SQL引擎的一个特别提示,它会为查询产生更高效的执行计划。false
setLocal(…)强制查询在纯本地模式下执行。false
setPageSize(…)定义可以在单个响应块中传输的最大条目数,以减少节点(查询发起者)。1024
setPartitions(…)为查询执行设置分区。查询只在指定分区的主节点上执行。null
setTimeout(…)配置查询执行的超时时间,如果正在执行的查询超过了该值,其会被自动取消。默认是禁用的,Ignite的1.8及其以后版本才可用。0
setAlias(…)设置一个查询中用作表名的别名。null

8.JDBC Driver

使用标准JDBC驱动程序连接启动。

8.1 JDBC Connection

Ignite提供了JDBC驱动,使得在JDBC API端就可以通过标准SQL查询从缓存中获得分布式数据,以及使用DML语句比如
INSERT、UPDATE或者DELETE更新数据。

Ignite中,JDBC连接URL的规则如下:
jdbc:ignite:cfg://[<params>@]<config_url>
  1. <config_url>是必需的,表示对于通过JDBC Driver建立连接期间待启动的Ignite客户端节点来说指向Ignite配置文件的任意合法URL,JDBC驱动会转发应用通过客户端发给集群的查询。
  2. 是可选的,格式如下:是可选的,格式如下:
param1=value1:param2=value2:...:paramN=valueN

支持以下参数:

属性方法描述默认值
cache缓存名,如果未定义会使用默认的缓存,区分大小写
nodeId要执行的查询所在节点的Id,对于在本地查询是有用的
local查询只在本地节点执行,这个参数和nodeId参数都是通过指定节点来限制数据集false
collocated优化标志,当Ignite执行一个分布式查询时,他会向单个的集群节点发送子查询,如果提前知道要查询的数据已经被并置到同一个节点,Ignite会有显著的性能提升和网络优化false
distributedJoins可以在非并置的数据上使用分布式关联。false
streaming通过INSERT语句为本链接开启批量数据加载模式,具体可以参照后面的流模式相关章节。false
streamingAllowOverwrite通知Ignite对于重复的已有键,覆写它的值而不是忽略他们,具体可以参照后面的流模式相关章节。false
streamingFlushFrequency超时时间,毫秒,数据流处理器用于刷新数据,数据默认会在连接关闭时刷新,具体可以参照后面的流模式相关章节。0
streamingPerNodeBufferSize数据流处理器的每节点缓冲区大小,具体可以参照后面的流模式相关章节。1024
streamingPerNodeParallelOperations数据流处理器的每节点并行操作数。具体可以参照后面的流模式相关章节。16

当前,JDBC驱动需要将一组jar包添加到应用或者SQL工具的类路径中,打开{apache_ignite_release}\libs文件夹,然后导入其中以及ignite-indexing和ignite-spring子文件夹的所有jar文件。

PS:Client vs Server Nodes
所有的节点默认都是以服务端模式启动的,客户端模式需要显式地开启,然而无论怎么配置,JDBC驱动都会以客户端模式启动一个节点。

PS:Cross-Cache Queries跨缓存查询
驱动连接到的缓存会被视为默认的模式,要跨越多个缓存进行查询,可以参照数据网格里的cache query章节。
PS:关联和并置
就像数据网格里的Cache Queries章节描述的那样,通过IgniteCacheAPI,如果关联对象是以并置模式存储的话,在分区缓存上的关联是可以正常执行的。细节可以参照数据网格的Affinity Collocation章节。

PS:复制与缓存分区
在复制缓存上的查询会直接在一个节点上执行,而在分区缓存上的查询是分布在所有缓存节点上的

8.2 Streaming Mode 流模式

使用JDBC驱动程序在流模式(批量模式)中将数据添加到ignite集群中是可行的。在此模式中,驱动程序在内部实例化IgniteDataStreamer并为其提供数据。要激活这个模式,可以向JDBC连接字符串添加流参数设置。

// Register JDBC driver.
Class.forName("org.apache.ignite.IgniteJdbcDriver");
 
// Opening connection in the streaming mode.
Connection conn = DriverManager.getConnection("jdbc:ignite:cfg://streaming=true@file:///etc/config/ignite-jdbc.xml");

目前,流模式仅支持INSERT操作,当需要将快速数据预加载用例到缓存时,它很有用。JDBC驱动程序定义了影响流模式行为的多个连接参数。参数列在上面的参数表中。

这些参数几乎涵盖了通用IgniteDataStreamer的所有设置,并允许您根据需要对streamer进行微调。有关如何配置streamer的更多信息,请参阅“ignite文档”的STREAMING & CEP下的Data Streamers部分。

PS:基于时间的刷新
默认情况下,当要么连接关闭,要么达到了streamingPerNodeBufferSize,数据才会被刷新,如果希望按照时间的方式来刷新,那么可以调整streamingFlushFrequency参数。

// Register JDBC driver.
Class.forName("org.apache.ignite.IgniteJdbcDriver");
// Opening a connection in the streaming mode and time based flushing set.
Connection conn = DriverManager.getConnection("jdbc:ignite:cfg://streaming=true@streamingFlushFrequency=1000@file:///etc/config/ignite-jdbc.xml");
PreparedStatement stmt = conn.prepareStatement(
  "INSERT INTO Person(_key, name, age) VALUES(CAST(? as BIGINT), ?, ?)");
// Adding the data.
for (int i = 1; i < 100000; i++) {
      // Inserting a Person object with a Long key.
      stmt.setInt(1, i);
      stmt.setString(2, "John Smith");
      stmt.setInt(3, 25);
      stmt.execute();
}
conn.close();
// Beyond this point, all data is guaranteed to be flushed into the cache.

8.3 样例

Ignite JDBC驱动会自动地只获取缓存中存储的对象中实际需要的那些字段。假设你有一个这样的Person类:

public class Person {
    @QuerySqlField
    private String name;
 
    @QuerySqlField
    private int age;
 
    // Getters and setters.
    ...
}

如果在缓存中有该类的实例,您可以通过标准JDBC API查询单个字段(名称、年龄或两者):

// Register JDBC driver.
Class.forName("org.apache.ignite.IgniteJdbcDriver");
 
// Open JDBC connection (cache name is not specified, which means that we use default cache).
Connection conn = DriverManager.getConnection("jdbc:ignite:cfg://file:///etc/config/ignite-jdbc.xml");
 
// Query names of all people.
ResultSet rs = conn.createStatement().executeQuery("select name from Person");
 
while (rs.next()) {
    String name = rs.getString(1);
    ...
}
 
// Query people with specific age using prepared statement.
PreparedStatement stmt = conn.prepareStatement("select name, age from Person where age = ?");
 
stmt.setInt(1, 30);
 
ResultSet rs = stmt.executeQuery();
 
while (rs.next()) {
    String name = rs.getString("name");
    int age = rs.getInt("age");
    ...
}

此外,还可以使用DML语句来修改数据。

8.3.1 INSERT

// Insert a Person with a Long key.
PreparedStatement stmt = conn.prepareStatement("INSERT INTO Person(_key, name, age) VALUES(CAST(? as BIGINT), ?, ?)");
 
stmt.setInt(1, 1);
stmt.setString(2, "John Smith");
stmt.setInt(3, 25);

stmt.execute();

8.3.2MERGE

// Merge a Person with a Long key.
PreparedStatement stmt = conn.prepareStatement("MERGE INTO Person(_key, name, age) VALUES(CAST(? as BIGINT), ?, ?)");
 
stmt.setInt(1, 1);
stmt.setString(2, "John Smith");
stmt.setInt(3, 25);
 
stmt.executeUpdate();

8.3.3 UPDATE

// Update a Person.
conn.createStatement().
  executeUpdate("UPDATE Person SET age = age + 1 WHERE age = 25");

8.3.4 DELETE

conn.createStatement().execute("DELETE FROM Person WHERE age = 25");

ignite-jdbc.xml的最小版本大概像下面这样:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
        <property name="clientMode" value="true"/>

        <property name="peerClassLoadingEnabled" value="true"/>

        <!-- Configure TCP discovery SPI to provide list of initial nodes. -->
        <property name="discoverySpi">
            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
                <property name="ipFinder">
                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder"/>
                </property>
            </bean>
        </property>
    </bean>
</beans>

8.4 Hostname Based JDBC Connection

对于以前版本的Apache ignite(之前的1.4)JDBC连接URL有以下模式:

jdbc:ignite://<hostname>:<port>/<cache_name>

如果您方便的话,您仍然可以在当前的Apache点火版本中继续使用这个驱动程序。有关详细信息,请参阅下面的文档。

要在应用或者SQL工具中使用该驱动,需要将{apache_ignite_release}\libs\ignite-core-{version}.jar添加到类路径中。

9. Geospatial Support

处理空间数据类型

9.1 概述

Ignite除了支持标准ANSI-99标准的SQL查询,支持基本数据类型或者特定/自定义对象类型之外,还可以查询和索引几何数据类型,比如点、线以及包括这些几何形状空间关系的多边形。 空间信息的查询功能,以及对应的可用的函数和操作符,是在SQL的简单特性规范中定义的,Ignite使用的JTS Topology Suite
完全实现了这个规范,它和H2一起,以分布式和容错的方式构建了一个独特的空间组件。

9.2 Including Ignite Geospatial Library

Ignite的空间库(ignite-geospatial)依赖于JTS,它是LGPL许可证,不同于Apache的许可证,因此ignite-geospatial并没有包含在Ignite的发布版中。 因为这个原因,ignite-geospatial的二进制库版本位于如下的Maven仓库中:

<repositories>
    <repository>
    <id>GridGain External Repository</id>
    <url>http://www.gridgainsystems.com/nexus/content/repositories/external</url>
    </repository>
</repositories>

在pom.xml中添加这个仓库以及如下的Maven依赖之后,就可以将该空间库引入应用中了。

<dependency>
    <groupId>org.apache.ignite</groupId>
  <artifactId>ignite-geospatial</artifactId>
  <version>${ignite.version}</version>
</dependency>

另外,也可以下载Ignite的源代码自己构建这个库。

9.3 Executing Geospatial Queries

这个空间模块只对com.vividsolutions.jts类型的对象有用。 要配置索引以及/或者几何类型的可查询字段,可以使用和已有的非几何类型同样的方法,首先,可以使用org.apache.ignite.cache.QueryEntity定义索引,他对于基于Spring的XML配置文件非常方便,第二,通过@QuerySqlField注解来声明索引也可以达到同样的效果,他在内部会转化为QueryEntities。

//QuerySqlField方式
private static class MapPoint {
    /** Coordinates. */
    @QuerySqlField(index = true)
    private Geometry coords;

    /**
     * @param coords Coordinates.
     */
    private MapPoint(Geometry coords) {
        this.coords = coords;
    }
}
//QueryEntity方式
<bean class="org.apache.ignite.configuration.CacheConfiguration">
    <property name="name" value="mycache"/>
    <!-- Configure query entities -->
    <property name="queryEntities">
        <list>
            <bean class="org.apache.ignite.cache.QueryEntity">
                <property name="keyType" value="java.lang.Integer"/>
                <property name="valueType" value="org.apache.ignite.examples.MapPoint"/>

                <property name="fields">
                    <map>
                        <entry key="coords" value="com.vividsolutions.jts.geom.Geometry"/>
                    </map>
                </property>

                <property name="indexes">
                    <list>
                        <bean class="org.apache.ignite.cache.QueryIndex">
                            <constructor-arg value="coords"/>
                        </bean>
                    </list>
                </property>
            </bean>
        </list>
    </property>
</bean>

在使用上面的方法定义几何类型字段之后,就可以使用这些字段中存储的值执行查询了。

// Query to find points that fit into a polygon.
SqlQuery<Integer, MapPoint> query = new SqlQuery<>(MapPoint.class, "coords && ?");

// Defining the polygon's boundaries.
query.setArgs("POLYGON((0 0, 0 99, 400 500, 300 0, 0 0))");

// Executing the query.
Collection<Cache.Entry<Integer, MapPoint>> entries = cache.query(query).getAll();

// Printing number of points that fit into the area defined by the polygon.
System.out.println("Fetched points [" + entries.size() + ']');

9.4 完整的例程

可以在这里找到一个现成-是-运行示例,演示在地理空间查询的用法。

地址:https://github.com/dmagda/geospatial

10. Performance and Debugging

性能考虑和调试技术

10.1 Using EXPLAIN Statement

ignite支持“EXPLAIN…"这种用于阅读执行计划和查询性能调查的语法。注意:一个计划游标会包含多行:最后一行是汇总节点的查询,其他是映射节点的。

SqlFieldsQuery sql = new SqlFieldsQuery(
  "explain select name from Person where age = ?").setArgs(26); 

System.out.println(cache.query(sql).getAll());

执行计划本身由H2生成:
http://www.h2database.com/html/performance.html#explain_plan

10.2 Using H2 Debug Console

当用Ignite进行开发时,有时对于检查表和索引是否正确或者运行在嵌入节点内部的H2数据库中的本地查询是非常有用的,为此Ignite提供了启动H2控制台的功能。要启用该功能,在启动节点时要将IGNITE_H2_DEBUG_CONSOLE系统属性或者环境变量设置为true。然后就可以在浏览器中打开控制台,可能需要点击控制台中的刷新按钮,因为有可能控制台在数据库对象初始化之前打开。

这里写图片描述

10.3 SQL Performance and Usability Considerations

在运行SQL查询时,应该考虑一些常见的陷阱。

  1. 如果查询使用了操作符OR那么他可能不是以期望的方式使用索引。比如对于查询:select name from Person where sex=‘M’ and (age = 20 or age = 30),会使用sex字段上的索引而不是age上的索引,虽然后者选择性更强。要解决这个问题需要用UNION ALL重写这个查询(注意没有ALL的UNION会返回去重的行,这会改变查询的语意而且引入了额外的性能开销),比如:select name from Person where sex=‘M’ and age = 20 UNION ALL select name from Person where sex=‘M’ and age = 30。
  2. 如果查询使用了操作符IN,那么会有两个问题:首先无法提供可变参数列表,这意味着需要在查询中指定明确的列表,比如如果查询使用了操作符IN,那么会有两个问题:首先无法提供可变参数列表,这意味着需要在查询中指定明确的列表,比如where id in (?, ?, ?),但是不能写where id in ?然后传入一个数组或者集合。第二,查询无法使用索引,要解决这两个问题需要像这样重写查询:select p.name from Person p join table(id bigint = ?) i on p.id = i.id,这里可以提供一个任意长度的对象数组(Object[])作为参数,然后会在字段id上使用索引。注意基本类型数组(比如int[],long[]等)无法使用这个语法,但是可以使用基本类型的包装器。

Example:

new SqlFieldsQuery(
  "select * from Person p join table(id bigint = ?) i on p.id = i.id").setArgs(new Object[]{ new Integer[] {2, 3, 4} }))
它转换为以下SQL:
select * from "cache-name".Person p join table(id bigint = (2,3,4)) i on p.id = i.id

10.4 Query Parallelism

默认情况下,一个SQL查询在每一个分区的ignite节点行的一个线程中执行,即单线程。这种方法对于返回包含索引搜索的小结果集的查询是最优的。例如:

select * from Person where p.id = ?
某些查询可能会受益于在多个线程中执行。这涉及到对表扫描和聚合的查询,这通常是OLAP工作负载的情况。例如:
select SUM(salary) from Person

通过CacheConfiguration.queryParallelism属性可以控制查询的并行化,这个参数定义了在单一节点中执行查询时使用的线程数。

如果查询包含JOIN,那么所有相关的缓存都应该有相同的并行化配置。

PS:慎用
当前,该属性影响在给定缓存上执行的所有查询。在为很重OLAP查询提供加速时,这个选项可能会减慢其他简单的查询。这种行为将在进一步的版本中得到改进。

10.5 Index Hints 索引提示

当知道一个索引比另一个索引更适合某些查询时,索引提示在场景中是有用的。它们还需要指导查询优化器选择更有效的执行计划。要在Apache ignite中进行这种优化,您可以使用 USE INDEX(indexA,…,indexN)语句,它会告诉Ignite对于查询的执行只会使用给定名字的索引之一。

下面是一个栗子:

SELECT * FROM Person USE INDEX(index_age)
  WHERE salary > 150000 AND age < 35;

10.6 Querying Replicated Caches(查询复制模式的缓存)

如果只在复制缓存所在的数据上执行SQL查询,那么可以设置SqlQuery.setReplicatedOnly(…)为true,这个给SQL引擎的特别提示会为查询产生更高效的执行计划。

10.7 Advanced DML Optimizations

使用UPDATE和DELETE语句时,需要执行一个SELECT查询来获取之后要处理的缓存条目集。在某些情况下,与直接将DML语句转为特定的缓存操作相比,这样可以避免导致显著的性能问题。

总结一下分布式DML节的内容,之所以UPDATE和DELETE会自动执行一个SELECT查询,有如下的原因:
1.UPDATE或者DELETE语句的WHERE子句会使用复杂的过滤。这在使用复杂而高级的条目过滤时就会发生,这时DML引擎需要做
额外的工作来准备要被DML语句更新的条目列表;
2.UPDATE语句包括表达式。即使WHERE子句比较简单并且通过使用_key或者_val直接指向要修改的缓存条目,这个表达式的
执行结果仍然可能产生新的字段值,这也是为什么DML引擎需要执行一个SELECT来评估表达式的执行结果;
3.UPDATE语句修改一个缓存条目的特定字段。DML引擎首先需要获取当前的缓存条目,再修改然后将其放回缓存。

10.7.1 Executing DML faster

要在最快的方法来执行 DML 操作,必须满足以下要求:
1.DML操作不能触发SELECT查询执行。
2.操作必须调整一个缓存条目。

为了满足上述要求,必须遵守以下规则:
1.筛选出 _key 和 _val 关键字只使用的缓存条目。
2.这些参数必须在DML语句中显式使用。不能访问和执行缓存条目的字段或表达式。
3.如果执行了UPDATE语句,那么它必须更新整个缓存条目(_val),而不是特定的字段。

让我们看看下面的例子。

cache.query(new SqlFieldsQuery("UPDATE Person SET _val = ?3" +
    " WHERE _key = ?1 and _val = ?2").setArgs(7, 1, 2));

UPDATE语句如下:
1.显式地告诉我们需要通过指定_key来更新哪个缓存条目属于和输入的预期值(_val)。
2.通过使用_val关键字更新整个缓存条目的值。

因此,DML引擎将执行下面的缓存操作

cache.replace(7, 1, 2);
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值