可配置的重试逻辑(CRL)是基于规则的机制,它根据你选择的SQL Server错误号自动重试失败的语句或初始连接尝试,以及你控制的计时参数。 Microsoft JDBC Driver 12.10 中引入了 CRL,适用于SQL Server。
CRL 与 空闲连接弹性以及 connectRetryCount / connectRetryInterval 属性相互独立。 空闲连接恢复功能可透明地恢复中断的连接,而 connectRetryCount 会针对内置的临时错误列表,按固定时间表重试初始身份验证。 CRL 允许你确定 哪些 错误可重试、 次数以及尝试之间等待 多长时间 。 可以将这三种机制一起使用。
CRL 重试的情况
CRL 处理两个不同的方案,每个方案由其自己的连接属性控制:
| Scenario | 资产 | 重试何时执行 | 触发者 |
|---|---|---|---|
| 语句执行失败 | retryExec |
执行某条语句时(例如,executeQuery、executeUpdate、execute或批量执行) |
错误编号与配置的语句规则匹配的 SQLServerException |
| 初始连接或身份验证失败 | retryConn |
在驱动程序的连接重试循环内(受 connectRetryCount 和 loginTimeout 控制) |
身份验证过程中,错误编号与配置的连接规则匹配的 SQLServerException,或者默认情况下,任何已包含在内置重试列表中的临时错误 |
对于语句,驱动程序仅重试失败的命令。 驱动程序不会重置当前事务状态,因此请围绕那些不影响会话可用性的错误设计规则,例如死锁受害者 (1205) 或锁超时 (1222)。
在连接方面,CRL 会扩展或替换驱动程序内置的瞬时连接错误列表。 有关 + 前缀语义,请参阅连接重试规则。
启用 CRL
CRL 有两个层:
- 连接重试层默认处于打开状态:只要
connectRetryCount > 0(默认值为 1),驱动程序将重试 内置的暂时性连接错误列表。 - 自定义层(你自己的
retryExec和retryConn规则)默认处于关闭状态。 这两个属性都是空字符串,除非设置它们。 您可以通过 JDBC URL、一个Properties对象或一个SQLServerDataSource来设置它们。 驱动程序会在这三种形式中均剥离可选的{...}包装。
本文中的Java代码片段省略导入和类包装器,以简洁起见。
在 JDBC URL 中
每个规则(或整个规则列表)都必须用大括号({...})包装,因为 JDBC URL 用作 ; 分隔符:
jdbc:sqlserver://server;databaseName=db;retryExec={1205,1222:3,2*2:select,update}
jdbc:sqlserver://server;databaseName=db;retryConn={+<customErrorNumber>}
使用 Properties 对象
Properties props = new Properties();
props.setProperty("user", "...");
props.setProperty("password", "...");
props.setProperty("retryExec", "1205,1222:3,2*2:select,update");
props.setProperty("retryConn", "+<customErrorNumber>");
Connection c = DriverManager.getConnection("jdbc:sqlserver://server;databaseName=db", props);
有 SQLServerDataSource
ISQLServerDataSource 接口上也有相同的 setter 方法:
SQLServerDataSource ds = new SQLServerDataSource();
ds.setServerName("server");
ds.setDatabaseName("db");
ds.setRetryExec("1205,1222:3,2*2:select,update");
ds.setRetryConn("+<customErrorNumber>");
规则语法
单个规则最多包含三个冒号分隔部分:
<errorNumbers> : <retryTimings> : <queryFilter>
| 章节 | Required? | Meaning |
|---|---|---|
errorNumbers |
是的 | 一个SQL Server错误号,或用逗号分隔的几个(例如,1205或1205,1222)。 对于连接规则,可选前导 + 控制是否保留现有的暂时性错误。 |
retryTimings |
语句规则必备。 对于连接规则,请省略。 |
retryCount[,initialRetryTime[<op>retryChange]]其中,<op>为+(加性)或*(乘性)。 |
queryFilter |
可选,仅限语句规则 | SQL 关键字的逗号分隔列表。 驱动程序在分析时小写该值,并小写以前在运行时执行的 SQL。 当联接的筛选器列表包含已执行的 SQL 的第一个令牌时,规则将触发。 省略第三部分以禁用筛选。 |
若要在同一属性中使用多个规则,请在将其放入 JDBC URL 时,使用 ; 分隔各条规则,并用 {...} 包裹每条规则。
计时参数
对于带有计时参数 retryCount, initialRetryTime <op> retryChange 的语句规则:
-
retryCount:驱动程序在第一次失败后进行的其他尝试次数。 值为0时,将禁用重试。 负值无效。 -
initialRetryTime:第一次重试前等待的秒数。 默认值为0。 -
<op>:运算符,可以是+或*。 默认值为+。 -
retryChange:应用于计算后续等待时间的量。 默认值为2。 当操作数为*且规则中省略了retryChange时,驱动程序会设置retryChange = initialRetryTime。
Important
如果未提供initialRetryTime显式操作数(例如,3,5),驱动程序将使用操作数和retryChange(和+)2的默认值。 等待时间并不是固定不变的。 每次重试,计时值增加 2。 若要获取常量等待,请使用显式形式 retryCount,N+0 (例如, 3,5+0)。
驱动程序在解析时计算第 i 次尝试(从 0 开始计数)的等待时间:
| 操作数 | 等待尝试 i |
|---|---|
+ (附加) |
initialRetryTime + (retryChange * i) |
* (乘法) |
initialRetryTime * (retryChange ^ i) |
计时字符串的示例:
| String | 重试次数 | initialRetryTime | 操作数 | retryChange | 等待顺序(秒) |
|---|---|---|---|---|---|
3 |
3 | 0(默认值) |
+(默认值) |
2 (默认值) | 0, 2, 4 |
3,5 |
3 | 5 |
+(默认值) |
2 (默认值) | 5, 7, 9 |
3,5+5 |
3 | 5 | + |
5 | 5, 10, 15 |
3,2*2 |
3 | 2 | * |
2 | 2, 4, 8 |
4,1* |
4 | 1 | * |
1(等于 initialRetryTime,因为操作数是 *,并且省略了 retryChange) |
1, 1, 1, 1 |
一个 retryTimings 节最多只能包含一个逗号。 多于一个逗号会引发 R_invalidParameterNumber。
语句重试规则(retryExec)
语句规则会重试失败语句的执行。 当语句引发 SQLServerException 时,驱动程序:
- 在已解析的语句规则集中查找导致失败的错误编号。
- 如果规则存在且当前尝试计数小于
retryCount,则可选择根据规则queryFilter检查上次执行的 SQL。 - 如果所有条件都匹配,驱动程序将等待
waitTimes[retryAttempt]秒(取决于queryTimeout,请参阅 与 queryTimeout 和 connectRetryCount 的交互),然后重新执行该语句。 - 如果没有匹配的规则,驱动程序会重新抛出该异常。
格式(语句)
{errorNumber(s):retryCount[,initialRetryTime[<op>retryChange]][:queryFilter]}
语句规则 必须 包含计时部分。
retryCount 是必需的。 仅包含错误号的规则会被解释为 连接 规则,因此对于语句规则,始终至少提供 retryCount。
示例(语句)
| 规则 | Effect |
|---|---|
{1205:3} |
对死锁受害者 (1205) 最多重试 3 次,重试之间不等待。 |
{1205,1222:3,5+5} |
对死锁受害者和锁超时最多重试 3 次,每次等待 5、10 和 15 秒。 |
{2714:2,1*2} |
重试“对象已存在”最多 2 次,等待 1 秒和 2 秒。 |
{1205:4,2+2:select,update} |
仅当失败的语句以 select 或 update 开头时才重试。 |
{1205:3,5+5};{1222:2,2} |
两个独立的规则,由 ; 分隔。 |
列出多个错误编号(例如 1205,1222)是一种简写。 驱动程序将该规则展开为每个错误对应一个条目,这些条目均共享相同的时间安排和查询过滤器。
连接重试规则 (retryConn)
连接规则适用于现有的连接重试循环。 此循环仅在 connectRetryCount > 0 时 处于活动状态(默认值为 1)。 该循环已按 connectRetryInterval 秒间隔重试内置的临时连接错误列表,最多进行 connectRetryCount 次额外尝试,且受 loginTimeout 限制。
连接规则仅包含错误编号部分。 它没有计时或查询筛选器:
{[+]errorNumber(s)}
- 如果没有
+,配置的规则 将替换 内置的暂时性错误列表。 仅会重试您列出的错误。 - 使用
+(例如{+4060})时,已配置的规则会添加到内置列表中。 将重试错误和驱动程序默认值。
替换或追加模式对整个 retryConn 值全局生效。 如果该值中的任何规则省略 +,驱动程序会切换到该值中的所有规则的替换模式。 例如, retryConn={+4060};{40143} 不向内置列表追加 4060 和 40143。 该 40143 规则省略 +,因此删除内置列表,并且只重试 4060 和 40143。 若要追加这两者,请编写 retryConn={+4060};{+40143} (或 retryConn={+4060,40143})。
连接循环继续使用 connectRetryInterval 和 connectRetryCount 进行速率控制和限制。 CRL 规则扩展或替换符合重试条件的错误集。
示例(连接)
| 规则 | Effect |
|---|---|
{+<customErrorNumber>} |
将自定义错误号添加到内置暂时性错误列表。 |
{+<customError1>,<customError2>} |
将多个自定义错误号添加到内置暂时性错误列表。 |
{4060} |
仅重试错误 4060。 CRL 不再重试内置的瞬态错误。 |
Note
retryConn 不会更改 loginTimeout 语义。 现有的连接重试循环仍然对总耗时设定了上限,并且如果下一次 connectRetryInterval 会使总耗时超过 loginTimeout,就会提前停止重试。
内置瞬态连接错误列表
只要满足 connectRetryCount > 0 条件,连接重试循环即使没有 CRL 配置,也会自动重试以下错误。 在包含 + 的 retryConn 规则中列出这些错误中的任何一个都是无效操作(因为它们已被涵盖)。 当需要添加未包含在该列表中的错误,或需要通过使用 no-+ 替换形式完全移除该列表时,请使用 retryConn 规则。
Note
无需追加常见的Azure SQL暂时性连接错误,例如 40197、40501、40613、49918、49919 或 49920。 内置列表已对这些错误进行重试。
| Error | Message | 故障排除 |
|---|---|---|
| 64 | 已成功与服务器建立连接,但在登录过程中发生错误。 (提供程序:TCP 提供程序,错误:0 - 指定的网络名称不再可用。 | TCP 连接在握手过程中中断了。 并非凭据错误。 如果问题仍然存在,请检查客户端网络不稳定、网卡卸载错误,或中间设备是否丢弃了半建立的连接。 |
| 233 | 由于登录前连接初始化过程中出错,客户端无法建立连接。 | 预登录传输或 TLS 失败。 当服务器无法接受该连接时(例如资源耗尽、已达到最大连接数,或客户端不受支持),通常会返回此响应。 这不是凭据失败。 验证服务器运行状况,然后检查 loginTimeout、TLS 设置和客户端/服务器 TLS 版本兼容性。 |
| 4060 | 无法打开登录名请求的数据库 database_name 。 登录失败。 | 登录名已通过身份验证,但无法打开请求的数据库。 暂时性原因包括:数据库正处于转换过程中(故障转移、还原、纵向扩展)或已自动暂停。 永久性原因(数据库不存在,登录缺少访问权限)不会通过重试来修复;检查数据库名称、登录映射和数据库状态。 |
| 4221 | 由于在 HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING 上等待时间过长,登录可读辅助节点失败。 |
当副本被回收时,正在进行的事务中仍缺少行版本,因此可读辅助节点无法接受登录。 缓解方法是避免在主节点上进行长时间的写入事务;一旦主节点提交或回滚未完成的事务,重试通常会成功。 |
| 10053 | 向服务器发送请求时发生传输级别错误。 (提供程序:TCP 提供程序,错误:0 - 已建立的连接已由主机中的软件中止。 |
本地端中止了该连接(Windows Sockets WSAECONNABORTED)。 通常是保持活动连接失败,或者本地网络堆栈拆除了空闲或半打开的连接。 检查客户端侧网络状况、操作系统保活定时器以及任何本地防火墙或 VPN 客户端。 |
| 10054 | 向服务器发送请求时发生传输级别错误。 (提供程序:TCP 提供程序,错误:0 - 远程主机强行关闭现有连接。 |
远程端发送了 TCP 重置 (Windows Sockets WSAECONNRESET)。 常见原因:对端进程崩溃、防火墙注入了重置信号,或 Azure SQL 网关关闭了空闲连接。 对于空闲重置模式,请在客户端启用 TCP 保持活动功能,或缩短连接池的空闲超时时间。 |
| 10928 | 资源 ID: N。数据库的 限制类型 限制为 N 且已达到。 有关用法,请参阅 sys.dm_exec_sessions。 |
已达到数据库资源治理限制(会话、工作线程或请求)。 从消息中识别限制类型,然后降低并发、扩容数据库,或缩短长时间占用该资源的操作的执行时间。 |
| 10929 | 资源 ID: N。 限制类型 最小保证为 N,最大限制为 N ,数据库当前使用情况为 N。但是,服务器当前太忙,无法支持此数据库的请求大于 N 。 | 数据库已超过其最低保障值,底层服务器正在对其进行限流。 当邻居负载下降时,重试通常成功。 持续出现表示需要更高的服务层级或不太干扰的环境。 |
| 40020 40143 40166 40540 |
在故障转移期间,此错误报告于错误代码 40197 的 Error code %d 槽中。 |
40197 故障转移消息中嵌入了子代码,某些路径会将其作为顶级错误代码显示。 驱动程序将每一种情况分别列出,以便针对这两种形式中的任一种进行重试。 将它们视为 40197。 |
| 40197 | 该服务在处理你的请求时遇到错误。 请重试。 错误代码 N。 | Azure SQL 中的一次软件升级、硬件故障或其他故障转移事件。 重新连接会将您路由到状态正常的副本。 嵌入的错误代码标识故障转移类型。 应使用会话跟踪 ID 报告持久性事件。 |
| 40501 | 当前服务繁忙。 请在 10 秒钟后重试请求。 事件编号:guid。 代码: N。 | Azure SQL 引擎限流。 建议的最低退避时间为 10 秒。 持续的限流表明您已超过 DTU/vCore 配额;请扩展资源或降低并发量。 |
| 40613 | 服务器server_name上的数据库database_name当前不可用。 请稍后重试连接。 如果问题仍然存在,请联系客户支持部门,并向他们提供 guid 的会话跟踪 ID。 | 数据库不可用,通常发生在故障转移过程中或扩展操作期间的短暂时刻。 按退避策略重试;如果问题持续数分钟以上,请捕获会话跟踪 ID 并提交支持工单。 |
| 42108 | 无法连接到 SQL 池,因为它已暂停。 请恢复 SQL 池,然后重试。 | 专用 SQL 池(Synapse)处于暂停状态。 只有当有其他操作并行恢复连接池时,重试才有效。 显式恢复连接池,或在恢复后重新调度工作负载。 |
| 42109 | SQL 池正在预热。 请重试。 | 专用 SQL 池正在恢复。 采用退避策略重试,直到其恢复在线;预热通常需要几分钟。 |
| 49918 | 无法处理请求。 没有足够的资源来处理请求。 | 控制平面目前无法为请求分配资源。 采用退避策略重试。 持续出现表示区域容量压力。 |
| 49919 | 无法处理创建或更新请求。 订阅 N 正在进行的创建或更新操作过多。 | 管理操作的订阅级并发限制。 减少并行创建/更新调用或将它们错开。 |
| 49920 | 无法处理请求。 订阅 N 正在进行的操作过多。 | 正在处理的操作的订阅级并发限制。 降低并行度,或等待进行中的操作处理完毕。 |
驱动程序的标准列表是 TransientError 中的 SQLServerError.java 枚举。 错误消息文本来自Azure SQL暂时性连接错误。 语句级别的错误(例如死锁受害者错误 1205 或锁请求超时错误 1222)不在此列表中,因为连接重试循环只会在初始连接时触发。 若要重试这些错误,请使用规则 retryExec 。
从属性文件加载规则
如果未在连接上设置 retryExec 或 retryConn,CRL 会在类路径中驱动程序 JAR 所在位置旁查找一个名为 mssql-jdbc.properties 的文件。 该文件使用基本 key=value 分析。 以retryExec=或retryConn=开头的行会被识别。 值使用本文中所述的相同语法,并 ; 分离多个规则。
使用确切的键名称(retryExec 和 retryConn),没有前导空格。 该文件未分析为完整的Java属性文件。 驱动程序对每一行都进行字面意义上的 startsWith 检查,因此:
- 将忽略以
#任何其他非retryExec/retryConn前缀开头的行。 - 键仅以
retryExec或retryConn开头的行(例如retryExec2=...)会被视为对应的属性,并可能导致解析错误。 不要引入自定义变体。
示例 mssql-jdbc.properties:
retryExec=1205:3,5+5;1222:2,2
retryConn=+4060,40143
如果文件缺失,CRL 会在 com.microsoft.sqlserver.jdbc.ConfigurableRetryLogic 记录器下记录一条 FINE 消息,并在不使用任何规则的情况下继续运行。 用于查找的文件路径包含在该日志消息中。
连接字符串值优先。 如果连接中的 retryExec 或 retryConn 为非空,驱动程序就不会到文件中查找该属性。
规则刷新行为
CRL 维护一个 JVM 全局规则集。 构建完成后,驱动程序会懒惰地刷新规则:
- 驱动程序在语句执行和连接重试期间评估刷新机会。
- 刷新实际上仅在自上一次读取以来经过 30 秒后才会进行。
- 如果规则最初来自
mssql-jdbc.properties,驱动程序会将文件的上次修改时间戳与在上一次读取上记录的时间戳进行比较。 如果文件发生更改,驱动程序会重新解析它。 - 如果规则最初来自连接字符串,驱动程序将重新应用以前存储的连接字符串值。
这种行为意味着,对 mssql-jdbc.properties 的编辑会在大约 30 秒内被自动检测到,而无需重启应用程序。
Important
由于该规则集是 JVM 范围内的单例,因此,如果打开第二个连接并为其设置不同的 retryExec 或 retryConn 值,第一个连接的规则也会被替换掉。 如果同一 JVM 中的多个连接存在分歧,请将 CRL 配置视为进程级设置,而不是每个连接设置。
与 queryTimeout 和 connectRetryCount 的相互作用
语句重试和 queryTimeout
触发语句规则时,驱动程序将下次等待时间与连接级别 queryTimeout 值进行比较:
- 如果
queryTimeout >= 0andtimeToWait > queryTimeout,驱动程序会引发R_InvalidRetryInterval,而不是重试。 驱动程序不会重新引发原始错误。 它引发配置错误。 -
queryTimeout连接属性默认为-1,因此默认情况下将跳过比较,并允许任何等待。 - 设置
queryTimeout=0并不会禁用此检查,因为0 >= 0为真。 任何timeToWait > 0都会引发R_InvalidRetryInterval。
当您将 queryTimeout 设为正值时,请确保 initialRetryTime + (retryCount - 1) * retryChange(加法)或 initialRetryTime * retryChange^(retryCount-1)(乘法)的值低于该值。
连接重试与 connectRetryCount 和 loginTimeout
retryConn 本身不会启用身份验证重试。 现有属性仍起主导作用:
-
connectRetryCount(默认值 1,范围 0-255)是额外的身份验证尝试次数。 将其设置为0禁用身份验证重试。 当connectRetryCount = 0时,retryConn无效,因为驱动程序会在首次失败时抛出异常。 -
connectRetryInterval(默认为 10 秒,范围为 1-60)是尝试之间的等待。 首次重试会立即进行。 -
loginTimeout是总体边界。 如果下一个间隔会使经过时间超过loginTimeout,驱动程序会提前放弃。
有关详细信息,请参阅连接复原能力(JDBC)。
示例
处理写入操作中的死锁和锁超时
jdbc:sqlserver://server;databaseName=db;retryExec={1205,1222:4,2*2:insert,update,delete,merge}
对于死锁受害者 (1205) 或锁超时 (1222),最多重试四次,退避时间分别为 2、4、8 和 16 秒,但仅限于写入语句。
在联机操作下重新运行架构创建
retryExec={2714:2,1+1};{3702:2,1+1}
重试错误 2714 (object already exists) 和 3702 (cannot drop database currently in use) 两次,等待 1 秒和 2 秒。
将自定义错误添加到暂时性错误列表
retryConn={+<customErrorNumber>}
添加内置列表中尚不存在的自定义错误号。 如果追加内置Azure SQL暂时性错误,例如 40197、40501、40613、49918、49919 或 49920,则不会发生任何更改,因为驱动程序已重试。
通过属性文件配置 CRL
将 mssql-jdbc.properties 放在驱动程序 JAR 旁边:
retryExec=1205:3,5+5:select,update
retryConn=+<customErrorNumber>
请勿在该连接上设置 retryExec 或 retryConn。 驱动程序从文件中读取规则,并在每次修改后重新读取(每 30 秒检查一次)。
CRL 疑难解答
在 com.microsoft.sqlserver.jdbc.ConfigurableRetryLogic 日志记录器上启用 FINE(或更精细)日志记录,以查看文件读取尝试和解析决策:
com.microsoft.sqlserver.jdbc.ConfigurableRetryLogic.level=FINE
常见配置错误:
| 错误消息密钥 | 原因 |
|---|---|
R_invalidParameterNumber |
在驱动程序预期为错误编号或计时参数的位置出现了非数字标记,或者 retryTimings 中包含多个逗号。 |
R_InvalidRuleFormat |
该规则包含超过 3 个以冒号分隔的部分。 |
R_InvalidRetryInterval |
某条语句规则的计算得出的等待时间超过 queryTimeout。 缩短等待时间或提高 queryTimeout。 |
R_PathInvalid 或 R_URLInvalid |
驱动程序无法解析要查找 mssql-jdbc.properties的路径。 |
R_errorReadingStream |
读取 mssql-jdbc.properties 时发生 I/O 错误。 |
Note
R_invalidParameterNumber 消息的文本为 参数编号 {0} 无效,这与驱动程序用于预处理语句参数绑定错误的资源字符串相同。 当 CRL 抛出该错误时,出错的值是你的重试规则标记(例如错误编号或非数值的时间元素),而不是 PreparedStatement 参数索引。
在规则未触发时要检查的事项:
- 异常
SQLServerError.getErrorNumber()实际上与规则中的数字匹配。 SQL Server可以根据上下文(例如死锁与锁定超时)将某些故障包装到不同的数字中。 - 对于包含
queryFilter的语句规则,您执行的 SQL 语句中第一个以空格分隔的标记(小写)会出现在过滤列表中。 注释和WITHCTE 会更改第一个标记。 -
retryCount重试是 额外的 尝试。 第一次执行不算。 - 对于连接规则而言,
connectRetryCount大于 0,且loginTimeout至少还能容纳一个connectRetryInterval。 - 该规则的形式是正确的。 语句规则需要包含“计时”部分。 连接规则则无需包含。