DBのログに ERROR: duplicate key value violates unique constraint やSQLSTATE[40001] が出たとき、意味を即答できますか? エラーコードを見ても何が起きているのか分からず、ひたすらスタックトレースを追うのは時間のロスです。 この記事では、SQLStateコードの読み方と、23505・40001・28000など頻出コードの対処法を解説します。
SQLState とは
SQLState(SQLステート)は、ANSI SQL標準(ISO/IEC 9075)で定義された5文字のエラーコードです。 PostgreSQL・MySQL・Oracle・SQL Serverなど主要なRDBMSが共通フォーマットで返すため、 コードの意味を一度覚えれば複数のDBMSで応用できます。
構造は先頭2文字のクラスコード(エラーの大分類)と、後ろ3文字のサブクラスコード(詳細種別)に分かれます。 たとえば 23505 は、クラス 23(制約違反)の サブクラス 505(一意制約)を意味します。 サブクラスが 000 の場合は「このクラスの一般的なエラー」を指します。
クラスコード一覧
まずクラスコード(先頭2文字)を見ることで、エラーの大まかな種類を即座に判断できます。エラーコード横断検索ツールを使うと、SQLStateを含む主要なエラーコードをキーワードで横断検索できます。
| クラス | 意味 | 代表的なサブコード |
|---|---|---|
| 00 | 成功(Successful Completion) | 00000(正常終了) |
| 01 | 警告(Warning) | 01000(一般警告)・01006(権限なし) |
| 02 | データなし(No Data Found) | 02000(NOT FOUND) |
| 08 | 接続エラー(Connection Exception) | 08001(接続失敗)・08004(接続拒否) |
| 22 | データ例外(Data Exception) | 22001(文字列切り捨て)・22012(ゼロ除算) |
| 23 | 制約違反(Integrity Constraint Violation) | 23502(NOT NULL)・23503(外部キー)・23505(一意制約) |
| 25 | トランザクション状態(Invalid Transaction State) | 25001(アクティブSQL)・25P02(中断状態) |
| 28 | 認証失敗(Invalid Authorization Specification) | 28000(認証失敗)・28P01(パスワード不正) |
| 40 | トランザクションロールバック(Transaction Rollback) | 40001(デッドロック)・40P01(PG専用デッドロック) |
| 42 | 構文エラー・アクセス違反(Syntax Error or Access Rule Violation) | 42601(構文エラー)・42501(権限なし)・42P01(テーブルなし) |
| 53 | リソース不足(Insufficient Resources) | 53100(ディスク満杯)・53300(接続数超過) |
頻出エラーの個別解説
23000系 — 制約違反(Integrity Constraint Violation)
23505: 一意制約違反(Unique Violation)
PRIMARY KEYまたはUNIQUEインデックスで重複する値を INSERT しようとしたときに発生します。 たとえばメールアドレスをユニーク制約付きで登録しているテーブルに、 同じアドレスを二重登録しようとした場合などです。 アプリ側では「INSERT ... ON CONFLICT DO NOTHING」や「ON CONFLICT DO UPDATE」(いわゆるUPSERT)を使うことで、 エラーを発生させずにべき等な処理が実装できます。
23503: 外部キー制約違反(Foreign Key Violation)
参照先のレコードが存在しない状態でINSERT/UPDATEしようとした場合、 または参照されているレコードをDELETEしようとした場合に発生します。 削除順序(子テーブルから親テーブルの順で削除する)の間違いや、 バルクインポート時にデータ投入順が正しくない場合によく見られます。
23502: NOT NULL制約違反(Not Null Violation)
NOT NULL制約のあるカラムにNULLを挿入しようとしたときに発生します。 フォームから渡されたデータに必須フィールドが含まれていない場合や、 スキーマ変更で既存カラムにNOT NULL制約を後付けした際に起きやすいエラーです。
40001 — デッドロック(Deadlock Detected)
複数のトランザクションが互いに相手のロック解除を待ち合い、処理が進まなくなった状態です。 データベースはデッドロックを自動検出し、一方のトランザクションをロールバックして40001を返します。
根本的な対策は「すべてのトランザクションで同じ順序でテーブル・行にアクセスする」ことです。 アプリ側ではリトライロジック(指数バックオフ付き)を実装して40001を受け取ったら再実行します。 またINSERT時の一意制約チェックを事前SELECTで行っているコードは競合しやすいため、 前述のUPSERT構文に置き換えることでデッドロックを大幅に減らせます。
28000 — 認証失敗(Invalid Authorization Specification)
接続ユーザー名・パスワードの不一致、または接続元IPアドレスが許可されていない場合に発生します。 確認すべき点は①接続文字列(ユーザー名・パスワード・DB名)が正しいか、 ②PostgreSQLの場合は pg_hba.conf で接続元IPが許可されているか、 ③ユーザーに対象DBへのCONNECT権限が付与されているかです。
-- PostgreSQL: 権限確認コマンド
SELECT datname, datacl FROM pg_database WHERE datname = 'mydb';
-- 接続ログ確認(pg_hba.conf のルールにマッチしているか)
-- /var/log/postgresql/postgresql-*.log を確認
08001 / 08004 — 接続エラー
08001(Connection Does Not Exist)はネットワーク疎通自体が失敗した場合、 08004(Connection Rejected)はサーバーが接続を拒否した場合に発生します。
確認順序は①ホスト名・ポートが正しいか、②サーバープロセスが起動しているか、 ③ファイアウォール・セキュリティグループでポートが開いているか、 ④PostgreSQLの場合は postgresql.conf のlisten_addresses が正しいかです。 なお接続数が max_connections に達した場合は08004ではなく53300で現れるケースが多いです。
42601 — 構文エラー(Syntax Error)
SQLの文法が誤っている場合に発生します。 よくあるミスは「SELECT末尾の余分なカンマ」「user・table などの予約語をカラム名に使う(ダブルクォートでエスケープが必要)」 「シングルクォートとダブルクォートの混在」「WITH句や副クエリの括弧の閉じ漏れ」です。 エラーメッセージに含まれる「at or near "xxx"」のキーワード周辺を重点的に確認しましょう。
53300 — 接続数超過(Too Many Connections)
PostgreSQLのデフォルトの max_connections は100です。 コネクションプーリングなしで多数のアプリインスタンスが起動すると、すぐに上限に達します。 短期対策としては max_connections を増やす方法がありますが、 接続ごとにメモリを消費するためサーバースペックと相談が必要です。
根本解決はPgBouncerなどのコネクションプーラー導入です。 アプリからPgBouncerへは多数の接続を許しつつ、PostgreSQLへの実接続は少数に抑えることができます。 Supabase・Railway・Heroku Postgresなどのマネージドサービスでは標準でPgBouncerが組み込まれています。
DBMS別 SQLState サポート状況
SQLStateの実装粒度はDBMSによって異なります。SQL方言横断検索ツールでは、SQLの構文・関数の各DBMSでの違いを横断的に調べることができます。
| DBMS | SQLStateサポート | 補足 |
|---|---|---|
| PostgreSQL | ◎ 非常に詳細 | 独自クラス(P0・HV等)も含め200以上のコードを定義。pg_exception_detail・pg_exception_hintでより詳細な情報も取得可 |
| MySQL / MariaDB | △ 標準準拠+独自 | ANSI標準のコードに加えHYxxx形式の独自コードを使用。独自の数値エラーコード(1062等)と対応。 |
| SQLite | × 非対応 | SQLStateを返さない。独自の整数エラーコード(SQLITE_CONSTRAINT=19等)のみを使用。 |
PostgreSQL でのデバッグ手順
SQLStateが分かっても原因の詳細が不明な場合、以下のデバッグ手順が有効です。
1. アクティブセッションの確認
-- 現在実行中のクエリとロック待ち状態を確認
SELECT pid, state, wait_event_type, wait_event, query
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY query_start;
2. 遅延クエリの実行計画確認
-- EXPLAIN ANALYZE で実行計画と実際の実行時間を確認
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 123;
3. ログレベルの調整(開発環境のみ)
-- postgresql.conf(本番環境では慎重に)
log_min_messages = DEBUG1
log_min_error_statement = ERROR
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
注意
log_min_messages = DEBUG は本番環境では大量のログを生成しディスクを圧迫します。 デバッグが終わったら必ず元の値(通常は WARNING)に戻してください。
まとめ
- SQLStateは5文字のコードで、先頭2文字がクラス(大分類)・後ろ3文字がサブクラス(詳細)
- 23505(一意制約違反)はUPSERT構文で事前回避するのがベストプラクティス
- 40001(デッドロック)はアクセス順の統一+リトライロジックで対処する
- 28000(認証失敗)は接続文字列・pg_hba.conf・CONNECT権限の3点を確認する
- 53300(接続数超過)はPgBouncerなどのコネクションプーラーで根本解決する
- PostgreSQLはSQLStateの実装が最も詳細で、MySQLは独自コードを追加、SQLiteはSQLState非対応
- デバッグには
pg_stat_activityとEXPLAIN ANALYZEが有効