Django 踩坑记:OceanBase 4012 Timeout 两条红线,语句超时 vs 事务超时一次讲透

8 阅读3分钟

一、4012 是谁抛的?

Django 本身没有 4012 错误码,它是 OceanBase 的“杀手”信号:

当前 SQL 当前事务累计执行时间 ≥ 系统阈值,直接返回 4012。


二、两条红线长啥样?

变量名默认阈值计时对象触发后果
ob_query_timeout10 000 000 µs = 10 s单条 SQL 执行时长这条语句被杀,事务可继续
ob_trx_timeout100 000 000 µs = 100 s事务 begin→commit 总时长下一条语句必 4012,事务必须回滚

谁先撞线谁动手,互不插队。


三、Django 视角看区别

1. 语句超时——只掐“这一条 SQL”

# 视图里甚至没开事务
from django.db import connection

def stmt_timeout_demo(request):
    with connection.cursor() as c:
        c.execute("SET SESSION ob_query_timeout = 1_000_000")  # 1 s
        c.execute("SELECT SLEEP(3)")        # 单条 SQL 执行 3 s → 4012
    return HttpResponse("ok")

现象SLEEP(3) 还没跑完,OceanBase 直接返回 4012,Django 抛 OperationalError,事务不存在,照样杀。

2. 事务超时——掐“begin 到 commit”的总时长

from django.db import transaction
import time

@transaction.atomic
def trx_timeout_demo(request):
    from app.models import Order
    Order.objects.filter(id=1).update(status=1)  # 0.1 s
    time.sleep(11)                               # 业务睡了 11 s
    Order.objects.filter(id=2).update(status=1)  # 事务已存活 > 10 s → 4012

现象:第 2 条 update 刚发出去就收到 4012,Django 自动回滚整个事务,再抛 OperationalError


四、本地复现完整脚本

把下面代码保存为 test_4012.pypython manage.py shell 里直接跑:

import os, django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings')
django.setup()

from django.db import connection

def trigger_4012():
    with connection.cursor() as cur:
        # 1. 把两条阈值都改成 1 s(单位 µs)
        cur.execute("SET SESSION ob_query_timeout = 1_000_000")
        cur.execute("SET SESSION ob_trx_timeout   = 1_000_000")
        print("阈值已设为 1 s")

        # 2. 开启事务
        cur.execute("BEGIN")

        # 3. 故意跑 3 s,触发语句超时
        try:
            cur.execute("SELECT SLEEP(3)")
        except Exception as e:
            print("语句超时 →", e)

        # 4. 再试一次,触发事务超时
        try:
            cur.execute("SELECT 1")
        except Exception as e:
            print("事务超时 →", e)
        finally:
            cur.execute("ROLLBACK")

if __name__ == '__main__':
    trigger_4012()

运行结果示例

阈值已设为 1 s
语句超时 → (4012, 'Timeout')
事务超时 → (4012, 'Timeout')

两条红线一次体验齐活。


五、如何调大阈值

-- 全局生效(需 OB 超管)
SET GLOBAL ob_query_timeout = 60_000_000;   -- 60 s
SET GLOBAL ob_trx_timeout   = 86400_000_000; -- 24 h

或者只在 Django 当前连接生效:

'OPTIONS': {
    'init_command': "SET ob_query_timeout=60_000_000, ob_trx_timeout=86400_000_000",
}

六、一句话总结

4012 不是 Django 的锅,而是 OceanBase 的两条“红线”:

  • 语句超时掐单条 SQL 执行时长;
  • 事务超时掐 begin→commit 总时长。
    搞清谁先撞线,定位改阈值还是拆事务,下次再见到 4012 就能秒解。