Created
June 26, 2024 16:33
-
-
Save denisxab/43e10f4963089d5b7bb7373235672bb5 to your computer and use it in GitHub Desktop.
Каждый бекенд-разработчик желает знать, сколько запрос к СУБД формирует Django ORM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
import traceback | |
from functools import wraps | |
from typing import Any | |
from django.db.backends.utils import CursorWrapper | |
class TraceSQL: | |
"""Класс для трассировки SQL запросов. | |
Позволяет узнать в какой части кода выполнялся SQL запрос, для того чтобы его оптимизировать. | |
Обычно вы можете узнать только какие SQL запросы выполнялись, но не знаете в какой части кода они выполнились, | |
и тогда нужно тратить время и угадывать какая строка кода вызывает эти SQL запросы. | |
Пример использования: | |
class ИмяApiViewTests(APITestCase): | |
def test_имя(self) -> None: | |
# В path_trace_compile укажем регулярное выражение для фильтрации стека трассировки | |
with TraceSQL(path_trace_compile="/code/api/(?!.*tests).*") as trace: | |
response = self.client.put( | |
'URL', | |
data={ | |
"имя": "имя", | |
"пол": "мужской", | |
"дата рождения": "01.01.2000", | |
}, | |
format="json", | |
) | |
# Трассировка выполненных запросов | |
print(trace.stack_sql) | |
""" | |
def __init__(self, path_trace_compile: str): | |
"""Инициализация. | |
Args: | |
path_trace_compile: Регулярное выражение для фильтрации файлов, которые мы хотим трассировать. | |
Например, укажите `/api/*`, чтобы игнорировать в трассировке файлы библиотек и тестов. | |
""" | |
self.path_trace_compile = re.compile(path_trace_compile) | |
# Список запросов | |
self.stack_sql: list[dict[str, Any]] = [] | |
# Сохраним оригинальные функции | |
self.original_execute = CursorWrapper.execute | |
self.original_executemany = CursorWrapper.executemany | |
def __enter__(self): | |
def base_trace_stack(sql: str, params: list): | |
stack: traceback.StackSummary = traceback.extract_stack()[:-1] | |
filter_stack: list[traceback.FrameSummary] = [ | |
frame for frame in stack if self.path_trace_compile.search(frame.filename) | |
] | |
self.stack_sql.append({"sql": sql, "stack": filter_stack, "params": params}) | |
@wraps(CursorWrapper.executemany) | |
def execute_with_trace(cursor, sql, params=None): | |
"""Трассировка запросов для execute.""" | |
base_trace_stack(sql, params) | |
return self.original_execute(cursor, sql, params) | |
@wraps(CursorWrapper.executemany) | |
def executemany_with_trace(cursor, sql, param_list): | |
"""Трассировка запросов для executemany.""" | |
base_trace_stack(sql, param_list) | |
return self.original_executemany(cursor, sql, param_list) | |
# Модифицируем функции для трассировки | |
CursorWrapper.execute = execute_with_trace | |
CursorWrapper.executemany = executemany_with_trace | |
return self | |
def __exit__(self, type, value, traceback): | |
"""Возвращает оригинальные функции.""" | |
CursorWrapper.execute = self.original_execute | |
CursorWrapper.executemany = self.original_executemany |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Заголовок
Каждый бекенд-разработчик желает знать, сколько запрос к СУБД формирует Django ORM
Django ORM - мощный инструмент для работы с базами данных, но он может создавать множество неявных SQL-запросов. Для бекенд-разработчиков критически важно понимать, сколько запросов генерируется и откуда они исходят. В этой статье мы рассмотрим проблему отслеживания SQL-запросов в Django и представим эффективное решение - TraceSQL.
Описание проблемы
Django ORM предоставляет удобный интерфейс для взаимодействия с базой данных, однако его использование может приводить к возникновению большого количества неявных запросов. Для оптимизации производительности критически важно знать, сколько и откуда выполняются SQL-запросы.
При разработке через TDD (Test-Driven Development) в legacy-проектах с большим количеством SQL-запросов часто возникает проблема: непонятно, откуда берутся запросы и где нужно оптимизировать код.
Чтобы отслеживать количество выполняемых SQL запросов во время выполнения API метода, есть несколько вариантов:
+
- Показывает место в коде где вызываетсяSQL
запрос.-
- Нельзя использовать во время тестов, поэтому не подходит для разработки через TDD.self.assertNumQueries(ОжидаемоеКоличествоЗапросов)
+
- Можно использовать во время тестов, показывает, какие SQL запросы выполнялись.-
- Не показывает место в коде, где вызываетсяSQL
запрос.TraceSQL
+
- Можно использовать во время тестов.+
- Показывает место в коде, где вызываетсяSQL
запрос.-
- Слишком подробный отчёт, который не особо нужен в тестах. Поэтому, когда вы уже оптимизировали SQL-запросы, используйте просто подсчёт количества выполняемых запросов черезself.assertNumQueries
.Как указано выше, стандартный
self.assertNumQueries
не показывает место в коде, откуда вызваны SQL-запросы. Когда таких запросов более десяти, уже трудно понять, откуда они берутся, и приходится угадывать эти места. Поэтому я исследовал интернет, спросил уGPT
и понял, что мало где написано про такой вариант.Код и пример TraceSQL
Заключение
Использование TraceSQL помогает более эффективно отслеживать и оптимизировать SQL-запросы в Django проекте. Этот инструмент является компромиссом между возможностями
django-debug-toolbar
иself.assertNumQueries
, позволяя разработчикам легко определять места в коде, вызывающие лишние SQL-запросы.