{"record":{"id":"7cbf31a6312720d3","repo":"alibaba/canal","slug":"read-error","errorCode":null,"errorMessage":"Read  error: ","messagePattern":"Read  error: ","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/QueryLogEvent.java","lineNumber":913,"sourceCode":"                            }\n                        }\n                        break;\n                    case Q_OPT_INDEX_FORMAT_PANDA_ENABLED:\n                        // *start++ = thd->variables.opt_index_format_panda_enabled;\n                        buffer.forward(1);\n                        break;\n                    default:\n                        /*\n                         * That's why you must write status vars in growing\n                         * order of code\n                         */\n                        logger.error(\"Query_log_event has unknown status vars (first has code: \" + code\n                                     + \"), skipping the rest of them\");\n                        return; // Break loop\n                }\n            }\n        } catch (RuntimeException e) {\n            throw new IOException(\"Read \" + findCodeName(code) + \" error: \" + e.getMessage(), e);\n        }\n    }\n\n    public final String getUser() {\n        return user;\n    }\n\n    public final String getHost() {\n        return host;\n    }\n\n    public final String getQuery() {\n        return query;\n    }\n\n    public final String getCatalog() {\n        return catalog;\n    }","sourceCodeStart":895,"sourceCodeEnd":931,"githubUrl":"https://github.com/alibaba/canal/blob/87be50e87686a3e8af08c368d0e1ffd1f59eb04a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/QueryLogEvent.java#L895-L931","documentation":"Thrown while parsing the status-variable block of a MySQL Query_log_event (e.g. BEGIN/COMMIT/DDL). The parser loops over status-var codes (Q_* constants) and this catch wraps any RuntimeException — buffer underflow, unknown code, or malformed length — into an IOException tagged with the offending status-var code name via findCodeName(code). The preceding default branch already logs-and-skips unknown codes, so this catch fires on structurally corrupt data: a truncated or misaligned status-var payload that breaks buffer reads.","triggerScenarios":"A QueryLogEvent whose status-vars section is shorter than the declared length, contains a code whose handler reads more bytes than remain, or originates from a MySQL/MariaDB fork (lizard, panda, recycle-bin) emitting a code the reader only partially understands. The exception surfaces at parse time, before the event is handed to a Canal instance.","commonSituations":"Canal version older than the upstream MySQL that produced the binlog; replicating from a patched MySQL fork (AliSQL lizard/panda) without the matching Canal build; binlog corruption on disk or over a flaky network; a partial binlog read after an unclean master shutdown.","solutions":["Upgrade Canal to a release that supports the status-var codes your MySQL version emits (check the Q_* cases in QueryLogEvent around the throwing line).","If the master is a fork, confirm the Canal build matches it (lizard/panda Q_LIZARD_* and Q_OPT_* branches must be present).","Inspect the binlog with mysqlbinlog at the reported position to confirm the event is structurally valid; if corrupt, skip the position with canal.instance.master.position or reseed from a known-good position.","If the code is genuinely unsupported, the existing default-branch logger.error already skips remaining vars — a RuntimeException before that means truncation, so verify binlog file integrity (checksum, file size vs index)."],"exampleFix":"// before: runtime exception during status-var read escapes as IOException\n// after (defensive read, guard each code handler against remaining bytes):\nint remaining = buffer.remaining();\nif (code == Q_WHATEVER && remaining < needed) {\n    logger.warn(\"Q_WHATEVER truncated (need {}, have {}), skipping\", needed, remaining);\n    return;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// At the CanalEventSink / parser boundary\ntry {\n    eventSink.consume(event);\n} catch (IOException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Read \")) {\n        // status-var parse failure — log position and skip the event\n        logger.error(\"Status-var parse failure at {}:{}: {}\",\n            logContext.getLogFilename(), event.getHeader().getLogPos(), e.getMessage());\n        // advance past this event to continue replication\n    } else {\n        throw e;\n    }\n}","preventionTips":["Keep Canal version >= the MySQL major version it replicates from so all Q_* status-var codes are recognized.","If replicating from a fork (AliSQL), use the matching Canal fork build.","Monitor binlog integrity (checksums) on the master."],"tags":["binlog","query-event","status-vars","parsing","mysql-fork"],"backgroundTag":null,"analyzedSha":"87be50e87686a3e8af08c368d0e1ffd1f59eb04a","analyzedAt":"2026-08-14T04:30:11.918Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}