PostgreSQL | SQLite+MySQL経験者向け、30日で習得するPostgreSQL:差分理解 - Day4 シーケンス

SQL PostgreSQL
スポンサーリンク

Day4 後半のゴール

「“連番まわりでハマらない”ための実践イメージを持つ」

前半で、SERIAL / BIGSERIAL の正体が「整数+シーケンス+デフォルト値のセット」だと分かりました。
後半では、もう一歩踏み込んで「実務でどう使うか」「どこでハマりやすいか」「MySQLから移行するとき何に気をつけるか」を具体例で固めていきます。

ここでのゴールはこうです。
SERIAL カラムに対して、INSERT・DELETE・リセットがどう効くかイメージできる。
シーケンスの「値が飛ぶ」「戻さない」という性質を、怖がらずに理解できる。
MySQLの AUTO_INCREMENT からPostgreSQLに移すときの、設計とマインドの差分を説明できる。


SERIALカラムに対するINSERTと“値が飛ぶ”話

「削除しても番号は戻さない、それでいい」

まずは、一番よくある「idが飛ぶ問題」から整理します。

次のようなテーブルを考えます。

CREATE TABLE users (
  id   SERIAL PRIMARY KEY,
  name TEXT NOT NULL
);
SQL

ここに、データを3件入れます。

INSERT INTO users (name) VALUES ('Taro');  -- id = 1
INSERT INTO users (name) VALUES ('Jiro');  -- id = 2
INSERT INTO users (name) VALUES ('Hanako');-- id = 3
SQL

ここまでは素直な連番です。
次に、id=2 を削除します。

DELETE FROM users WHERE id = 2;
SQL

この状態で、もう1件INSERTします。

INSERT INTO users (name) VALUES ('Saburo'); -- id はいくつ?
SQL

このとき、id は 4 になります。
「2が空いているから2を再利用しよう」とはなりません。

これはMySQLの AUTO_INCREMENT と同じで、
「連番は“ユニークな識別子”であって、“隙間なく連続した番号”ではない」という考え方です。

ここで大事なのは、「番号が飛ぶのは正常」「戻そうとしない」というマインドです。
id は「人間が数えて気持ちよくなるための番号」ではなく、「機械が一意に識別するためのキー」です。


シーケンスを直接触るときのイメージ

「nextval / currval / setval の役割」

普段は SERIAL に任せておけばOKですが、
シーケンスを直接触りたくなる場面もあります。

代表的な関数は3つです。

nextval('seq'):次の値を取り出し、シーケンスを進める
currval('seq'):そのセッションで最後に nextval した値を返す
setval('seq', n):シーケンスの現在値を n に設定する

例えば、users_id_seq というシーケンスがあるとします。

SELECT nextval('users_id_seq'); -- 1
SELECT nextval('users_id_seq'); -- 2
SELECT currval('users_id_seq'); -- 2
SQL

currval は、「この接続の中で最後に nextval した値」を返すので、
「さっきINSERTした行のidを知りたい」ときに使えます。

ただし、実務では RETURNING を使う方がスマートです。

INSERT INTO users (name) VALUES ('Taro') RETURNING id;
SQL

これで、「INSERTした行のid」を一発で取れます。
シーケンスを意識せずに済むので、アプリコードからは RETURNING を使うのが定番です。


シーケンスとトランザクションの関係

「ロールバックしても番号は戻らない、を理解しておく」

PostgreSQL初心者がよく驚くポイントが、「トランザクションをロールバックしてもシーケンスは戻らない」という挙動です。

例えば、こういう流れを考えます。

BEGIN;

INSERT INTO users (name) VALUES ('Taro'); -- シーケンスは 1 を発行
-- 何かエラーが起きたとする
ROLLBACK;
SQL

このとき、INSERTはロールバックされるので、テーブルには行が残りません。
しかし、シーケンスは「1を発行した」という事実を覚えています。

次に、改めてINSERTすると、

INSERT INTO users (name) VALUES ('Jiro'); -- id = 2
SQL

id は 1 ではなく 2 になります。
「ロールバックしたから1に戻るだろう」とはならないのがポイントです。

これは、「シーケンスはトランザクションとは独立して動く」という設計です。
理由はシンプルで、「複数のトランザクションから同時に nextval される世界」で、
ロールバックのたびに番号を戻していたら、整合性が崩れるからです。

ここでの大事な理解は、「idが飛ぶのは正常」「ロールバックしても番号は戻らない」「それを前提に設計する」ということです。


AUTO_INCREMENTからSERIALに移行するときの“考え方の差分”

「“カラムの属性”から“外部の番号発生器”へ」

MySQLからPostgreSQLに移るとき、よくある変換はこうです。

MySQL:

CREATE TABLE users (
  id   INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL
);
SQL

PostgreSQL:

CREATE TABLE users (
  id   SERIAL PRIMARY KEY,
  name VARCHAR(50) NOT NULL
);
SQL

見た目はほぼ同じですが、頭の中のモデルは少し変えた方がいいです。

MySQLでは、「idカラムが勝手に増える」という感覚でした。
PostgreSQLでは、「idカラムは“シーケンスから値をもらっているだけ”」という感覚です。

この違いを意識すると、次のような場面で迷いにくくなります。

テーブルをDROPしたとき、シーケンスはどうなるか。
テーブルをバックアップ・リストアしたとき、シーケンスの値はどう扱われるか。

SERIAL で作った場合は、テーブルとシーケンスの紐付けがされるので、
DROP TABLE時にシーケンスも一緒に消えます。
逆に、手動で作ったシーケンスは、自分で管理する必要があります。

「連番の本体はシーケンス」という意識を持っておくと、
こうした挙動も「まあそうだよね」と納得しやすくなります。


これからの“より標準的な書き方”への布石

「SERIALからIDENTITYへの橋渡しとして理解しておく」

少し先の話ですが、PostgreSQLには GENERATED ... AS IDENTITY という、
より標準SQL寄りの自動採番の書き方もあります。

例えば、こうです。

CREATE TABLE users (
  id   INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  name TEXT NOT NULL
);
SQL

内部的にはやはりシーケンスを使いますが、
「SERIALはPostgreSQL方言」「IDENTITYは標準寄り」という位置づけです。

今は SERIAL を使っておけば十分ですが、
「どのみち中身はシーケンスなんだ」という理解を持っておくと、
将来IDENTITYに移行するときもスムーズです。

Day4では、「PostgreSQLの連番=シーケンス」という軸さえしっかり持てればOKです。


Day4 後半のまとめ

SERIAL カラムは、INSERTのたびに裏で紐づいたシーケンスから nextval で番号をもらっているだけなので、行をDELETEしても番号は詰め直されず、トランザクションをロールバックしても「発行済みの番号」は戻らず、idが飛ぶのは正常な挙動として受け入れる必要がある。
シーケンスは nextval(次の値を発行)、currval(そのセッションで最後に発行した値)、setval(現在値の調整)といった関数で直接触れる独立オブジェクトであり、実務では INSERT ... RETURNING id を使うことで、シーケンスを意識せずに「今INSERTした行のid」を安全に取得できる。
MySQLの AUTO_INCREMENT が「カラムに埋め込まれたカウンタ」という感覚なのに対し、PostgreSQLは「外部の番号発生器=シーケンスをカラムが利用している」というモデルで設計されており、この違いを理解しておくことで、削除・ロールバック・バックアップ/リストア時の挙動や、将来的に GENERATED ... AS IDENTITY のような標準SQL寄りの自動採番に進むときにも迷わずに済む。

タイトルとURLをコピーしました