зеркало из
https://github.com/iharh/notes.git
synced 2025-10-30 13:16:07 +02:00
m
Этот коммит содержится в:
родитель
3c47fbb5fa
Коммит
8e3a218ddd
82
db/sql/features/query/antipatterns/nplusone.txt
Обычный файл
82
db/sql/features/query/antipatterns/nplusone.txt
Обычный файл
@ -0,0 +1,82 @@
|
|||||||
|
******************************************************************************************************************************************************
|
||||||
|
Антипаттерн: N+1 запросов — как заметить и починить
|
||||||
|
|
||||||
|
Вы берёте список сущностей, а потом в цикле для каждой тянете связанные данные. В итоге - 1 запрос за «родителями» + N запросов за «детьми». Латентность растёт линейно от размера выборки.
|
||||||
|
|
||||||
|
Симптомы
|
||||||
|
|
||||||
|
- В логах много одинаковых коротких запросов.
|
||||||
|
- Кол-во запросов ≈ размеру списка.
|
||||||
|
- Страница/endpoint сильно «замедляется» при росте данных.
|
||||||
|
|
||||||
|
Плохой пример (SQL + псевдокод)
|
||||||
|
|
||||||
|
|
||||||
|
-- Берём пользователей
|
||||||
|
SELECT id, name FROM users WHERE active = true;
|
||||||
|
|
||||||
|
-- Потом в цикле по каждому:
|
||||||
|
SELECT count(*) FROM orders WHERE user_id = :id;
|
||||||
|
|
||||||
|
|
||||||
|
Правильно (SQL, PostgreSQL) — сетевое мышление:
|
||||||
|
|
||||||
|
|
||||||
|
SELECT u.id,
|
||||||
|
u.name,
|
||||||
|
count(o.*) AS orders_cnt
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN orders o ON o.user_id = u.id
|
||||||
|
WHERE u.active = true
|
||||||
|
GROUP BY u.id, u.name;
|
||||||
|
|
||||||
|
|
||||||
|
Django ORM
|
||||||
|
|
||||||
|
|
||||||
|
# Плохо: в шаблоне/цикле обращаемся к user.orders -> N+1
|
||||||
|
users = User.objects.filter(active=True)
|
||||||
|
|
||||||
|
# Хорошо: подгрузим связи заранее
|
||||||
|
users = (User.objects
|
||||||
|
.filter(active=True)
|
||||||
|
.prefetch_related('orders')) # для 1:N
|
||||||
|
# для 1:1 / ForeignKey используйте select_related('profile')
|
||||||
|
|
||||||
|
# Агрегация без цикла
|
||||||
|
from django.db.models import Count
|
||||||
|
users = (User.objects.filter(active=True)
|
||||||
|
.annotate(orders_cnt=Count('orders')))
|
||||||
|
|
||||||
|
|
||||||
|
SQLAlchemy
|
||||||
|
|
||||||
|
|
||||||
|
from sqlalchemy.orm import selectinload, joinedload
|
||||||
|
|
||||||
|
# 1:N — безопаснее selectinload (батчирует IN (...))
|
||||||
|
users = (session.query(User)
|
||||||
|
.options(selectinload(User.orders))
|
||||||
|
.filter(User.active.is_(True))
|
||||||
|
.all())
|
||||||
|
|
||||||
|
# 1:1 — joinedload
|
||||||
|
user = (session.query(User)
|
||||||
|
.options(joinedload(User.profile))
|
||||||
|
.get(user_id))
|
||||||
|
|
||||||
|
|
||||||
|
Практические советы
|
||||||
|
|
||||||
|
- Логируйте кол-во запросов на эндпойнт/страницу. В Django - django-debug-toolbar, assertNumQueries в тестах; в SQLAlchemy - echo/интеграция с логгером.
|
||||||
|
- Индексы: обязательно orders(user_id); если фильтруете по статусу - составной (user_id, status).
|
||||||
|
- Батчинг вместо циклов: тяните детей одним запросом WHERE user_id IN (...), затем мапьте в памяти.
|
||||||
|
- Осторожно с joinedload для 1:N на больших выборках - риск «взрыва» строк. Для 1:N чаще выбирайте selectinload.
|
||||||
|
- Колонки по делу: не тащите SELECT *, берите только нужные поля.
|
||||||
|
- Пагинация: уменьшает N и давление на сеть/память.
|
||||||
|
- EXPLAIN (ANALYZE, BUFFERS) - проверяйте планы и кардинальности.
|
||||||
|
|
||||||
|
|
||||||
|
💡Думайте наборами, а не циклами. Eager loading + агрегаты закрывают 90% случаев N+1. Настройте мониторинг количества запросов - и ловите проблему до продакшена.
|
||||||
|
|
||||||
|
Сохрани, чтобы не наступить снова. Поделись с коллегами. А как вы ловите N+1 у себя?
|
||||||
@ -8,6 +8,27 @@ environment:
|
|||||||
volumes:
|
volumes:
|
||||||
$PWD/data:/storage
|
$PWD/data:/storage
|
||||||
|
|
||||||
|
docker run -it --rm --name windows \
|
||||||
|
-p 8006:8006 \
|
||||||
|
--device=/dev/kvm \
|
||||||
|
--device=/dev/net/tun \
|
||||||
|
--cap-add NET_ADMIN \
|
||||||
|
-v "${PWD}/windows:/storage" \
|
||||||
|
--stop-timeout 120 \
|
||||||
|
dockurr/windows
|
||||||
|
|
||||||
|
???
|
||||||
|
--stop-timeout
|
||||||
|
Timeout (in seconds) to stop a container, or -1 to disable timeout.
|
||||||
|
|
||||||
|
The --stop-timeout flag sets the number of seconds to wait for the container
|
||||||
|
to stop after sending the pre-defined (see --stop-signal) system call signal.
|
||||||
|
If the container does not exit after the timeout elapses, it is forcibly killed
|
||||||
|
with a SIGKILL signal.
|
||||||
|
|
||||||
|
If --stop-timeout is set to -1, no timeout is applied, and the daemon will
|
||||||
|
wait indefinitely for the container to exit.
|
||||||
|
|
||||||
2024
|
2024
|
||||||
Wolfgang - Running Windows in a Docker Container! 0:00 of 10:06
|
Wolfgang - Running Windows in a Docker Container! 0:00 of 10:06
|
||||||
https://www.youtube.com/watch?v=xhGYobuG508
|
https://www.youtube.com/watch?v=xhGYobuG508
|
||||||
|
|||||||
3
pl/java/libfws/spring/core/docs/presentations.txt
Обычный файл
3
pl/java/libfws/spring/core/docs/presentations.txt
Обычный файл
@ -0,0 +1,3 @@
|
|||||||
|
2025
|
||||||
|
DanVega - Spring Boot Tutorial: Resolving Bean Ambiguity Like a Pro 0:00 of 16:51
|
||||||
|
https://www.youtube.com/watch?v=jCjHAD94c8A
|
||||||
1
science/ai/tools/client/cli-skynet.txt
Обычный файл
1
science/ai/tools/client/cli-skynet.txt
Обычный файл
@ -0,0 +1 @@
|
|||||||
|
https://github.com/hybridgroup/skynet
|
||||||
Загрузка…
x
Ссылка в новой задаче
Block a user