Bug #115832 Cursor-based reading can fail in certain scenarios
Submitted: 14 Aug 2024 6:18 Modified: 14 Aug 2024 13:18
Reporter: DF Rong Email Updates:
Status: Verified Impact on me:
None 
Category:Connector / J Severity:S2 (Serious)
Version:9.0.0 OS:Any
Assigned to: CPU Architecture:Any
Tags: allowMultiQueries=true, FetchSize>0, SQL statement which contains a semicolon, useCursorFetch=true

[14 Aug 2024 6:18] DF Rong
Description:
When I use JDBC with cursor-based streaming to retrieve a set number of rows each time (useCursorFetch=true, FetchSize>0), and also set the JDBC parameter allowMultiQueries=true, if the SQL statement I execute contains a semicolon (;), then in JDBC 8.0.25, cursor-based reading will fail, and all the data will be directly cached on the client, which can easily cause a client OOM (Out of Memory) error. However, this issue does not exist in JDBC version 5.1.40. Please confirm if this is a bug.

How to repeat:
You are experiencing a problem where using JDBC 8.0.25 with cursor-based streaming (useCursorFetch=true, FetchSize>0) and multi-query (allowMultiQueries=true) settings, combined with semicolon-separated SQL statements, results in a failure of cursor-based reading. This causes all data to be cached client-side, leading to potential Out Of Memory (OOM) errors—a problem that does not occur in JDBC 5.1.40.

Here’s a Java code example to reproduce this issue:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBCCursorFetchExample {
    public static void main(String[] args) {
        String jdbcUrl = "jdbc:mysql://localhost:3306/your_database?useCursorFetch=true&allowMultiQueries=true";
        String username = "your_username";
        String password = "your_password";
        int fetchSize = 10;

        try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
             Statement statement = connection.createStatement()) {

            statement.setFetchSize(fetchSize);

            // A semicolon-separated SQL statement 
            String sql = "SELECT * FROM large_table1; SELECT * FROM large_table2;";
            
            boolean isResultSet = statement.execute(sql);

            while (isResultSet) {
                try (ResultSet resultSet = statement.getResultSet()) {
                    while (resultSet.next()) {
                        // Process the rows here
                        System.out.println(resultSet.getString(1));
                    }
                }

                // Move to the next result
                isResultSet = statement.getMoreResults();
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
复制
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBCCursorFetchExample {
    public static void main(String[] args) {
        String jdbcUrl = "jdbc:mysql://localhost:3306/your_database?useCursorFetch=true&allowMultiQueries=true";
        String username = "your_username";
        String password = "your_password";
        int fetchSize = 10;

        try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
             Statement statement = connection.createStatement()) {

            statement.setFetchSize(fetchSize);

            // A semicolon-separated SQL statement 
            String sql = "SELECT * FROM large_table1; SELECT * FROM large_table2;";
            
            boolean isResultSet = statement.execute(sql);

            while (isResultSet) {
                try (ResultSet resultSet = statement.getResultSet()) {
                    while (resultSet.next()) {
                        // Process the rows here
                        System.out.println(resultSet.getString(1));
                    }
                }

                // Move to the next result
                isResultSet = statement.getMoreResults();
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

 java
Explanation:
Configuration:

JDBC URL includes useCursorFetch=true to enable cursor-based streaming.
allowMultiQueries=true allows multiple SQL queries in a single statement.
Settings:

statement.setFetchSize(fetchSize) aims to set the fetch size for cursor-based streaming.
SQL Statement:

The sql string contains a semicolon-separated multi-query.
Execution:

statement.execute(sql) executes the multi-query.
statement.getMoreResults() is used to handle multiple result sets.
However, in JDBC 8.0.25 with the described configuration, the cursor-based streaming might not work as expected, causing all data to be loaded into the client, thereby potentially leading to an OOM error. This behavior does not occur in JDBC 5.1.40, indicating it might be considered a bug.

Ensure the your_database, your_username, your_password, and table names (large_table1, large_table2) are replaced with actual values relevant to your setup.
[14 Aug 2024 13:18] MySQL Verification Team
Hello DF Rong,

Thank you for the report and feedback.
Verified as described.

regards,
Umesh
[20 Aug 2024 21:36] Axyoan Marcelo
Posted by developer:
 
Hello DF Rong,

If you look at the documentation for useCursorFetch (https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-performance-extensions.ht...)
You can see the following: "Please note that 'useServerPrepStmts' is automatically set to "true" in this case because cursor functionality is available only for server-side prepared statements."
Meaning the driver will use Prepared Statements, the problem is that prepared statements cannot execute multiple statements, see the following: https://dev.mysql.com/doc/refman/9.0/en/sql-prepared-statements.html
"SQL syntax for prepared statements does not support multi-statements (that is, multiple statements within a single string separated by ; characters)."

In this case, the driver automatically falls back to emulated client prepared statements, (see https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-prepared-statements.html#...). Because this is not a server-side prepared statement, you cannot use a cursor-based result set. Hope this helps you.

Regards,
Axyoan
[10 Apr 9:02] MySQL Verification Team
Bug #117928 marked as duplicate of this one