英文:
How to properly manage connection pooling with the managed driver of Oracle Data Provider for .NET
问题
我们开发了一个.NET应用程序,使用的是Oracle 12c(12.1.0.4)数据库。这是一个旧应用程序的重写版本,因此数据库本身并不新,也没有在重写过程中发生太多变化。新的应用程序是一个.NET 6应用程序,具有前端(Blazor WASM,这里不太重要)和Web API后端。后端API公开了一些通常访问数据库几次以返回和/或写入一些数据的方法。非常标准的操作。
我们使用Oracle Data Provider for .NET(ODP.NET)的托管驱动程序来访问数据库,来自Oracle的“Oracle.ManagedDataAccess.Core”Nuget包,版本3.21.61。一个API方法将连接到数据库,仅执行单个查询,或在某些情况下执行几个查询,因此连接的生命周期很短。这些查询不处理大量数据,通常只有几行,没有太多数据量。
我们刚刚在上周部署了新的应用程序,替换了旧的应用程序,但在几分钟内就遇到了严重的连接池问题,迫使我们关闭了应用程序并进行了调查。打开连接失败,出现错误消息“Délai d'expiration de la demande de connexion mise en pool"(很抱歉是法语,它大致翻译为“在池中等待连接的超时”,或类似的内容)。
我们检查了我们的代码,乍一看注意到OracleCommand和OracleDataReader对象没有被处理,所以我们修复了这个问题。OracleConnection被正确地处理了。但仍然似乎没有解决问题(尽管我们在测试环境中检查过,只有我们开发人员访问应用程序)。最终,我们在处理连接之前添加了ClearPool调用,这让我感到不太对劲,因为在我看来,我们基本上是在关闭连接池。这确实有很大帮助,但一天后仍然出现了连接失败的情况,这促使我们将池的大小从默认的100增加到200,然后再到250。到目前为止,似乎250是可以维持的,但这令人担忧。
基本上,根据我的估计,我们可能在某一时刻只有十几个用户左右,而且他们可能不会在完全相同的时间访问数据库。所以并不多。但是,尽管我们积极地清除了池,我们仍然需要数百个连接,这令人费解。
这是我们应用程序的一个示例代码,用于连接数据库(这个示例是用于更新查询,因此没有数据读取):
using (OracleConnection conn = new OracleConnection(_connectionString))
{
conn.Open();
OracleCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = query;
result.RecordCount = cmd.ExecuteNonQuery();
cmd.Dispose();
OracleConnection.ClearPool(conn);
}
这基本上类似于Oracle文档中的示例。我们打开一个连接,创建一个命令,使用它,然后释放命令和连接。
看起来连接池似乎没有正常工作,或者至少不是我认为应该的方式。但另一方面,我对池的工作原理的理解可能是错误的。我与我们的DBA一起进行了一些测试,他监视着开发数据库,我们可以看到连接确实被重复使用,尽管不如我预期的频繁。另一方面,由于我们清除了池,它根本不应该能够重复使用连接。
可能出了什么问题?为什么连接池没有正常工作,特别是考虑到低使用率,以及为什么我们需要使用ClearPool,这似乎只应该在不寻常的情况下使用?
英文:
We have developped a .NET application with an Oracle 12c (12.1.0.4) database. It's a rewrite of an old app, so the database itself isn't new and haven't changed much for the rewrite. The new app is a .NET 6 application, with a frontend (Blazor WASM, not that it matters here) and a web API backend. The backend API expose a number of methods that typically access the database a few times to return and/or write some data. Pretty standard stuff.
We use the managed driver of the Oracle Data Provider for .NET (ODP.NET) to access the database, from the "Oracle.ManagedDataAccess.Core" Nuget package from Oracle, version 3.21.61. An API method will connect to the database only to execute a single query, or in some cases a few queries, so connections are short lived. Those queries aren't handling a lot of data, typically a handful of rows, nothing big.
We just deployed the new app last week, replacing the old one, and in a matter of minutes we ran into severe connection pooling problems, forcing us to shut down the app and investigate. Opening connections were failing, with the error message "Délai d'expiration de la demande de connexion mise en pool" (sorry for the french, it roughly translate to "Timeout while pooling the opening of the connection", or something like that).
We checked our code, and at first glance noticed that the OracleCommand and OracleDataReader objects weren't disposed, so we fixed that. The OracleConnection were disposed correctly, though. Still, it didn't seemed to solve the issue (although we checked that in a testing environnement, with only us devs accessing the app). We ended up adding calls to ClearPool just before disposing of the connections, which feels wrong to me, as it looks to me like we're basically turning off connection pooling. This helps a lot, though, but we still got connections failure after a day, which led us to increase the size of the pool, from the default number of 100, to 200 and then 250. So far, it seems to hold at 250, but this is worrying.
Basically, according my estimates, we might have, at a given moment, only a dozens users or so, and they're likely not doing things that access the database at the exact same time. So, not a lot. Still, we somehow needed hundreds, and that despite agressively clearing the pool.
Here is a code sample from our app that make a connection to the database (this one is for an update query, so no data reading) :
using (OracleConnection conn = new(_connectionString))
{
conn.Open();
OracleCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = requete;
retour.NbEnregistrement = cmd.ExecuteNonQuery();
cmd.Dispose();
OracleConnection.ClearPool(conn);
}
This is essentially like the examples in Oracle's documentation. We open a connection, create a command, use it, then dispose of the command and the connection.
It looks like connection pooling isn't working properly, or at least not the way I think it should. Then again, my understanding of how pooling works might be wrong. I did some tests with our DBA who was monitoring the development database, and we can see that connections do get reused, although not as often as I would expect. On the other hand, since we clear the pools, it shouldn't even be able to reuse connections at all to begin with.
What could be wrong? How come connection pooling is not doing its job, expecially given the low usage, and why was it necessary for us to use ClearPool, which seems to be something that should only be used in unusual cases?
答案1
得分: 1
发现有一个情况,其中一个 OracleConnection 没有被正确释放。因此,在该上下文中使用的连接没有在连接池中释放,因此不管设置得多高,连接池最终都会达到限制。由于这发生在应用程序的最常用部分,所以往往会很快发生。
我修复了这个问题,现在一切都正常。
英文:
It turned out that there was one case where a OracleConnection wasn't disposed properly. So, the connections used in that context weren't released in the connection pool, and thus sooner or later the pool would reach the limit, no matter how high it was set. Since this was in the most used part of the app, it tended to happen quickly.
I fixed that, and now everything works fine.
答案2
得分: 0
I had the same problem and solved it by closing the connection after my database request.
if (conn.State == ConnectionState.Closed)
{
conn.Open();
}
if (conn.State == ConnectionState.Open)
{
var query = xxxxxx;
conn.Close();
}
I don't really know if it's better to conn.close() or to ClearPool(conn)..
英文:
I had the same problem and solved it by closing the connection after my database request.
if (conn.State == ConnectionState.Closed)
{
conn.Open();
}
if (conn.State == ConnectionState.Open)
{
var query = xxxxxx;
conn.Close();
}
i don't really know if it's better to conn.close() or to ClearPool(conn)..
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论