当一个长时间运行的应用程序在整个应用运行期间保持数据库连接打开时,连接很可能经常处于空闲状态,不被用来执行SQL语句。然而,这会占用服务器上的进程和会话内存资源,从而限制其他用户的连接。如果无法重新架构以使用Oracle驱动程序连接池,可以通过更改连接字符串来利用Oracle Database 23ai的“隐式连接池”功能共享数据库主机上的资源。这减少了内存需求,使数据库更具可扩展性。
我的上一篇博客文章 DRCP 帮助不使用连接池的应用 展示了一个频繁连接和断开连接但不使用连接池的例子。它解释了应用程序如何从数据库居民连接池(DRCP)中获益,因为这些频繁的断开操作让数据库知道何时可以安全地重用数据库服务器进程来为其他用户提供服务。
以下示例有所不同,因为断开连接的操作仅在长时间运行的应用终止时发生。纯DRCP无法帮忙,因为没有明确的连接使用“界限”来指示何时重用数据库服务器进程。幸运的是,Oracle Database 23ai的“隐式连接池功能”可以处理这种情况。
让我们来看一个演示程序。下面是一个简单的Python应用long_run.py
,它打开一个连接并进入循环。在每次迭代中执行一个查询,并在每次查询后有一个延时,模拟用户的思考时间。当应用程序结束(with
上下文块结束时),连接会被关闭。这模拟了一个长时间运行的应用程序,即使在不执行SQL语句时也会持续保持连接的状态。
# long_run.py
import os
import time
import oracledb
import sample_env # 包含凭证,参见 python-oracledb 示例
# 此脚本运行期间执行的查询数量
NUMSQLS = globals().get("NUMSQLS", 0)
# 执行 SQL 语句后的等待秒数
SLEEPTIME = globals().get("SLEEPTIME", 0)
# 每次执行此脚本时唯一的“用户”名称
APPUSERNAME = globals().get("APPUSERNAME", None)
sql = """select unique sid||'-'||serial# as sidser,
current_timestamp as ct
从 v$session_connect_info
其中 sid = sys_context('USERENV', 'SID')"""
with oracledb.connect(
user=sample_env.get_main_user(),
password=sample_env.get_main_password(),
dsn=sample_env.get_connect_string(),
) as connection:
for i in range(NUMSQLS):
with connection.cursor() as cursor:
for s, d in cursor.execute(sql):
print(f"{APPUSERNAME} 会话标识符 {s} 于 {d}")
time.sleep(SLEEPTIME)
应用程序查询获取当前时间和会话ID/序列号——这一对标识符表示当前使用的数据库服务器会话。每次查询将输出类似的一行结果。
用户User01 在2024年6月11日 20:29:34.632666时 sid-ser 407-62786
连接字符串很简单,比如 localhost/orclpdb1
。没有涉及DRCP。
一个辅助脚本 runner.py
用于模拟多个用户同时运行 long_run.py
,该脚本通过使用线程来调用它。辅助脚本通过线程调用 long_run.py
10 次(在 "NUMUSERS
" 中设置)。每次调用 long_run.py
时,都会传递一个唯一的“用户名”(比如 User01
,通过参数 APPUSERNAME
传递),执行的 SQL 语句数量("NUMSQLS
"),以及执行后的休眠时间("SLEEPTIME
")。休眠时间模拟应用程序用户处于空闲状态,不执行任何 SQL 语句。辅助脚本如下:
# runner.py
import threading
import time
import sample_env # 包含凭据,参见 python-oracledb 样例
NUMUSERS = 10 # 并发调用 long_run.py 的次数(共 NUMUSERS 次)
def start_app(tn):
app_globals = {
"APPUSERNAME": "User{:02d}".format(tn + 1), # 运行 long_run.py 的用户名称
"NUMSQLS": 5, # 在 long_run.py 的生命周期中执行的查询数量
"SLEEPTIME": 5, # 在执行完 SQL 语句后休眠的秒数
}
exec(open("long_run.py").read(), app_globals) # 执行 long_run.py 并传递变量字典
def start_workload():
thread = []
for i in range(NUMUSERS):
t = threading.Thread(target=start_app, args=(i,))
t.start()
thread.append(t) # 线程列表中添加 t
for i in range(NUMUSERS):
thread[i].join() # 等待线程完成
if __name__ == "__main__":
print(f"使用来自 sample_env 模块的连接字符串: {sample_env.get_connect_string()}")
start = time.time()
start_workload()
elapsed = time.time() - start
print("所有操作已完成!")
print("耗时 {:04.2f} 秒".format(elapsed))
根据代码,每个 long_run.py
进程将运行 5 个查询,并在每个查询后暂停(sleep)5 秒。(实际上,您的应用程序可能会长时间运行。)加上连接和执行语句所需的一些额外时间,预计的总端到端时间为略超过 25 秒。
$ python3 runner.py
使用连接字符串:localhost/orclpdb1
用户01 会话标识-序列号 407-62786,2024-06-11 20:29:34.632666
用户03 会话标识-序列号 26-48119,2024-06-11 20:29:34.802162
用户02 会话标识-序列号 172-37948,2024-06-11 20:29:34.973973
用户04 会话标识-序列号 272-26116,2024-06-11 20:29:35.146129
用户05 会话标识-序列号 398-8361,2024-06-11 20:29:35.315068
用户07 会话标识-序列号 33-58745,2024-06-11 20:29:35.485322
用户08 会话标识-序列号 154-64891,2024-06-11 20:29:35.667774
用户09 会话标识-序列号 268-40439,2024-06-11 20:29:35.837789
用户10 会话标识-序列号 408-62081,2024-06-11 20:29:36.013276
用户06 会话标识-序列号 10-45069,2024-06-11 20:29:36.189829
用户01 会话标识-序列号 407-62786,2024-06-11 20:29:39.653528
用户03 会话标识-序列号 26-48119,2024-06-11 20:29:39.821690
用户02 会话标识-序列号 172-37948,2024-06-11 20:29:39.990345
用户04 会话标识-序列号 272-26116,2024-06-11 20:29:40.162383
用户05 会话标识-序列号 398-8361,2024-06-11 20:29:40.330363
用户07 会话标识-序列号 33-58745,2024-06-11 20:29:40.503150
用户08 会话标识-序列号 154-64891,2024-06-11 20:29:40.686834
用户09 会话标识-序列号 268-40439,2024-06-11 20:29:40.854444
用户10 会话标识-序列号 408-62081,2024-06-11 20:29:41.029533
用户06 会话标识-序列号 10-45069,2024-06-11 20:29:41.206820
用户01 会话标识-序列号 407-62786,2024-06-11 20:29:44.666896
用户03 会话标识-序列号 26-48119,2024-06-11 20:29:44.835387
用户02 会话标识-序列号 172-37948,2024-06-11 20:29:45.005330
用户04 会话标识-序列号 272-26116,2024-06-11 20:29:45.177302
用户05 会话标识-序列号 398-8361,2024-06-11 20:29:45.343248
用户07 会话标识-序列号 33-58745,2024-06-11 20:29:45.520129
用户08 会话标识-序列号 154-64891,2024-06-11 20:29:45.704843
用户09 会话标识-序列号 268-40439,2024-06-11 20:29:45.868719
用户10 会话标识-序列号 408-62081,2024-06-11 20:29:46.046426
用户06 会话标识-序列号 10-45069,2024-06-11 20:29:46.225250
用户01 会话标识-序列号 407-62786,2024-06-11 20:29:49.679691
用户03 会话标识-序列号 26-48119,2024-06-11 20:29:49.848716
用户02 会话标识-序列号 172-37948,2024-06-11 20:29:50.021498
用户04 会话标识-序列号 272-26116,2024-06-11 20:29:50.191881
用户05 会话标识-序列号 398-8361,2024-06-11 20:29:50.359139
用户07 会话标识-序列号 33-58745,2024-06-11 20:29:50.542895
用户08 会话标识-序列号 154-64891,2024-06-11 20:29:50.715070
用户09 会话标识-序列号 268-40439,2024-06-11 20:29:50.880684
用户10 会话标识-序列号 408-62081,2024-06-11 20:29:51.062274
用户06 会话标识-序列号 10-45069,2024-06-11 20:29:51.245149
用户01 会话标识-序列号 407-62786,2024-06-11 20:29:54.694733
用户03 会话标识-序列号 26-48119,2024-06-11 20:29:54.866731
用户02 会话标识-序列号 172-37948,2024-06-11 20:29:55.038168
用户04 会话标识-序列号 272-26116,2024-06-11 20:29:55.206117
用户05 会话标识-序列号 398-8361,2024-06-11 20:29:55.372266
用户07 会话标识-序列号 33-58745,2024-06-11 20:29:55.560076
用户08 会话标识-序列号 154-64891,2024-06-11 20:29:55.730851
用户09 会话标识-序列号 268-40439,2024-06-11 20:29:55.899642
用户10 会话标识-序列号 408-62081,2024-06-11 20:29:56.078866
用户06 会话标识-序列号 10-45069,2024-06-11 20:29:56.260751
都完成了!
总耗时 26.85 秒
重要的一点是,每个进程会打开一个连接并在long_run.py
应用程序运行期间保持该连接打开。这可以通过每个用户脚本始终使用相同的会话ID/序列号来执行查询体现出来。例如,User01
始终使用407-62786
。这表明始终有10个连接保持打开,每个连接都有一个对应的服务器进程,并且占用数据库主机资源的会话内存空间。
隐式连接池是 Oracle Database 23AI 的一个特性,它利用数据库驻留连接池来共享数据库服务器进程和会话内存。该特性得到了包括 python-oracledb、node-oracledb 和 JDBC 在内的流行 Oracle 数据库驱动程序的支持。
隐式连接池适用于在不进行数据库操作时仍保持连接打开的应用程序。这包括自己实现并维持连接池,让底层数据库连接在整个连接池存在期间保持打开的应用程序。
隐式连接池通过透明地识别应用程序何时停止使用打开的连接来工作。然后,它允许另一个应用程序连接使用第一个(当前空闲)连接的服务器进程和会话内存。当第一个应用程序随后发起新的数据库请求时,一个空闲的数据库服务器进程将重新分配给它,应用程序不会察觉到这个临时的“借用”。应用程序无需做任何更改。数据库资源被共享,从而提高了系统的可扩展性。
隐式连接池与纯DRCP的主要区别在于,Oracle会自动处理数据库服务器进程的映射和解映射。而在纯DRCP中,映射和解映射操作只在应用程序发起获取连接和释放连接请求时才会发生。
由于 long_run.py
只有一个打开和关闭的配对(关闭在 with
代码块的末尾内部完成),它是一个使用隐式连接池的理想选择。只需在数据库中启用 DRCP,将连接字符串更改为 DRCP 连接池服务器,并在其中添加 POOL_BOUNDARY
参数即可。应用程序代码无需改动。重新运行演示,您将看到如下输出:
python3 runner.py
使用连接字符串:localhost/orclpdb1:pooled?pool_boundary=statement
User01 会话标识 399-25678 在时间 2024-06-11 20:28:46.803668
User02 会话标识 399-25678 在时间 2024-06-11 20:28:46.830118
User05 会话标识 399-25678 在时间 2024-06-11 20:28:46.840759
User04 会话标识 399-25678 在时间 2024-06-11 20:28:46.867337
User03 会话标识 152-55977 在时间 2024-06-11 20:28:46.908656
User08 会话标识 152-55977 在时间 2024-06-11 20:28:46.978751
User09 会话标识 152-55977 在时间 2024-06-11 20:28:47.045773
User06 会话标识 152-55977 在时间 2024-06-11 20:28:47.106757
User10 会话标识 152-55977 在时间 2024-06-11 20:28:47.169244
User07 会话标识 152-55977 在时间 2024-06-11 20:28:47.229357
User01 会话标识 152-55977 在时间 2024-06-11 20:28:51.844053
User02 会话标识 399-25678 在时间 2024-06-11 20:28:51.844449
User05 会话标识 152-55977 在时间 2024-06-11 20:28:51.855879
User04 会话标识 152-55977 在时间 2024-06-11 20:28:51.883601
User03 会话标识 152-55977 在时间 2024-06-11 20:28:51.940661
User08 会话标识 152-55977 在时间 2024-06-11 20:28:51.990124
User09 会话标识 152-55977 在时间 2024-06-11 20:28:52.058879
User06 会话标识 152-55977 在时间 2024-06-11 20:28:52.120180
User10 会话标识 152-55977 在时间 2024-06-11 20:28:52.182709
User07 会话标识 152-55977 在时间 2024-06-11 20:28:52.243295
User01 会话标识 152-55977 在时间 2024-06-11 20:28:56.874489
User02 会话标识 399-25678 在时间 2024-06-11 20:28:56.874487
User04 会话标识 399-25678 在时间 2024-06-11 20:28:56.895112
User03 会话标识 399-25678 在时间 2024-06-11 20:28:56.954245
User05 会话标识 277-27334 在时间 2024-06-11 20:28:56.969050
User08 会话标识 277-27334 在时间 2024-06-11 20:28:56.997923
User09 会话标识 277-27334 在时间 2024-06-11 20:28:57.072845
User06 会话标识 277-27334 在时间 2024-06-11 20:28:57.131504
User10 会话标识 277-27334 在时间 2024-06-11 20:28:57.195656
User07 会话标识 277-27334 在时间 2024-06-11 20:28:57.257700
User01 会话标识 399-25678 在时间 2024-06-11 20:29:01.903889
User02 会话标识 277-27334 在时间 2024-06-11 20:29:01.903889
User04 会话标识 152-55977 在时间 2024-06-11 20:29:01.910282
User03 会话标识 152-55977 在时间 2024-06-11 20:29:01.968523
User05 会话标识 152-55977 在时间 2024-06-11 20:29:01.997715
User08 会话标识 152-55977 在时间 2024-06-11 20:29:02.011130
User09 会话标识 152-55977 在时间 2024-06-11 20:29:02.086866
User06 会话标识 152-55977 在时间 2024-06-11 20:29:02.139241
User10 会话标识 152-55977 在时间 2024-06-11 20:29:02.209716
User07 会话标识 152-55977 在时间 2024-06-11 20:29:02.271144
User01 会话标识 152-55977 在时间 2024-06-11 20:29:06.940760
User02 会话标识 277-27334 在时间 2024-06-11 20:29:06.940760
User04 会话标识 399-25678 在时间 2024-06-11 20:29:06.940760
User03 会话标识 399-25678 在时间 2024-06-11 20:29:06.982961
User05 会话标识 399-25678 在时间 2024-06-11 20:29:07.010853
User08 会话标识 399-25678 在时间 2024-06-11 20:29:07.023677
User09 会话标识 399-25678 在时间 2024-06-11 20:29:07.102189
User06 会话标识 399-25678 在时间 2024-06-11 20:29:07.152508
User10 会话标识 399-25678 在时间 2024-06-11 20:29:07.223001
User07 会话标识 399-25678 在时间 2024-06-11 20:29:07.284733
搞定!
耗时 25.96 秒
这表明服务器进程和会话内存在不同用户查询之间被重用,例如,前两行拥有相同的会话标识符和序列号。
User01 会话ID 399-25678, 时间 2024-06-11 20:28:46.803668
User02 会话ID 399-25678, 时间 2024-06-11 20:28:46.830118
如果你仔细查看输出结果,可以看到此次运行中只有三个服务器被用来处理所有查询。会话标识符及其序列号如下:
152-55977
277-27334
399-25678
这比没有隐式连接池时需要的十个服务器要少得多。你的实际结果会因各种因素(比如时间)而有所不同。
两个运行之间的总时间差异虽然接近,在这种小测试中还不足以用来比较。总体来看,由于隐式连接池共享资源,而Oracle堆栈还需要做一些额外的工作,因此在切换到隐式连接池后,总时间可能会变慢——如果这种差异能准确测量的话。这取决于应用程序执行SQL的频繁程度。然而,好处是数据库层由于服务器进程的数量减少而使用的内存更少,因此可能更省资源,并且它肯定能处理其他应用程序中更多的用户连接。
设置隐式连接池DRCP 提供了隐式连接池功能使用的可配置的数据库服务器进程池。需要在数据库中启用、监控和调整 DRCP,请参阅技术简报《实现极高的 Oracle 数据库连接扩展性的数据库驻留连接池》(详见《利用数据库驻留连接池实现极高的 Oracle 数据库连接扩展性》技术简报)。(https://www.oracle.com/docs/tech/drcp-technical-brief.pdf)
在应用程序连接方面,您的连接字符串只需请求一个 DRCP 池化服务器实例,例如使用“:pooled
”或“(SERVER=POOLED)
”。连接字符串还需要包含一个新的 POOL_BOUNDARY
参数。本博文中使用的连接字符串是 Easy Connect 格式的 localhost/orclpdb1:pooled?pool_boundary=statement
。在 tnsnames.ora
文件中,等效的连接描述符将包含 "...(POOL_BOUNDARY=STATEMENT)
..."
POOL_BOUNDARY
参数可以是 STATEMENT
或 TRANSACTION
- 声明:当连接处于隐式无状态(即连接中没有活动游标,所有游标的数据已被内部获取,没有活动事务,没有临时表,也没有临时LOB)时,连接会被释放回DRCP连接池。
- 事务:当应用程序发起提交或回滚时,连接会被释放回DRCP连接池。因此,在使用隐式连接池的情况下,不建议启用任何驱动程序的“自动提交”功能。如果启用了该功能,您将无法获取需要多次数据库交互的数据,例如流式LOB数据。
根据标准的DRCP安全建议,在连接字符串中添加一个POOL_CONNECTION_CLASS
参数,并为所有类似的应用程序使用相同的值,例如,可以为所有类似的应用程序使用相同的值。
localhost/orclpdb1:pooled?pool_boundary=statement&pool_connection_class=myappname
隐式连接池使用的 DRCP “纯净” 默认为 SELF
,这允许重用服务器会话内存中的进程。添加连接字符串参数 POOL_PURITY=NEW
会改变这种设置,从而每次使用连接时都会重新创建会话内存。
你可以选择性地创建一个 PL/SQL 包 ORA_CPOOL_STATE
,其中包含获取和重置连接会话状态的程序,请参阅 Oracle 调用接口文档。
如果你能重新组织你的应用程序来使用Oracle提供的连接池驱动程序,这可能是最高效的处理方式。连接池驱动程序不仅提供资源共享,还增强了Oracle数据库的高可用性。使用连接池驱动程序(如python-oracledb的连接池或node-oracledb的连接池)通常最适合多用户应用程序,但即使是偶尔使用连接的单用户应用也能从中受益。博客文章《使用node-oracledb和electron的桌面应用》(链接)中展示了这种情形。如果应用程序正在高效使用Oracle应用连接池,并频繁获取和释放连接,那么隐式连接池便不再有额外的好处。
如果你的应用程序无法切换到 Oracle 驱动程序连接池,但是该应用程序频繁打开和关闭连接,那么你就可以直接从 DRCP 中获益,而无需开启隐式连接池。请参阅我的博客文章《DRCP 未使用连接池的应用程序》DRCP 帮助未使用连接池的应用程序。
在应用层面,有些情况下隐式连接池并不适用。从本文示例可以看出,用户在脚本运行过程中看到的每个 SQL 语句的会话标识符和序列号会有所不同。因此,如果您的应用程序依赖于这些值不变,则隐式连接池可能不太合适。
另一个可能存在问题是,在应用程序使用多个游标或流式处理LOB数据时,使用TRANSACTION作为语句边界。在这种情况下,应用程序的任何提交都可能会使所有打开的游标失效,并且也会阻止LOB流式处理。
你应该在使用隐式连接池技术时彻底地测试你的应用,以确保数据库服务器和会话的内部重用不会导致任何问题,以避免任何潜在的问题。
最后隐式连接池允许长时间保持连接的应用共享数据库服务器的进程和会话内存。这能减轻数据库主机的内存负担,让整个系统更加灵活可扩展。对于那些打开单一连接或自己实现连接池而不是使用Oracle提供的驱动程序连接池的应用程序来说,隐式连接池可能有帮助。它受到流行Oracle数据库驱动程序的支持。
这篇帖子讨论了隐式连接池是如何使用DRCP的。它还可以利用PRCP,当你的系统使用[Oracle连接管理器的流量总监模式]时,PRCP在这种情况下可以被使用。
可以观看我的同事Sharad Chandran R.的视频《Effortless Connection Management with Implicit Pooling in Oracle数据库23c》(Effortless Connection Management with Implicit Pooling in Oracle Database 23c),以了解隐式连接池管理的相关内容。
参考文献- 视频:轻松管理 Oracle Database 23c 中的隐式连接池功能(视频)。
- Python-oracledb 文档:隐式连接池。
- Node-oracledb 文档:隐式连接池。
- 博客文章:永远使用连接池及其应用方法。
- 博客文章:DRCP 帮助没有使用连接池的应用程序
- 技术简报:使用 DRCP 实现 Oracle 数据库连接的极致可扩展性。
- 技术简报:CMAN-TDM — Oracle 数据库连接代理,适用于可扩展和高可用性应用程序。
共同学习,写下你的评论
评论加载中...
作者其他优质文章