天天看点

关于数据库的SQL超时实现策略

在执行SQL的时候,有可能需要限制SQL的最长执行时间。这个限制在JDBC和.NET Data Provider中分别通过下面两个方法设置。

其他数据库驱动可能也有类似的参数。

Statement.setQueryTimeout()

DbCommand.CommandTimeout()

这个功能是很简单的,但是如何实现这个超时呢?不同的数据库驱动,可能会采取不同的方法。我接触过几个驱动,大致有下面几种方法。

1)在客户端进行超时控制

客户端驱动通过设置Socket数据接受超时或者在另一个线程里开一个定时器进行超时判断。当检出SQL超时时取消正在执行的SQL。

使用这种方法的前提是服务端支持SQL取消功能。取消SQL的请求是不能直接用原来的连接往服务端发的,因为这个连接已经被这个正在执行的SQL占据了,所以驱动内部必须新开一个Socket。取消SQL有一种极端的情况,由于发出取消请求的时刻和取消请求真正被执行的时刻之间有个时间差。在这个时间差里,有可能你想取消的SQL已经执行完毕,并且对应的物理连接回池,然后有新的连接拿到这个物理连接之后开始执行新的SQL。于是诡异的事情发生了,一个正常的SQL被莫名其妙的取消了。我们就曾遇到过这么变态的bug。

PostgreSQL的JDBC驱动pgjdbc就是这么干的。印象中Oracle和MySQL好像也是这么干的。

2)在服务端进行超时控制

驱动把超时参数发给服务端,由服务端处理超时,并把超时错误反馈给客户端。

这种方法通常工作得很好,但无法应付服务端或者网络故障。当发生故障时,客户端可能会在那儿一直傻等。

印象中SQL Server好像是这么干的。

3)客户端和服务端同时进行超时控制

除此1和2外,还有一种组合的方案,这就是PostgreSQL的.NET驱动Npgsql的实现。这也是本人见到过的最好的方案。

Npgsql通过向服务端发送statement_timeout参数让服务端进行超时控制,同时为了防止出现故障又在驱动里设置了一个计时器。但这个计时器的超时时间比用户设置的多5秒,所以驱动中的计时器可以在服务端超时机制失效的5秒之后挺身而出。