如何以原子方式运行 2 个 SQL“SELECT”?或者在我处理它们之前获取行数的任何其他更好的方法

Posted

技术标签:

【中文标题】如何以原子方式运行 2 个 SQL“SELECT”?或者在我处理它们之前获取行数的任何其他更好的方法【英文标题】:How to run 2 SQL "SELECT"s atomically? Or any other better way to get number of rows before i process them 【发布时间】:2015-08-02 07:04:27 【问题描述】:

我正在尝试以原子方式运行

ResultSet resSet;
resSet = statement.executeQuery("SELECT COUNT(*) FROM table");
resSet.next()
long rowCount = resSet.getLong(1);
resSet = statement.executeQuery("SELECT * FROM table");
// read data of known row count...

我的问题是最好的方法是什么?

目前我发现我可以做到:

connection.setAutoCommit(false);
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE)
// call 2 SQL queries above
connection.commit();

这种方法似乎行得通。我测试了另一个线程被阻止在第一个SELECTcommit() 之间执行INSERT。 这是正确和最佳的方式吗?我可以确定这样我的 COUNT 将始终与从下一个选择返回的行数相同吗?

我也希望Connection.TRANSACTION_SERIALIZABLE 代替Connection.TRANSACTION_REPEATABLE_READ 就足够了。 但它在 Derby 10.11.1.1 中不起作用。它是一个错误吗?我是数据库业务的新手,但它对 H2 数据库按预期工作 - 因此我预计它可能是一个 derby 错误......

请注意,我已经知道您可以在哪里执行的解决方案:

statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, 
                                 ResultSet.CONCUR_READ_ONLY);
resultSet = statement.executeQuery("SELECT * FROM table");
if (ResultSet.last()) 
    int rowCount = ResultSet.getRow();
    ResultSet.beforeFirst(); 

while(ResultSet.next())...

但是这个解决方案并不是最优的。对于德比,我测量它慢了约 7 倍。对于 H2,它慢了约 2 倍。

【问题讨论】:

为什么不简单地执行第二个查询,并为每个读取行增加一个计数器。 还注意到有趣的事情,当connection.commit()executeQuery() 之后resultSet.next() 之前,H2 数据库将正确打印结果。但是 Derby 数据库会为您提供修改后的数据。因此,在 Derby 中,要使其以原子方式工作,您需要在从 resultSet 读出数据后输入 commit() 坦率地说,我不会太在意这种用例的隔离。假设您很不幸,并且在计数查询和第二个查询之间添加了一行。所以呢?处理第 n-1 个元素时,您的进度条将显示为 100% 而不是 99.999%。这真的有问题吗? @VitBernatik 您可以在定义 url 连接时使用所有应用程序的这些参数初始化连接,因此根本不需要调用 Connection#setAutocommitConnection#setTransactionIsolation,所有连接都将被创建像这样。此外,最好只调用第二个查询并使用计数器变量来检查您获得了多少行,而不是执行(同样繁重的)查询两次。 高度依赖于数据库(版本、设置、驱动程序),序列化工作的好坏和效率低下。我会不惜一切代价避免它。 【参考方案1】:

怎么样

SELECT (SELECT COUNT(*) FROM table) c, * FROM table

【讨论】:

哇,谢谢。但我不确定它是否不是太大的开销。想象一下,我有 1M 行,每行我都会复制这个计数,而我只需要计数一次。 同样适用于H2,但是Derby不理解这样的SQL语句。它抱怨第二个星号java.sql.SQLSyntaxErrorException: Syntax error: Encountered "*" at line 1, column 43. 您应该枚举所有列名而不是“*”。内部 SELECT 是外部 SELECT 中的一个常量值,所以我认为它会被评估一次。但这取决于实施。 是的,当我明确列出像 SELECT (SELECT COUNT(*) FROM table) c, ID, VAR1 FROM table 这样的列时,它也适用于 Derby DB。 所以我确实测试了这种方法,它看起来很有趣。它拓宽了我的 SQL 知识。但它并不快。我对 200K 行(4 列)的测试。表明 2 次连续读取比嵌入式数据库的组合选择更快。在 Derby 的情况下,我节省了 15% 的时间(~109 毫秒)。在 H2 的情况下,我节省了 35% 的时间(652 毫秒)。 Derby 的总读取时间(对于更快的连续读取版本)为 777 毫秒,H2 数据库为 1839 毫秒。 (5 次测量的平均值)

以上是关于如何以原子方式运行 2 个 SQL“SELECT”?或者在我处理它们之前获取行数的任何其他更好的方法的主要内容,如果未能解决你的问题,请参考以下文章

EF4 Code First + SQL Server CE:以原子方式保存双向引用

以原子方式更新多行

如何以无锁方式自动更新 2 个对象?

在 SQL Server 中,如何以类似于 Oracle 的“SELECT FOR UPDATE WAIT”的方式锁定单行?

如何以输出生成“创建表语句”的方式格式化 Oracle SQL 查询?

以编程方式添加新的 jquery-select2-4 选项并重置搜索字段?