アンチパターン@RDB¶
はじめに¶
本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。
01. DB接続を再利用しない¶
問題¶
DB処理のたびにDB接続を確立すると、DB処理に時間がかかる。
実装例(TypeScript)¶
import type {User} from "@prisma/client";
import {prisma} from "~/services/prisma.server";
export async function handler() {
// 毎回、PrismaClientを作成してしまっている
const prisma = new PrismaClient();
return prisma.user.findMany();
}
02. 不要なDBレコードやカラムを取得する¶
問題¶
不要なDBレコードやカラムを取得すると、DB処理に時間がかかる。
実装例(TypeScript)¶
import type {User} from "@prisma/client";
import {prisma} from "~/services/prisma.server";
const user = await prisma.user.findUnique({
where: {id},
// 不要なカラムも取得してしまっている
include: {team: true, bans: true, logs: true, profiles: true},
});
03. N+1問題を起こす¶
N+1問題とは¶
親テーブルを経由して子テーブルにアクセスする時に、親テーブルのレコード数分のSQLを発行してしまうアンチパターンのこと。
実装例(PHP)¶
▼ 問題がある実装¶
反復処理の中で子テーブルのレコードにアクセスしてしまう場合、N+1問題が起こる。
内部的には、親テーブルへのSQLと、Where句を持つSQLが親テーブルのレコード数分だけ発行される。
<?php
// 親テーブルにSQLを発行 (1回)
$departments = Department::all();
foreach($departments as $department) {
// 親テーブルのレコード数分のWhere句SQLを発行する (N回)
$department->employees;
}
# 1回
select * from `departments`
# N回
select * from `employees` where `department_id` = 1
select * from `employees` where `department_id` = 2
select * from `employees` where `department_id` = 3
...
▼ 解決方法¶
反復処理の前に小テーブルにアクセスしておく。
データアクセス時にwith
メソッドを使用すると、親テーブルへのアクセスに加えて、親テーブルのEloquentモデルのプロパティに子テーブルのレコードを保持するように処理する。
そのため、反復処理ではプロパティからデータを取り出すだけになる。
内部的には、親テーブルへのSQLと、In句を使用したSQLが発行される。
<?php
// SQL発行 (2回)
$departments = Department::with('employees')->get();
foreach($departments as $department) {
// キャッシュを使用するのでSQLの発行はされない (0回)
$department->employees;
}
# 2回
select * from `departments`
select * from `employees` where `department_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ... 100)
実装例(TypeScript)¶
反復処理の中で子テーブルのレコードにアクセスしてしまう場合、N+1問題が起こる。
内部的には、親テーブルへのSQLと、Where句を持つSQLが親テーブルのレコード数分だけ発行される。
import type {User} from "@prisma/client";
import {prisma} from "~/services/prisma.server";
// 親テーブルにSQLを発行 (1回)
const users = await prisma.user.findMany({where: {teamId}});
for (const u of users) {
// 親テーブルのレコード数分のWhere句SQLを発行する (N回)
const logs = await prisma.log.findMany({
where: {userId: u.id},
});
}
04. 一覧取得でページング(取得数指定)がない¶
問題¶
DBからレコードの一覧を取得する場合、ページング(取得数指定)がないと、DB処理に時間がかかる。
実装例(TypeScript)¶
const users = await prisma.user.findMany({
where: {teamId},
});