Golang database/sql: db.QueryContext() не возвращает ошибку несмотря на ORA-56735 в Oracle Resource Manager
Мы используем Oracle базу данных с Golang и драйвером github.com/godror/godror версии v0.49.0. При выполнении SELECT-запроса, который занимает более 60 секунд с использованием DB.QueryContext(), мы наблюдаем непоследовательное поведение:
rows, err = db.QueryContext(ctx, SqlString, namedParams...)
Без установки таймаута для контекста мы наблюдаем:
-
При использовании имени сервиса
mydb_svc_b(пакетный режим с таймаутом 30 минут):- Запрос возвращает строки без какой-либо ошибки
-
При использовании имени сервиса
mydb_svc_s(кратковременный режим с таймаутом по умолчанию 1 минута):- В логах Oracle отображается
ORA-56735: elapsed time limit exceeded - call aborted - Строки не возвращаются, но и ошибка не возвращается
- В логах Oracle отображается
Соответствующий исходный код из database/sql/sql.go:
func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error) {
var rows *Rows
var err error
err = db.retry(func(strategy connReuseStrategy) error {
rows, err = db.query(ctx, query, args, strategy)
return err
})
return rows, err
}
Это известная проблема драйвера godror или пакета database/sql? Какой рекомендуемый подход для правильной обработки ошибок ORA-56735 в Golang при запросах к Oracle базам данных?
Проблема с драйвером godror для Oracle в Go: обработка ошибок ORA-56735
Драйвер godror для Oracle в Go не правильно распространяет ошибки ORA-56735 (таймауты из Oracle Resource Manager) обратно в приложение из-за того, как драйвер обрабатывает коды ошибок OCI. Это известное ограничение, при котором серверные таймауты обнаруживаются, но не преобразуются в ошибки Go, которые могут быть перехвачены db.QueryContext(), что приводит к тихим сбоям вместо корректной обработки ошибок.
Содержание
- Понимание проблемы
- ORA-56735 и Oracle Resource Manager
- Почему godror не возвращает ошибку
- Решения и обходные пути
- Рассмотрения пула соединений
- Тестирование и валидация
Понимание проблемы
Проблема, с которой вы сталкиваетесь, связана с тем, как драйвер godror обрабатывает ошибки таймаута Oracle Resource Manager (ORA-56735). Когда запрос превышает лимит времени, установленный в Oracle Resource Manager, база данных прерывает операцию и регистрирует ошибку ORA-56735, но драйвер godror не преобразует это в ошибку Go, которую можно перехватить с помощью db.QueryContext().
Согласно обсуждению на Stack Overflow, это поведение различается в разных конфигурациях Oracle service:
- Пакетный сервис (таймаут 30 минут): Запросы успешно завершаются без ошибок
- Короткий сервис (таймаут 1 минута): Запросы прерываются на сервере, но ошибка не возвращается в приложение
Основная причина заключается в том, как Oracle OCI слой обрабатывает ошибки таймаута и как драйвер godror преобразует их в ошибки Go. В отличие от некоторых других ошибок Oracle, ORA-56735 может быть правильно не отображен логикой преобразования ошибок драйвера.
ORA-56735 и Oracle Resource Manager
ORA-56735: elapsed time limit exceeded - call aborted - это ошибка Oracle Database, которая возникает, когда Resource Manager применяет временные ограничения к запросам. Это важный механизм предотвращения неограниченных запросов, которые потребляют чрезмерные ресурсы базы данных.
Документация Oracle указывает, что эта ошибка возникает, когда:
- Запрос превышает лимит времени, назначенный его группе потребителей
- Resource Manager активно прерывает операцию
- Транзакция откатывается из-за таймаута
Примечание: ORA-56735 отличается от клиентских таймаутов. Это механизм принудительного исполнения на уровне Oracle Database, который происходит независимо от любых настроек таймаута на уровне Go.
При использовании разных имен Oracle service (mydb_svc_b против mydb_svc_s) вы фактически подключаетесь к разным конфигурациям Resource Manager с разными политиками таймаута. Пакетный сервис позволяет 30 минут, в то время как короткий сервис применяет ограничение в 1 минуту.
Почему godror не возвращает ошибку
Драйвер godror использует под капотом библиотеку клиента Oracle OCI (Oracle Call Interface), и здесь и originates проблема преобразования ошибок. Как отмечено в репозитории godror на GitHub, драйвер relies на отличный OCI wrapper ODPI-C от Энтони Туининга.
Проблема возникает потому, что:
- Сопоставление ошибок OCI: Не все коды ошибок Oracle единообразно сопоставляются с ошибками OCI
- Тихие прерывания: Некоторые условия прерывания на сервере могут не запускать коды возврата ошибок OCI
- Состояние соединения: Соединение может оставаться открытым, но в несогласованном состоянии после таймаута
Как указано в обсуждении на Stack Overflow, это известное ограничение, которое влияет на различные версии драйвера godror, включая v0.49.0, который вы в настоящее время используете.
Решения и обходные пути
1. Обновление до более новой версии godror
Последняя версия godror (на ноябрь 2025) - 0.49.4. Хотя в обсуждении на Stack Overflow предполагается, что эта проблема все еще может существовать, более новые версии часто включают улучшенную обработку ошибок:
// Проверьте текущую версию и обновите при необходимости
import "github.com/godror/godror"
fmt.Println(godror.Version)
2. Реализация клиентского таймаута запроса
Добавьте клиентский таймаут для отлова долгих запросов независимо от поведения сервера:
func queryWithTimeout(ctx context.Context, db *sql.DB, query string, args ...interface{}) (*sql.Rows, error) {
// Создайте контекст с таймаутом
timeoutCtx, cancel := context.WithTimeout(ctx, 60*time.Second)
defer cancel()
// Выполните запрос с контекстом таймаута
return db.QueryContext(timeoutCtx, query, args...)
}
3. Проверка состояния соединения после запроса
Проверьте состояние соединения после операций, которые могли быть прерваны таймаутом:
func executeWithConnectionCheck(ctx context.Context, db *sql.DB, query string, args ...interface{}) (*sql.Rows, error) {
rows, err := db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
// Проверьте, что соединение все еще валидно
var isValid bool
err = db.QueryRowContext(ctx, "SELECT 1 FROM DUAL").Scan(&isValid)
if err != nil {
rows.Close()
return nil, fmt.Errorf("соединение, похоже, невалидно после запроса: %w", err)
}
return rows, nil
}
4. Использование специфичной для Oracle проверки ошибок
Воспользуйтесь возможностями обработки ошибок, специфичными для godror:
import "github.com/godror/godror"
rows, err := db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
// Проверьте специфичные для Oracle условия
if godror.IsError(err, godror.ORA-56735) {
return nil, fmt.Errorf("запрос превысил таймаут в Oracle Resource Manager: %w", err)
}
5. Альтернативная настройка Resource Manager
Поработайте с вашим DBA для настройки Resource Manager или рассмотрите использование разных групп потребителей для разных типов операций.
Рассмотрения пула соединений
Как упоминается в документации godror, клиент Oracle OCI имеет проблемы с быстрыми циклами подключения-переподключения, что может привести к SIGSEGV в C-библиотеке. Драйвер рекомендует использование пула соединений:
// Настройте пул соединений
db.SetMaxIdleConns(10) // Сохраняйте неактивные соединения
db.SetMaxOpenConns(100) // Максимальное количество открытых соединений
db.SetConnMaxLifetime(30 * time.Minute) // Время жизни соединения
Кроме того, вы можете использовать встроенный пул сессий godror:
// Используйте встроенный пул сессий godror
db.SetConnMaxLifetime(0) // Отключите время жизни соединения для пула сессий
Тестирование и валидация
Для корректного тестирования и валидации обработки таймаутов реализуйте комплексную стратегию тестирования:
1. Создайте тестовые запросы с известными задержками
func testQueryTimeouts(db *sql.DB) {
// Тестируйте с запросом, который должен превысить таймаут
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
// Создайте запрос, который займет больше времени, чем таймаут
// (Требуется таблица с достаточным объемом данных и медленный запрос)
query := `
SELECT /*+ MONITOR */ *
FROM large_table
WHERE some_condition = 'value'
`
rows, err := db.QueryContext(ctx, query)
if err != nil {
log.Printf("Ожидаемая ошибка таймаута: %v", err)
} else {
rows.Close()
log.Println("Запрос завершился без ошибки таймаута")
}
}
2. Мониторинг Oracle логов
Настройте мониторинг для перехвата ошибок ORA-56735 в логах Oracle:
-- Включите SQL trace для сессии
ALTER SESSION SET sql_trace = true;
ALTER SESSION events '10046 trace name context forever, level 12';
3. Реализация метрик и оповещений
Добавьте метрики для отслеживания долгих запросов и неудачных операций:
var (
queryDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "oracle_query_duration_seconds",
Help: "Длительность запросов Oracle",
},
[]string{"service", "query_type"},
)
queryErrors = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "oracle_query_errors_total",
Help: "Общее количество ошибок запросов Oracle",
},
[]string="service", "error_code", "error_type",
)
)
func recordQueryMetrics(service string, duration time.Duration, err error) {
queryDuration.WithLabelValues(service, "select").Observe(duration.Seconds())
if err != nil {
var oraErr *godror.Error
if errors.As(err, &oraErr) {
queryErrors.WithLabelValues(service, fmt.Sprintf("%d", oraErr.Code), "oracle").Inc()
} else {
queryErrors.WithLabelValues(service, "unknown", "other").Inc()
}
}
}
Источники
- Stack Overflow: golang database/sql db.QueryContext() does not throw error even if we see ORA-56735 in Resource Manager for the query
- Oracle Database Error Messages: ORA-56735
- GitHub - godror/godror: GO DRiver for ORacle DB
- godror package documentation
- Oracle Developer: Working in Go applications with Oracle Database
Заключение
Проблема обработки ошибок ORA-56735 в godror - это известное ограничение, при котором серверные таймауты Resource Manager не правильно распространяются обратно в приложения Go. Для решения этой проблемы рассмотрите:
- Обновление до последней версии godror (0.49.4 или новее) для улучшенной обработки ошибок
- Реализация клиентских таймаутов как предохранителя независимо от поведения сервера
- Добавление валидации состояния соединения после потенциально прерванных таймаутом запросов
- Использование правильного пула соединений для избежания проблем, связанных с OCI
- Реализация комплексного мониторинга для обнаружения тихих сбоев
Наиболее надежный подход сочетает клиентские таймауты с корректной обработкой ошибок и мониторингом, чтобы обеспечить эффективное обнаружение и обработку таймаутов Oracle Resource Manager в ваших приложениях Go.