TiDB 最佳实践系列(五)Java 数据库应用开发指南

 2024-01-16 05:02:51  阅读 0

Java是目前非常流行的开发语言。 很多 TiDB 用户的业务层都是使用 Java 开发的。 本文将从Java数据库交互组件开发的角度出发,介绍各个组件的推荐配置和推荐使用方法。 希望对Java开发有所帮助。 读者在使用 TiDB 时可以更好地利用数据库性能。

Java应用程序中与数据库相关的组件

Java应用程序中常用的与数据库相关的组件有:

java 连接池是什么_连接池实现_java连接池druid

如上图所示,应用程序可以用来管理和控制事务的非手动启动和停止,通过类似的数据访问框架来管理SQL的生成和执行,通过连接池获取池化的长连接,最后通过JDBC接口调用实现MySQL协议并执行。 TiDB 完成交互。

接下来我们将介绍一下各个组件在使用时可能需要注意的问题。

数据库连接

尽管Java应用程序可以选择打包在不同的框架中,但在最底层,它们通常通过调用JDBC与数据库服务器进行交互。 对于JDBC来说,主要需要注意的是:API选择和API参数配置。

1. 数据库接口

基本的 JDBC API 使用方法可以参考 JDBC 官方教程。 本文主要强调几个重要的 API 选择。

1.1 使用API

对于OLTP场景,程序发送到数据库的SQL语句是有一定类型的,去掉参数变化后可以枚举出来。 因此,建议使用准备好的语句 ( ) 代替普通的文本执行,并复用直接执行,从而避免 TiDB 重复解析的开销。

目前大多数上层框架都是调用API来执行SQL。 如果直接使用JDBC API进行开发,要谨慎选择使用API​​。

另外需要注意的是,MySQL /J 实现默认只会在客户端进行语句预处理,并且会发送 ? 被客户替换后以文本形式发送给客户。 因此,除了使用API​​外,还需要在JDBC连接参数中=true。 ,可以在 TiDB 服务器上进行语句预处理(详细内容见下面的参数配置章节)。

1.2 使用Batch批量插入和更新

对于批量插入更新,如果插入的记录较多,可以选择使用/API。 通过这种方法,多条SQL插入和更新记录首先缓存在客户端,然后一起发送到数据库服务器。

注意:

对于MySQL/J实现来说,默认的Batch只是将多个SQL发送机会延迟到调用的时候,但实际的网络发送仍然会一一发送,这通常不会减少与数据库服务器的网络交互次数。

如果希望Batch网络发送批量插入,则需要在JDBC连接参数中配置ents=true(详细内容在下面的参数配置章节中介绍)。

1.3 使用流式获取执行结果

一般情况下,为了提高执行效率,JDBC会默认提前获取查询结果,并将其保存在客户端内存中。 然而,在查询返回非常大的结果集的场景下,客户端会希望数据库服务器减少一次返回给客户端的记录数,并等到客户端在有限的时间内处理完部分结果。在向服务器询问下一批之前的内存。

JDBC中通常有两种处理方式:

TiDB 两种方式都支持,但更推荐使用第一种方式,设置为 .,比第二种功能实现更简单,效率更高。

2.MySQL JDBC参数

JDBC 实现通常以 JDBC URL 参数的形式提供与实现相关的配置。 这里使用MySQL官方/J来介绍参数配置(如果您正在使用,可以参考类似的配置)。 由于配置项较多,这里重点关注几个可能影响性能的参数。

2.1 相关参数

默认为false,即即使使用API​​,也只会在客户端做“”。 因此,为了避免服务器重复解析的开销,如果同一条SQL语句需要多次使用API​​,建议将该选项设置为true。

在 TiDB 监控中,可以通过 Query > QPS By 查看请求命令类型。 如果请求被替换为or,则生效。

虽然=true允许服务器执行语句,但默认情况下客户端会在每次执行完后关闭该语句,不会重复使用,因此效率甚至不如文本执行。 因此,建议同时启用=true和配置=true,这样可以让客户端缓存该语句。

在 TiDB 监控中,您可以通过 Query > QPS By 查看请求命令类型。 如下图所示,请求数大很多就会生效。

java连接池druid_连接池实现_java 连接池是什么

另外,通过=配置会同时配置多个参数,包括=true。

配置完成后,还需要注意t配置(默认为256)。 此配置控制客户端缓存语句的最大长度。 如果超过这个长度,就不会被缓存。

在某些场景下,SQL的长度可能会超过此配置,导致SQL无法复用。 建议根据应用SQL的长度来决定是否增大该值。

在 TiDB 监控中,通过 Query > QPS by 检查请求命令类型。 如果已经配置了=true,但仍然基本等于and has,则需要检查该配置项是否设置太小。

控制缓存语句的数量(默认为 25)。 如果应用程序需要多种类型的 SQL 并希望重用语句,则可以增大该值。

与上一类似,通过监控中的“查询 > QPS”查看请求数是否大很多,以确认是否正常。

2.2 批次相关参数

执行批量写入处理时,建议配置ents=true。 使用或默认使用JDBC后,SQL仍然会一一发送,例如:

pstmt = prepare(“insert into t (a) values(?)”);
pstmt.setInt(1, 10);
pstmt.addBatch();
pstmt.setInt(1, 11);
pstmt.addBatch();
pstmt.setInt(1, 12);
pstmt.executeBatch();

虽然使用了批处理,但仍然有多个单独的语句发送到 TiDB:

insert into t(a) values(10);
insert into t(a) values(11);
insert into t(a) values(12);

如果设置 ents=true,则发送到 TiDB 的 SQL 将为:

insert into t(a) values(10),(11),(12);

需要注意的是,重写后的语句只能将多个值拼接成一整条SQL语句。 如果语句中存在其他差异,则不能重写。 例如:

insert into t (a) values (10) on duplicate key update a = 10;
insert into t (a) values (11) on duplicate key update a = 11;
insert into t (a) values (12) on duplicate key update a = 12;

不会被重写为语句。 在这个例子中,如果SQL改写如下:

insert into t (a) values (10) on duplicate key update a = values(a);
insert into t (a) values (11) on duplicate key update a = values(a);
insert into t (a) values (12) on duplicate key update a = values(a);

可以满足重写条件,最终重写为:

insert into t (a) values (10), (11), (12) on duplicate key update a = values(a);

如果批量更新时有3次及以上更新,则SQL语句会以-的形式重写并发送。 这样可以有效减少客户端到服务器的请求开销,但副作用是会生成较大的SQL语句,比如这样:

update t set a = 10 where id = 1; update t set a = 11 where id = 2; update t set a = 12 where id = 3;

另外,由于客户端bug,不建议在batch以外的场景设置ents=true。

2.3 执行前检查参数

通过监控,你可能会发现,虽然业务只在集群上运行,但是你看到了很多冗余的语句。 通常这是因为 JDBC 发送一些查询设置类 SQL 语句(例如 @@.y)。 这些 SQL 对于 TiDB 来说是无用的,建议配置 = 以避免额外的开销。

= 将包含一组配置:

cacheServerConfiguration=true
useLocalSessionState=true
elideSetAutoCommits=true
alwaysSendSetIsolation=false
enableQueryTimeouts=false

配置完成后查看监控可以看到冗余语句减少了。

连接池

建立 TiDB(MySQL)连接是一个相对昂贵的操作(至少对于 OLTP 而言)。 除了建立 TCP 连接之外,还需要进行连接认证操作,因此客户端通常会将 TiDB(MySQL)连接保存到连接池中以供重复使用。

Java中有很多连接池实现(例如-jdbc、durid、c3p0、dbcp)。 TiDB 不限制使用的连接池。 应用程序可以根据业务特点选择连接池的实现方式。

1、连接数配置

比较常见的是应用程序需要根据自身情况配置合适的连接池大小,举个例子:

在使用连接池时,应用程序需要注意使用完成后返回连接。 建议应用程序使用相应的连接池相关的监控(如)。 通过监控,可以及时定位连接池问题。

2. 探索实时配置

连接池维护与 TiDB 的长连接。 TiDB 默认不会主动关闭客户端连接(除非报错),但一般客户端和 TiDB 之间会有 LVS 或者其他网络代理,它们通常会在连接空闲一定时间后关闭连接的时间。 然后主动清理连接。 连接池除了关注代理的空闲配置外,还需要保持连接或检测连接。

如果你经常在Java应用中看到以下错误:

The last packet sent successfully to the server was 3600000 milliseconds ago. The driver has not received any packets from the server. com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

如果 n 前的 n 为 0 或很小的值,通常是执行的 SQL 错误导致 TiDB 异常退出。 建议检查 TiDB 日志; 如果n是一个非常大的值(比如这里),很可能是因为这个连接空闲时间太长而被中间代理关闭了,通常的解决方案是增加代理的空闲配置,同时也可以使用连接池:

不同的连接池实现可能支持这些方法中的一种或多种。 可以查看使用的连接池文档找到对应的配置。

数据访问框架

业务应用程序经常使用某种数据访问框架来简化数据库访问。

1.

它是目前流行的Java数据访问框架,主要用于管理SQL并完成结果集和Java对象的来回映射。 与 TiDB 的兼容性非常好,从历史问题可以看出很少出现问题。 这里我们主要关注以下几个配置。

1.1 参数

支持两个参数:

1.2 动态SQL批处理

要支持自动将多个语句重写为...(...),(...),...的形式,除了上面提到的在 JDBC 中配置 ents=true 之外,还可以使用动态 SQL 语法半自动生成批次。 例如:

<insert id="insertTestBatch" parameterType="java.util.List" fetchSize="1">
insert into test
(id, v1, v2)
values
<foreach item="item" index="index" collection="list" separator=",">
(
#{item.id}, #{item.v1}, #{item.v2}
)
foreach
>
on duplicate key update v2 = v1 + values(v1)
insert>

将生成 on key 语句。 后续的 (?, ?, ?) 数量是根据传入的列表数量来确定的。最终的效果与使用 ts=true 类似,可以有效减少客户端与 TiDB 之间的网络交互次数。 还需要注意的是,超过t限制会导致不缓存语句的问题。

1.3 结果

上一篇文章介绍了如何在JDBC中使用流式读取结果。 除了JDBC的相应配置之外,如果要在JDBC中读取非常大的结果集,还需要注意:

对于使用 xml 的配置映射,可以通过在映射部分配置 ==“-”(.) 来流式传输结果。

<select id="getAll" resultMap="postResultMap" fetchSize="-2147483648">
select * from post;
select
>

使用代码配置映射时,可以使用 @( = .) 并返回它,以便可以流式传输 SQL 结果。

@Select("select * from post")
@Options(fetchSize = Integer.MIN_VALUE)
Cursor queryAllPost();

2.

您可以在现场选择,支持三种类型:

通常默认值是,并且需要在调用时更改。 如果是Batch执行,就会遇到事务的第一部分或第二部分速度很快,但读取数据或事务却较慢的情况。 这其实是正常现象,排查慢SQL时需要注意。

在应用程序代码中,业务可以使用AOP方面来启动和停止事务。

通过在方法定义中添加@注解来标记方法,AOP将在方法之前启动事务,并在方法返回结果之前启动事务。 如果遇到类似业务,可以找到代码@来确定交易的开仓和平仓时机。 需要特别注意嵌入的情况。 如果发生嵌入,根据配置将使用不同的行为。 由于 TiDB 不支持,因此不支持嵌套事务。

故障排除工具

当Java应用程序出现问题且业务逻辑未知时,使用JVM强大的故障排除工具会更有用。 下面简单介绍一下几个常用的工具:

1.

对应Go中的pprof/,可以更方便的排查进程卡顿问题。

通过执行pid,可以输出目标进程中所有线程的线程ID和堆栈信息。 默认情况下,仅输出 Java 堆栈。 如果想同时输出JVM中的C++堆栈,则需要添加-m选项。

通过多次,可以很容易发现卡住问题(例如:都是通过flush调用)或者死锁问题(例如:测试程序正在抢占应用程序中的某个锁,没有发送SQL)

另外,top -p $PID -H 或Java瑞士刀都是查看线程ID的常用方法。 使用“%x\n”pid将线程ID转换为16进制,然后在输出结果中找到对应线程的堆栈信息。 可以定位“某个线程占用CPU比较高,不知道在执行什么”的问题。

2.jmap&mat

与Go中的pprof/heap不同,jmap会转储整个进程的内存快照(go是分配器的采样),然后可以通过另一个工具mat进行分析。

通过mat可以看到进程中所有对象的关联信息和属性,还可以观察线程的运行状态。 例如:我们可以使用mat来查出当前应用程序中有多少个MySQL连接对象,以及每个连接对象的地址和状态信息是什么。

需要注意的是,mat默认只会处理它。 如果想排查young gc问题,可以在mat配置中设置并查看。 另外,用Java来考察young gc问题(或者大量生命周期短的对象)的内存分配也比较方便。

3.跟踪

在线应用程序通常无法修改代码,并且希望在Java中进行动态检测来定位问题。 建议使用或追踪。 他们可以动态插入跟踪代码,而无需重新启动进程。

4. 火焰图

在Java应用程序中获取火焰图是很麻烦的。 可以参考Java Flame:Fire For! 手动获取它。

总结

本文从常用的 Java 数据库交互组件的角度,阐述了使用 TiDB 开发 Java 应用的常见问题及解决方案。 TiDB 是一个与 MySQL 协议高度兼容的数据库。 大多数基于 MySQL 开发的 Java 应用的最佳实践也适用于 TiDB。

如果您在使用中遇到任何问题,都可以提问。 也欢迎更多的朋友与我们分享和讨论在 Java 应用中使用 TiDB 的实用技巧。

标签: tidb 连接池 jdbc

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码