mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-10 00:33:28 +08:00
🐛 fix(sql-error): 锁等待超时不再被误判为查询超时并给出无效建议
MySQL 报 Error 1205 (HY000): Lock wait timeout exceeded 时,界面归类为「查询超时或被取消」, 建议「检查 SQL 执行计划、过滤条件和索引,必要时缩小查询范围或调整超时时间」—— 这套建议对行锁等待完全无效:成因是另一个事务持锁,调大超时只会让用户等更久。 根因:timeout_or_canceled 规则里有一条过宽的 /timeout/i,而 findSqlErrorSemantic 取首个 命中的规则,于是把锁等待超时也吞成了查询超时。 - 新增 lock_contention 规则并排在 timeout_or_canceled 之前,覆盖各方言的锁竞争与死锁: MySQL 1205/1213、PostgreSQL deadlock detected 与 lock timeout、 SQL Server 1222/1205、Oracle ORA-00060/00054/30006、SQLite database is locked - 文案改为指向真正的处置动作:先提交或回滚未完成的事务(含本应用 SQL 编辑器里自己未提交的 事务)再重试,并明确说明调大查询超时无效 - 6 个语言补齐 label/explanation/suggestion - 补 4 项测试:1205 不再归类为超时、建议不再提"调整超时"、各方言锁竞争均被识别、 普通查询超时不被新规则误吞。已确认清空新规则的 patterns 后 3 项必定失败, 且失败信息正好复现原缺陷
This commit is contained in:
@@ -125,3 +125,54 @@ describe('formatSqlExecutionError', () => {
|
||||
expect(formatSqlExecutionError(raw)).toBe(raw);
|
||||
});
|
||||
});
|
||||
|
||||
describe('锁竞争错误的语义分类', () => {
|
||||
// 回归背景:timeout_or_canceled 规则的 /timeout/i 过宽,把 MySQL 的
|
||||
// "Lock wait timeout exceeded" 也吞成「查询超时」,于是给出「检查执行计划、过滤条件和索引,
|
||||
// 必要时调整超时时间」这种完全无效的建议 —— 行锁等待超时的成因是另一个事务持锁,
|
||||
// 调大超时只会让用户等更久。lock_contention 规则必须先于 timeout 规则命中。
|
||||
it('MySQL 1205 锁等待超时不再被归类为查询超时', () => {
|
||||
const formatted = formatSqlExecutionError(
|
||||
'Error 1205 (HY000): Lock wait timeout exceeded; try restarting transaction',
|
||||
);
|
||||
|
||||
expect(formatted).toContain('Semantic meaning: Blocked by another transaction holding locks');
|
||||
expect(formatted).not.toContain('Query timed out or was canceled');
|
||||
expect(formatted).toContain('Raw error: Error 1205 (HY000): Lock wait timeout exceeded; try restarting transaction');
|
||||
});
|
||||
|
||||
it('建议指向提交或回滚未完成事务,而不是调整超时', () => {
|
||||
const formatted = formatSqlExecutionError('Lock wait timeout exceeded; try restarting transaction');
|
||||
|
||||
expect(formatted).toContain('Commit or roll back the pending transaction first');
|
||||
expect(formatted).not.toContain('adjust the timeout');
|
||||
});
|
||||
|
||||
it('覆盖各方言的锁竞争与死锁错误', () => {
|
||||
const messages = [
|
||||
'Error 1213 (40001): Deadlock found when trying to get lock; try restarting transaction',
|
||||
'ERROR: deadlock detected',
|
||||
'ERROR: canceling statement due to lock timeout',
|
||||
'Lock request time out period exceeded.',
|
||||
'Transaction (Process ID 52) was deadlocked on lock resources with another process',
|
||||
'ORA-00060: deadlock detected while waiting for resource',
|
||||
'ORA-00054: resource busy and acquire with NOWAIT specified',
|
||||
'database is locked',
|
||||
];
|
||||
for (const message of messages) {
|
||||
expect(
|
||||
formatSqlExecutionError(message),
|
||||
`未被识别为锁竞争:${message}`,
|
||||
).toContain('Semantic meaning: Blocked by another transaction holding locks');
|
||||
}
|
||||
});
|
||||
|
||||
it('普通查询超时仍归类为查询超时,未被新规则误吞', () => {
|
||||
for (const message of ['context deadline exceeded', 'sql: statement canceled', 'query execution timeout']) {
|
||||
expect(
|
||||
formatSqlExecutionError(message),
|
||||
`被锁竞争规则误吞:${message}`,
|
||||
).toContain('Semantic meaning: Query timed out or was canceled');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,6 +122,36 @@ const SQL_ERROR_RULES: SqlErrorSemanticRule[] = [
|
||||
/constraint failed/i,
|
||||
],
|
||||
},
|
||||
{
|
||||
// 必须排在 timeout_or_canceled 之前:findSqlErrorSemantic 取首个命中的规则,
|
||||
// 而那条规则的 /timeout/i 过宽,会把 "Lock wait timeout exceeded" 也吞成「查询超时」,
|
||||
// 于是给出「检查执行计划、过滤条件和索引,必要时调整超时时间」这种完全无效的建议 ——
|
||||
// 行锁等待超时的成因是另一个事务持锁,调大超时只会让用户等更久。
|
||||
key: 'lock_contention',
|
||||
fallbackLabel: 'Blocked by another transaction holding locks',
|
||||
fallbackExplanation: 'Another uncommitted transaction holds locks on the target rows or table, so this statement waited past the lock timeout or was picked as a deadlock victim.',
|
||||
fallbackSuggestion: 'Commit or roll back the pending transaction first — including this app\'s own uncommitted SQL editor transaction — then retry. Raising the query timeout does not help.',
|
||||
patterns: [
|
||||
// MySQL / MariaDB:1205 锁等待超时、1213 死锁
|
||||
/lock wait timeout exceeded/i,
|
||||
/deadlock found when trying to get lock/i,
|
||||
// PostgreSQL 系
|
||||
/deadlock detected/i,
|
||||
/canceling statement due to lock timeout/i,
|
||||
/could not obtain lock/i,
|
||||
// SQL Server:1222 锁请求超时、1205 死锁牺牲者
|
||||
/lock request time out period exceeded/i,
|
||||
/was deadlocked on lock resources/i,
|
||||
/deadlock victim/i,
|
||||
// Oracle:ORA-00060 死锁、ORA-00054/ORA-30006 资源忙
|
||||
/ORA-00060/i,
|
||||
/ORA-00054/i,
|
||||
/ORA-30006/i,
|
||||
// SQLite
|
||||
/database is locked/i,
|
||||
/database table is locked/i,
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'timeout_or_canceled',
|
||||
fallbackLabel: 'Query timed out or was canceled',
|
||||
|
||||
@@ -6724,6 +6724,9 @@
|
||||
"query_editor.sql_error.rule.generic.explanation": "Die Datenbank hat einen Ausführungsfehler zurückgegeben, ohne dass ein spezifischerer Fehlertyp erkannt wurde.",
|
||||
"query_editor.sql_error.rule.generic.label": "Datenbankausführungsfehler",
|
||||
"query_editor.sql_error.rule.generic.suggestion": "Untersuche weiter mit dem ursprünglichen Fehler, dem SQL-Fragment und dem aktuellen Datenbankdialekt.",
|
||||
"query_editor.sql_error.rule.lock_contention.explanation": "Eine andere, nicht committete Transaktion hält Sperren auf den Zielzeilen oder der Tabelle. Diese Anweisung hat daher das Sperr-Timeout überschritten oder wurde als Deadlock-Opfer ausgewählt.",
|
||||
"query_editor.sql_error.rule.lock_contention.label": "Durch Sperren einer anderen Transaktion blockiert",
|
||||
"query_editor.sql_error.rule.lock_contention.suggestion": "Committen oder rollen Sie die offene Transaktion zuerst zurück – einschließlich der eigenen, nicht committeten Transaktion im SQL-Editor dieser Anwendung – und versuchen Sie es erneut. Ein höheres Abfrage-Timeout hilft nicht.",
|
||||
"query_editor.sql_error.rule.object_missing.explanation": "Das SQL verweist auf eine Tabelle, View, Sequenz oder ein anderes Datenbankobjekt, das in der aktuellen Datenbank oder im schema nicht gefunden wurde.",
|
||||
"query_editor.sql_error.rule.object_missing.label": "Tabelle oder Objekt existiert nicht",
|
||||
"query_editor.sql_error.rule.object_missing.suggestion": "Prüfe Objektname, Groß-/Kleinschreibung, schema/database-Präfix und ob die für diese Abfrage ausgewählte Datenbank korrekt ist.",
|
||||
|
||||
@@ -6724,6 +6724,9 @@
|
||||
"query_editor.sql_error.rule.generic.explanation": "The database returned an execution failure, and no more specific error type was matched.",
|
||||
"query_editor.sql_error.rule.generic.label": "Database execution error",
|
||||
"query_editor.sql_error.rule.generic.suggestion": "Continue troubleshooting with the raw error, SQL fragment, and current database dialect.",
|
||||
"query_editor.sql_error.rule.lock_contention.explanation": "Another uncommitted transaction holds locks on the target rows or table, so this statement waited past the lock timeout or was picked as a deadlock victim.",
|
||||
"query_editor.sql_error.rule.lock_contention.label": "Blocked by another transaction holding locks",
|
||||
"query_editor.sql_error.rule.lock_contention.suggestion": "Commit or roll back the pending transaction first — including this app's own uncommitted SQL editor transaction — then retry. Raising the query timeout does not help.",
|
||||
"query_editor.sql_error.rule.object_missing.explanation": "The SQL references a table, view, sequence, or other database object that cannot be found in the current database or schema.",
|
||||
"query_editor.sql_error.rule.object_missing.label": "Table or object does not exist",
|
||||
"query_editor.sql_error.rule.object_missing.suggestion": "Check the object name, casing, schema/database prefix, and whether the selected database for this query is correct.",
|
||||
|
||||
@@ -6724,6 +6724,9 @@
|
||||
"query_editor.sql_error.rule.generic.explanation": "データベースから実行失敗情報が返されましたが、より具体的なエラー種別には一致しませんでした。",
|
||||
"query_editor.sql_error.rule.generic.label": "データベース実行エラー",
|
||||
"query_editor.sql_error.rule.generic.suggestion": "元のエラー、SQL 断片、現在のデータベース方言を合わせて引き続き調査してください。",
|
||||
"query_editor.sql_error.rule.lock_contention.explanation": "未コミットの別トランザクションが対象行またはテーブルのロックを保持しているため、本ステートメントはロックタイムアウトを超えて待機したか、デッドロックの犠牲者に選ばれました。",
|
||||
"query_editor.sql_error.rule.lock_contention.label": "他のトランザクションのロックによりブロックされました",
|
||||
"query_editor.sql_error.rule.lock_contention.suggestion": "先に未完了のトランザクション(本アプリの SQL エディターで未コミットのものを含む)をコミットまたはロールバックしてから再試行してください。クエリタイムアウトを延ばしても解決しません。",
|
||||
"query_editor.sql_error.rule.object_missing.explanation": "SQL が、現在のデータベースまたは schema に存在しない表、ビュー、シーケンス、その他のデータベースオブジェクトを参照しています。",
|
||||
"query_editor.sql_error.rule.object_missing.label": "表またはオブジェクトが存在しません",
|
||||
"query_editor.sql_error.rule.object_missing.suggestion": "オブジェクト名、大文字小文字、schema/database プレフィックス、およびこのクエリで選択したデータベースが正しいか確認してください。",
|
||||
|
||||
@@ -6724,6 +6724,9 @@
|
||||
"query_editor.sql_error.rule.generic.explanation": "База данных вернула ошибку выполнения, но более конкретный тип ошибки не был определен.",
|
||||
"query_editor.sql_error.rule.generic.label": "Ошибка выполнения в базе данных",
|
||||
"query_editor.sql_error.rule.generic.suggestion": "Продолжайте диагностику по исходной ошибке, фрагменту SQL и текущему диалекту базы данных.",
|
||||
"query_editor.sql_error.rule.lock_contention.explanation": "Другая незафиксированная транзакция удерживает блокировки целевых строк или таблицы, поэтому этот запрос превысил таймаут блокировки или был выбран жертвой взаимоблокировки.",
|
||||
"query_editor.sql_error.rule.lock_contention.label": "Заблокировано блокировками другой транзакции",
|
||||
"query_editor.sql_error.rule.lock_contention.suggestion": "Сначала зафиксируйте или откатите незавершённую транзакцию — включая собственную незафиксированную транзакцию в SQL-редакторе этого приложения — затем повторите. Увеличение таймаута запроса не поможет.",
|
||||
"query_editor.sql_error.rule.object_missing.explanation": "SQL ссылается на таблицу, представление, последовательность или другой объект базы данных, которого нет в текущей базе или schema.",
|
||||
"query_editor.sql_error.rule.object_missing.label": "Таблица или объект не существует",
|
||||
"query_editor.sql_error.rule.object_missing.suggestion": "Проверьте имя объекта, регистр, префикс schema/database и правильность выбранной для запроса базы данных.",
|
||||
|
||||
@@ -6724,6 +6724,9 @@
|
||||
"query_editor.sql_error.rule.generic.explanation": "数据库返回了执行失败信息,当前未匹配到更具体的错误类型。",
|
||||
"query_editor.sql_error.rule.generic.label": "数据库执行错误",
|
||||
"query_editor.sql_error.rule.generic.suggestion": "结合原始错误、SQL 片段和当前数据库方言继续排查。",
|
||||
"query_editor.sql_error.rule.lock_contention.explanation": "另一个未提交的事务正持有目标行或表的锁,本语句等待超过锁超时时间,或被选为死锁牺牲者。",
|
||||
"query_editor.sql_error.rule.lock_contention.label": "被其他事务的锁阻塞",
|
||||
"query_editor.sql_error.rule.lock_contention.suggestion": "先提交或回滚那个未完成的事务(也包括本应用 SQL 编辑器里自己未提交的事务),然后重试。调大查询超时时间无效。",
|
||||
"query_editor.sql_error.rule.object_missing.explanation": "SQL 引用了当前库或 schema 中找不到的表、视图、序列或其他数据库对象。",
|
||||
"query_editor.sql_error.rule.object_missing.label": "表或对象不存在",
|
||||
"query_editor.sql_error.rule.object_missing.suggestion": "确认对象名称、大小写、schema/database 前缀,以及当前查询所选数据库是否正确。",
|
||||
|
||||
@@ -6724,6 +6724,9 @@
|
||||
"query_editor.sql_error.rule.generic.explanation": "資料庫傳回了執行失敗資訊,目前未匹配到更具體的錯誤類型。",
|
||||
"query_editor.sql_error.rule.generic.label": "資料庫執行錯誤",
|
||||
"query_editor.sql_error.rule.generic.suggestion": "結合原始錯誤、SQL 片段和目前資料庫方言繼續排查。",
|
||||
"query_editor.sql_error.rule.lock_contention.explanation": "另一個未提交的交易正持有目標列或資料表的鎖,本語句等待超過鎖定超時時間,或被選為死鎖犧牲者。",
|
||||
"query_editor.sql_error.rule.lock_contention.label": "被其他交易的鎖阻塞",
|
||||
"query_editor.sql_error.rule.lock_contention.suggestion": "請先提交或回滾那個未完成的交易(也包括本應用 SQL 編輯器中自己未提交的交易),然後重試。調大查詢超時時間無效。",
|
||||
"query_editor.sql_error.rule.object_missing.explanation": "SQL 引用了目前資料庫或 schema 中找不到的表、檢視、序列或其他資料庫物件。",
|
||||
"query_editor.sql_error.rule.object_missing.label": "表或物件不存在",
|
||||
"query_editor.sql_error.rule.object_missing.suggestion": "確認物件名稱、大小寫、schema/database 前綴,以及目前查詢所選資料庫是否正確。",
|
||||
|
||||
Reference in New Issue
Block a user