{"record":{"id":"b213720c6878b147","repo":"Tencent/APIJSON","slug":"config-gettable-sqlcount-sql","errorCode":null,"errorMessage":"截至 ${config.getTable()} 已执行 ${sqlCount} 条 SQL，数量已超限，必须在 0-${maxSQLCount} 内 !","messagePattern":"截至 (.+?) 已执行 (.+?) 条 SQL，数量已超限，必须在 0-(.+?) 内 !","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractParser.java","lineNumber":2163,"sourceCode":"\t\t\t}\n\t\t\telse {\n\t\t\t\tresult = getSQLExecutor().execute(config, false);\n\t\t\t\t// FIXME 改为直接在 sqlExecutor 内加好，最后 Parser<T, M, L> 取结果，可以解决并发执行导致内部计算出错\n//\t\t\t\texecutedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration();\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tthrow CommonException.wrap(e, config);\n\t\t}\n\t\tfinally {\n\t\t\tif (config.getPosition() == 0 && config.limitSQLCount()) {\n\t\t\t\tint maxSQLCount = getMaxSQLCount();\n\t\t\t\tint sqlCount = getSQLExecutor().getExecutedSQLCount();\n\t\t\t\tLog.d(TAG, \"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< \\n\\n\\n 已执行 \" + sqlCount + \"/\" + maxSQLCount + \" 条 SQL \\n\\n\\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\");\n\t\t\t\tif (sqlCount > maxSQLCount) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"截至 \" + config.getTable() + \" 已执行 \" + sqlCount + \" 条 SQL，数量已超限，必须在 0-\" + maxSQLCount + \" 内 !\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\n\t//事务处理 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\tprivate int transactionIsolation = Connection.TRANSACTION_NONE;\n\t@Override\n\tpublic int getTransactionIsolation() {\n\t\treturn transactionIsolation;\n\t}\n\t@Override\n\tpublic void setTransactionIsolation(int transactionIsolation) {\n\t\tthis.transactionIsolation = transactionIsolation;\n\t}\n\n\t@Override","sourceCodeStart":2145,"sourceCodeEnd":2181,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java#L2145-L2181","documentation":"After executing the top-level request item (config.getPosition() == 0), the parser counts SQL statements executed by the SQL executor and compares against getMaxSQLCount(). If exceeded, an IllegalArgumentException is thrown from the finally block — a guard against runaway queries from deep joins, huge batches, or recursive APP JOIN fan-out.","triggerScenarios":"A single request whose joins/arrays fan out into more SQL statements than the limit (default caps are small, e.g. 200): batch inserts of many rows, APP JOIN over a large parent array (one SQL per parent row), nested multi-level arrays, or @combine with many conditions.","commonSituations":"Raising batch sizes without adjusting limits; enabling APP JOIN on lists that previously used SQL JOIN; production data growth pushing a per-row lookup pattern past the cap; demo defaults left in place.","solutions":["Reduce the SQL fan-out: shrink 'count'/batch size, unnest arrays, or replace APP JOIN per-row lookups with SQL JOIN","Raise the cap deliberately via your parser configuration (override getMaxSQLCount()/setMaxSQLCount on the Parser, or the corresponding framework config) after assessing DB load","Split one mega-request into several smaller requests so each stays under the limit"],"exampleFix":"// before\n{ '[]': { 'count': 500, 'User': {}, 'join': '@/User/Comment/toId@' } }  // one SQL per user -> blows past maxSQLCount\n// after\n{ '[]': { 'count': 20, 'User': {}, 'join': '</Comment/userId@', 'Comment': { 'userId@': '/User/id' } } }  // single JOIN SQL","handlingStrategy":"try-catch","validationCode":"// Estimate SQL cost client-side before sending: parent rows x per-row APP JOIN tables + 1 per SQL-joined table + 1 per array level\nfunction estimateSQLCount(req) {\n  let n = 0;\n  for (const [k, v] of Object.entries(req)) {\n    if (k.endsWith('[]') && v && typeof v === 'object') {\n      n += 1 + (v.count ?? 10) * countAppJoinTables(v); // rough upper bound\n    } else if (v && typeof v === 'object') n += 1;\n  }\n  return n;\n}\n// if (estimateSQLCount(req) > MAX_SQL_COUNT) shrink count / batch before sending","typeGuard":null,"tryCatchPattern":"try { result = parser.parse(request); } catch (IllegalArgumentException e) { if (e.getMessage().contains('数量已超限')) { /* halve batch size and retry once, or surface quota error to caller */ } else throw e; }","preventionTips":["Set count/batch sizes so (parent rows x joined lookups) stays well under maxSQLCount","Prefer SQL JOIN over APP JOIN for lists that can grow","Cap request size at the gateway; monitor getExecutedSQLCount() in logs to catch drift before users do"],"tags":["apijson","sql-limit","performance","configuration"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}