ぱんだツールズぱんだツールズ

技術背景

SQLStateエラーの読み方と対処法 — 23505・40001・28000をDBログから解決する

約7分

DBのログに ERROR: duplicate key value violates unique constraintSQLSTATE[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.conflisten_addresses が正しいかです。 なお接続数が max_connections に達した場合は08004ではなく53300で現れるケースが多いです。

42601 — 構文エラー(Syntax Error)

SQLの文法が誤っている場合に発生します。 よくあるミスは「SELECT末尾の余分なカンマ」「usertable などの予約語をカラム名に使う(ダブルクォートでエスケープが必要)」 「シングルクォートとダブルクォートの混在」「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での違いを横断的に調べることができます。

DBMSSQLStateサポート補足
PostgreSQL◎ 非常に詳細独自クラス(P0・HV等)も含め200以上のコードを定義。pg_exception_detailpg_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_activityEXPLAIN ANALYZE が有効

よくある質問

23505(一意制約違反)をアプリで処理するベストプラクティスは?

アプリケーション側でSQLStateを捕捉し、ユーザーに「すでに登録済みのメールアドレスです」のような具体的なメッセージを返すのが基本です。PostgreSQLのエラーオブジェクトには `constraint` フィールドが含まれるため、どの制約に違反したかを特定できます。INSERT前にSELECTで確認する(SELECT → INSERT パターン)は競合状態が発生するため避け、「INSERT ... ON CONFLICT DO NOTHING」や「INSERT ... ON CONFLICT DO UPDATE(UPSERT)」を使うのが正しい実装です。

40001(デッドロック)が頻発するときの対処法は?

デッドロックは複数のトランザクションが互いに相手のロックを待ち合う状態です。根本対処は「テーブルや行へのアクセス順を全トランザクションで統一する」ことです。たとえばテーブルAとテーブルBを更新するトランザクションが混在する場合、必ずA→Bの順でアクセスするよう統一します。アプリ側では40001を受け取ったら一定回数リトライするロジック(指数バックオフ推奨)を実装します。ORMでは `@Retryable` や `retryWhen` 相当の仕組みを使うと簡潔に書けます。

SQLStateとDBMS固有のエラーコード(例: MySQLの1062)の違いは?

SQLStateはANSI SQL標準で定義された5文字のコードで、DBMSをまたいで共通の意味を持ちます(23505は一意制約違反)。一方、MySQLの1062やPostgreSQLのエラーコードはDBMS独自の数値コードです。両者が対応していることが多く、MySQL 1062はSQLState 23000(または23S01)に相当します。アプリケーションの移植性を高めたい場合はSQLStateで分岐し、DBMS固有の詳細が必要な場合は両方を確認します。

PostgreSQLとMySQLでSQLStateは共通ですか?

クラスコード(先頭2文字)はANSI SQL標準に基づくため大半が共通です。ただしサブクラスの実装粒度は異なり、PostgreSQLは非常に細かいコードを定義しているのに対し、MySQLは標準外のコードを独自に追加しています。SQLiteはSQLStateを返さず、独自の整数エラーコードのみを使います。標準外のコード(例: `HV`〜`P0` など)は各DBMSが独自に定義するため、完全な互換性はないものと考えておくのが安全です。

接続エラー 08xxx が出たら何を確認すべきですか?

08000系は接続の問題を示します。確認順序は①ホスト名・ポート番号の誤りがないか、②データベースサーバーが起動しているか(`pg_isready -h ホスト名`)、③ファイアウォール・セキュリティグループで接続先ポートが開いているか、④PostgreSQLの場合は `pg_hba.conf` で接続元IPが許可されているか(08004が多い)、⑤接続数が `max_connections` に達していないか(53300で現れることも多い)の順です。エラー発生時刻のサーバーログと照合すると原因を絞り込めます。

53300(接続数超過)をpgbouncerで解決する方法は?

PgBouncer(ピージーバウンサー)はPostgreSQLの手前に置くコネクションプーリングサーバーです。アプリからPgBouncerへは数百〜数千の接続を受け付けつつ、PgBouncerからPostgreSQLへは数十本の実接続に束ねます。インストール後に `pgbouncer.ini` で `pool_size`(DB側への実接続数)と `max_client_conn`(アプリ側からの上限)を設定し、接続文字列をPostgreSQLのポート(5432)からPgBouncerのポート(6432が慣例)に変更するだけで導入できます。Heroku・Supabase等のマネージドサービスでは標準搭載されているケースが多いです。

42601(構文エラー)が出たときの効率的な調べ方は?

PostgreSQLのエラーメッセージには「syntax error at or near "xxx"」のようにエラー箇所のキーワードが含まれます。よくある原因は①カンマの過不足(SELECT末尾の余分なカンマ)、②予約語をカラム名に使っている("user"・"table"など)、③文字列クォートの不一致(シングルクォートとダブルクォートの混在)、④CTE(WITH句)や副クエリの閉じ括弧漏れです。複雑なクエリは `EXPLAIN` を先頭に付けてパースエラーだけを確認するか、クエリを小さく分割して再現箇所を特定するのが効率的です。

DBエラーをアプリのログで記録するときの注意点は?

SQLStateとエラーメッセージを必ずログに含めるようにします。ただし、エラーメッセージにはSQLの一部(パラメータ値など)が含まれることがあるため、個人情報・パスワード・クレジットカード番号が混入しないように注意が必要です。本番環境ではエラーの詳細をユーザーには返さず、ログにのみ出力するのが原則です。23505(一意制約)のようにユーザー操作で頻繁に発生するエラーは「WARNレベル」、40001(デッドロック)や08xxx(接続エラー)はリトライ後も継続する場合「ERRORレベル」で記録するのが一般的な分類です。

この記事で紹介したツール