语句句柄缓存

实际应用中,经常会遇到在同一个线程中多条不同的SQL反复交替执行的场景,执行效率低。为了优化,程序中经常有 ACIStmtPrepare 去准备一条sql语句后,可以通过 ACIStmtExecute 多次执行这个stmt,但如果同一个stmt执行了其他sql语句,之前Prepare过的sql语句无法继续使用,需要重新执行 ACIStmtPrepare ,没有达到提供性能的目的。 ACIStmtPrepare2 接口可以满足这类需求,他会为每一个sql创建一个对应的stmt,并将stmt缓存到sess句柄中,通过键值(key名称)对stmt进行标识,stmt中存储了已经用 ACIStmtPrepare2 过的SQL和相关的执行计划,如果是相同SQL,通过键值就可以找到已经prepare过这个SQL的stmt对象,拿到后直接执行。

ACIStmtPrepare2 解决了一个应用系统中,多个SQL交替执行,且能最大限度的使用语句缓存的能力,进一步提高了SQL执行效率。

ACIStmtPrepare2 会为没有存储过的SQL创建一个新的stmt对象,通过 ACIStmtRelease 接口将生成的stmt对象放到sess句柄的缓存结构中去。

开启语句句柄缓存

开启语句句柄缓存的方式:

  • 单用户连接

通过 ACILogon2 接口登录时,如果将mode参数设置为ACI_LOGON2_STMTCACHE,开启则代表开启语句句柄缓存。

如果从连接池中获取到的连接,且开启语句缓存,mode设置为:ACI_LOGON2_CPOOL | ACI_LOGON2_STMTCACHE

ACILogon2(m_penv, m_perr, &m_psvc, (OraText*)g_dbuser, strlen(g_dbuser), (OraText*)g_dbpwd, strlen(g_dbpwd), poolName, poolNameLen, ACI_LOGON2_CPOOL | ACI_LOGON2_STMTCACHE)
  • 非连接池模式

是在 ACISessionBegin 调用过程中,将最后一个mode参数设置为ACI_STMT_CACHE:

r = ACISessionBegin(m_psvc, m_perr, m_pses, ACI_CRED_RDBMS, ACI_STMT_CACHE);
  • 连接池模式

需要调用 ACISessionGet 时使用ACI_SESSGET_CPOOL | ACI_SESSGET_STMTCACHE 模式:

r = ACISessionGet(m_penv, m_perr, &m_psvc, authhp, poolName, poolNameLen,
NULL, 0, NULL, NULL, NULL, ACI_SESSGET_CPOOL | ACI_SESSGET_STMTCACHE);
  • 隐式开启

当我们在 ACISessionBeginACISessionGet 调用时都没有指定STMTCACHE模式,但设置了svc上下文句柄的ACI_ATTR_STMTCACHESIZE的属性,ACI会隐式开启语句句柄缓存功能。

获得语句句柄

开启语句句柄缓存后,需要使用 ACIStmtPrepare2 去准备SQL语句,返回语句句柄规则如需:

1) 如果是非语句缓存模式或者key为NULL,则根据提供的sql语句生成一个新的stmt句柄返回;

2) 如果是语句缓存模式且key不为NULL,则根据key查找该会话中的stmt缓存,查找成功则直接返回,此时忽略提供的sql语句。查找失败则根据提供的sql语句生成一个新的stmt句柄返回;

char    *ptrKey = "aaaaa";
r = ACIStmtPrepare2(m_psvc, &stmt2, m_perr, (const OraText *)selectSQL,
                (ub4)strlen(selectSQL), (OraText *) ptrKey, strlen(ptrKey), ACI_HTYPE_ERROR, ACI_DEFAULT);

执行了 ACIStmtPrepare2 后,语句句柄并没有真实的进行缓存。如果在调用 ACIStmtPrepare2 时;

ACISessionBegin 未开启语句句柄缓存时, ACIStmtPrepare2 会忽略传入的键值,即使是相同的SQL语句,每次返回的stmt句柄都不相同。

语句句柄的缓存存储

ACIStmtRelease 的执行才会将语句句柄存储在sess句柄中,执行方式如下:
r = ACIStmtRelease(stmt2, m_perr, (const text *)ptrKey, strlen(ptrKey), ACI_DEFAULT);

如果多次执行 ACIStmtRelease 且传入的stmt相同,但键值不同,则以最后一次传入的键值才能找到对应的stmt。对于sess中的stmt缓存,stmt是唯一存在的,不会为相同的stmt用不同的键值进行存储多个。

另外,如果用一个相同的键值去存储不同的stmt句柄,如果在缓存中找到了相同的键值,aci会自动将键值对应的stmt句柄释放,然后再存储新的stmt句柄。

如果在执行 ACIStmtPrepare2 时的键值传入为NULL,在 ACIStmtRelease 时键值也传入NULL,则不会存储这个语句句柄,而是会将这个stmt句柄释放;

ACISessionBegin 为开启语句句柄缓存时, ACIStmtRelease 会忽略传入的键值,同时也会将传入的stmt句柄释放;

ACIStmtRelease 接口模式为ACI_DEFAULT时,会再将缓存语句放回,模式指定为ACI_STRLS_CACHE_DELETE,此时就不将缓存语句再放回并且释放掉stmt句柄。

注意:

  1. 当模式值错误的情况下,不释放句柄,且不会缓存语句。
  2. 模式为ACI_STRLS_CACHE_DELETE时,删除当前缓存,并释放句柄。
  3. 模式为ACI_DEFAULT时,缓存语句,不释放句柄;
  4. 如果 ACIStmtRelease 的mode值传入错误,此时接口会返回一个错误,但语句句柄并不会释放,需要开发者进行错误处理和释放,否则可能会造成内存泄漏。

设置句柄缓存数量

ACI默认一个sess句柄中可以存储1024个语句句柄,可设置上下文句柄的ACI_ATTR_STMTCACHESIZE的属性更改会话中可缓存的句柄数量:

r = ACIAttrSet(m_psvc, ACI_HTYPE_SVCCTX, &cacheNum, sizeof(cacheNum), ACI_ATTR_STMTCACHESIZE, m_perr);

如果缓存的句柄数量超过了设定的值,则会通过先进先出的机制,将最早缓存的stmt句柄自动释放。如果设置的最大值小于已有的缓存数,则将在调用 ACIStmtRelease 时释放最早缓存的句柄(aci在设置时释放),直到小于最大值。

语句句柄缓存的释放

语句句柄的缓存时sess级的,因此当调用 ACISessionEndACISessionRelease 后,sess中的语句句柄缓存会被全部释放,之后无法使用。

开发者也可以设置ACI_ATTR_STMTCACHESIZE属性,将缓存设置为0,则ACI也会自动释放缓存中的句柄。

设置句柄缓存数量

应用程序请设计好要缓存语句句柄的唯一键值,做到SQL语句与唯一键值的对应,且将经常用到重复支持的SQL语句的stmt进行缓存。将所有的SQL语句的语句句柄缓存并不是优化手段,反而会降低性能。

语句句柄的缓存会占用一定的内存空间,应用程序的内存占用会更高,需要根据运行环境的实际情况,设置合理的缓存大小,不合理的值也可能会降低性能。