risingwavelabs/risingwave · error · BatchError

failed to get row stream from mysql query

Error message

failed to get row stream from mysql query

What it means

In the MySQL batch query executor, `mysql_async`'s `query_iter(...).stream::<Row>()` returned `None`, meaning the connection did not yield a binary/text row stream for the executed statement (e.g. the statement produced no rowset). Since the executor expects result rows for the query, it fails with this error.

Source

Thrown at src/batch/executors/src/executor/mysql_query.rs:124

            .tcp_port(self.port.parse::<u16>().unwrap()) // FIXME
            .user(Some(self.username))
            .pass(Some(self.password))
            .db_name(Some(self.database))
            .into();

        let pool = mysql_async::Pool::new(database_opts);
        let mut conn = pool
            .get_conn()
            .await
            .context("failed to connect to mysql in batch executor")?;

        let query = self.query;
        let mut query_iter = conn
            .query_iter(query)
            .await
            .context("failed to execute my_sql_query in batch executor")?;
        let Some(row_stream) = query_iter.stream::<mysql_async::Row>().await? else {
            bail!("failed to get row stream from mysql query")
        };

        let mut builder = DataChunkBuilder::new(self.schema.data_types(), self.chunk_size);
        tracing::debug!("mysql_query_executor: query executed, start deserializing rows");
        // deserialize the rows
        #[for_await]
        for row in row_stream {
            let row = row?;
            let owned_row = mysql_row_to_owned_row(row, &self.schema)?;
            if let Some(chunk) = builder.append_one_row(owned_row) {
                yield chunk;
            }
        }
        if let Some(chunk) = builder.consume_all() {
            yield chunk;
        }
        return Ok(());
    }

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Verify the generated query returns a result set by running it directly against MySQL with the same credentials.
  2. Check for proxies/load balancers between RisingWave and MySQL and test with a direct connection.
  3. Confirm the MySQL user has SELECT privileges and the server supports streaming results (binary protocol).
  4. Check the MySQL server error log for aborted statements around the time of the failure.
Defensive patterns

Strategy: try-catch

Validate before calling

-- run the expected query manually before wiring the external table
SELECT 1 FROM information_schema.tables WHERE table_schema = 'db' AND table_name = 't';
-- then run the actual query shape to confirm it returns a result set

Try / catch

// wrap batch query execution and classify the failure
match executor.execute().await {
    Err(e) if e.to_string().contains("failed to get row stream from mysql query") => {
        // check query returns a result set; verify proxy/privileges; retry with direct connection
    }
    other => other?,
}

Prevention

When it happens

Trigger: Executing a batch MySQL external-table query where the statement does not return a row stream — e.g. the query was rewritten or routed to a statement that returns no resultset (non-SELECT), or the server closed/reset the result set.

Common situations: MySQL proxy/middleware (e.g. ProxySQL, MaxScale) interfering with result-set streaming; server-side errors surfaced as empty streams; pointing a RisingWave MySQL external table at a non-query endpoint or using a driver/proxy version mismatch.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/be74e2f04e468076. Report an issue: GitHub.