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
上述代码中的dbConn
是java.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