MySQL 和 JDBC 缓存(?)问题与 Scala 中的过程调用有关

Posted

技术标签:

【中文标题】MySQL 和 JDBC 缓存(?)问题与 Scala 中的过程调用有关【英文标题】:MySQL and JDBC caching (?) issue with procedure call in Scala 【发布时间】:2014-02-27 06:31:27 【问题描述】:

我正在从事一个项目,该项目从字符串中解析文本单元(称为“ngrams”)并将它们存储在 mysql 数据库中。

在 MySQL 中,如果 ngram 尚不存在,我有以下过程应该将 ngram 存储在特定数据集(表)中并返回 ngram 的 id:

CREATE PROCEDURE `add_ngram`(IN ngram VARCHAR(400), IN dataset VARCHAR(128), OUT ngramId INT)
BEGIN

    -- try get id of ngram
    SET @s = CONCAT('SELECT `id` INTO @ngramId FROM `mdt_', dataset, '_b1` WHERE `ngram` = ''', ngram, ''' LIMIT 1');
    PREPARE stm FROM @s;
    EXECUTE stm;
    SET ngramId = @ngramId;

    -- if id could not be retrieved
    IF ngramId IS NULL THEN BEGIN
        -- insert ngram into dataset
        SET @s = CONCAT('INSERT INTO `mdt_', dataset, '_b1`(`ngram`) VALUES (''', ngram, ''')');
        PREPARE stm FROM @s;
        EXECUTE stm;
        SET ngramId = LAST_INSERT_ID();
    END;
    END IF;

END$$

一个数据集表只有两列:id,一个用作主键的自动递增 int,ngram,一个用作唯一索引的varchar(400)

在我的 scala 应用程序中,我有一个方法,它接收一个字符串,将其拆分为 ngram,然后通过将 ngram 传递给上述过程来返回 ngram id 的 Seq:

private def processNgrams(text: String, dataSet: String) 
    val ids = parser.parse(text).map(ngram => 
        val query = dbConn.prepareCall("CALL add_ngram(?,?,?)")
        query.setString(1, ngram)
        query.setString(2, dataSet)
        query.registerOutParameter(3, java.sql.Types.INTEGER)
        query.executeUpdate()
        dbConn.commit()
        val id = query.getInt(3)
        Debug(s"parsed ngram - id: $id $" " * (3 - id.toString.length)[ $ngram ]")
        id
    

上述代码中的dbConnjava.sql.Connection 的一个实例,并且自动提交设置为false。

执行此操作时,我注意到数据库中存储的 ngram 非常少。以下是上述方法中的调试语句打印出来的内容:

因此,有多个彼此明显不同的 ngram 似乎具有从该过程返回的相同 id。如果我查看数据库表,我可以看到例如 ngram“i”的 id 为“1”,但似乎紧随其后插入的 ngrams 也返回了一个 id 为“1”。我在表格中查找的其他 ngram 也是如此。这让我相信过程调用可能会被缓存?

我已经尝试了很多事情,例如在方法外部创建准备好的调用并重用它并每次调用 clearParameters,每次在方法内部创建一个新调用(如上所示),甚至休眠调用之间的线程,但似乎没有任何区别。

我还通过在 MySQL 客户端中手动运行查询来测试该过程,它似乎运行良好,尽管我的程序执行查询的速度比我手动执行的快得多,所以这可能会有所不同。

我不完全确定这是调用过程中的 JDBC 问题还是过程中的 MySQL 问题。我是 scala 和 MySQL 程序的新手,所以如果这真的很简单,我无法理解,请原谅我。

【问题讨论】:

【参考方案1】:

想通了!原来,是存储过程造成了所有的麻烦。

在过程中,我通过动态 SQL 查询(因为传入了表名)来检查 ngram 是否存在,该查询将值存储在@ngramId 中,这是一个会话变量。我将它存储在@ngramId 而不是ngramId(过程输出参数)中,因为准备好的语句只能选择到会话变量中(或者我在最初创建过程时被告知错误)。接下来我将@ngramId的值设置为ngramId,并检查ngramId是否为null,判断表中是否存在ngram;如果为 null,则将 ngram 插入表中,并将 ngramId 设置为最后插入的 id。

问题在于,因为@ngramId 是一个会话变量,并且因为我对所有过程调用使用了相同的数据库连接,所以@ngramId 的值在调用之间保持不变。例如,如果我使用 ngram “I”进行调用,并且在 id 为 1 的数据库中找到它,@ngramId 现在的值为 1。接下来,如果我尝试插入另一个不存在于该表中,动态选择语句没有返回任何内容,因此 @ngramId 的值保持为 1。由于输出参数 ngramId 填充了 @ngramId 的值,现在它不再为 NULL,它绕过了 if将 ngram 插入数据库并返回在表中找到的最后一个 ngram 的 id 的语句,导致 ngram id 看似缓存。

对此的解决方案是将以下行添加为过程中的第一条语句:

 SET @ngramId = NULL;

这会在同一会话中对过程的调用之间重置@ngramId 的值。

【讨论】:

以上是关于MySQL 和 JDBC 缓存(?)问题与 Scala 中的过程调用有关的主要内容,如果未能解决你的问题,请参考以下文章

JDBC - 语句、PreparedStatement、CallableStatement 和缓存

将 DDL 与 SELECT 通过 JDBC 混合时出现“错误:缓存计划不能更改结果类型”

mssql-jdbc MS SQL Server JDBC 驱动程序准备好的语句缓存性能问题与 Hikari CP

MySQL 和 JDBC 与 rewriteBatchedStatements=true

漏洞检测方法如何选?详解源代码与二进制SCA检测原理

缓存 JDBC 连接