2026年5月28日
2026年5月28日
MySQLのINSERT・UPDATE・DELETE文の使い方 WordPressのデータを安全に書き換える手順
INSERT は行を追加し、UPDATE は既存の行を書き換え、DELETE は行を削除する文です。書き方そのものは短いのですが、WHERE を1つ書き忘れるだけでテーブル全体が書き換わり、元に戻す手段はバックアップしかありません。この記事では3つの文の基本形に加えて、「実行前に SELECT で対象を確かめる」「トランザクションで取り消せる状態にする」という安全な手順と、WordPress のデータを直接書き換えるときに壊しやすい点(シリアライズされた値、キャッシュ、関連テーブル)を説明します。
作業の前に必要なこと
書き換え系の文は、実行した瞬間に反映されます。作業の前に次の3つをそろえてください。
- データベースのバックアップ。WP-CLI なら
wp db export backup.sql、mysqldumpなら InnoDB のテーブルを止めずに取れる--single-transactionを付けます - 試す場所。可能ならステージング環境か、本番のコピーで同じ操作を先に実行します
- テーブル名の確認。接頭辞は
wp_とは限りません。wp db prefixかwp-config.phpの$table_prefixで確かめます
この記事の例は MySQL 8.0 以降と MariaDB 10.x 以降の InnoDB テーブルを想定しています(2026年10月現在)。
INSERT:行を追加する
INSERT INTO wp_options (option_name, option_value, autoload)
VALUES ('myplugin_version', '1.2.0', 'off');
列名を明示し、同じ順番で値を並べます。列名を省略してすべての列の値を並べる書き方もできますが、テーブルに列が追加されたときに壊れるため、列名は書いておくほうが安全です。autoload に入る値は WordPress のバージョンで変わってきた経緯があり(6.6 以降は on・off・auto などが使われます)、オプションは本来 add_option() や update_option() で扱う前提のデータです。SQL で直接入れる例は、仕組みを理解するためのものと考えてください。
複数行はカンマでつなげて1文で入れられます。1行ずつ実行するより通信と処理の回数が減ります。
INSERT INTO wp_myplugin_logs (user_id, action, created_at)
VALUES (1, 'login', NOW()),
(2, 'login', NOW()),
(1, 'logout', NOW());
主キーや UNIQUE 制約と重複すると Duplicate entry のエラーで止まります。「無ければ追加、あれば更新」をしたい場合は INSERT ... ON DUPLICATE KEY UPDATE を使います。INSERT IGNORE も重複を無視できますが、ほかの警告まで見えにくくなるため、意図がはっきりしている場面に限って使います。
自動採番(AUTO_INCREMENT)の列は値を指定せずに入れ、直後に SELECT LAST_INSERT_ID(); で採番された値を取得できます。
UPDATE:既存の行を書き換える
UPDATE wp_posts
SET comment_status = 'closed'
WHERE post_type = 'post'
AND post_status = 'publish'
AND post_date < '2024-01-01';
2024年より前の公開済み投稿のコメント受け付けを閉じる例です。SET に書き換える列と値、WHERE に対象の行の条件を書きます。WHERE が無いと全行が対象になります。
ほかの列の値を使った計算もできます。SET menu_order = menu_order + 10 のように書けば、現在の値に10を足します。
実行前に同じ WHERE で SELECT する
UPDATE を書いたら、すぐに実行せず、WHERE をそのまま使った SELECT で対象を確かめます。
SELECT COUNT(*) FROM wp_posts
WHERE post_type = 'post'
AND post_status = 'publish'
AND post_date < '2024-01-01';
件数が想定と大きく違えば条件の誤りです。件数だけでなく、SELECT ID, post_title ... LIMIT 20 で中身を何件か目で確かめておくと、思い込みによるミスを減らせます。
DELETE:行を削除する
DELETE FROM wp_myplugin_logs
WHERE created_at < NOW() - INTERVAL 90 DAY;
90日より古いログを消す例です。UPDATE と同様、先に同じ WHERE で SELECT して件数を確かめます。大量の行を一度に消すとロックが長引き、サイトの応答が遅くなることがあります。LIMIT 1000 を付けて何回かに分けて実行すると、負荷を分散できます。
テーブルを空にする TRUNCATE TABLE は、DELETE とは性質が異なります。行を1件ずつ消すのではなくテーブルを作り直す操作で、トランザクションで取り消せず、AUTO_INCREMENT の値も初期化されます。WordPress のコアテーブルに対して使う場面はほとんどありません。
トランザクションで取り消せる状態にする
InnoDB では、START TRANSACTION から COMMIT までの変更をまとめて確定でき、途中で ROLLBACK すれば取り消せます。
START TRANSACTION;
UPDATE wp_posts
SET post_status = 'draft'
WHERE post_type = 'post' AND post_author = 5;
-- 影響した行数と結果を確かめる
SELECT ROW_COUNT();
SELECT ID, post_status FROM wp_posts WHERE post_author = 5 LIMIT 20;
-- 想定どおりなら確定、違えば取り消す
COMMIT;
-- ROLLBACK;
注意点が3つあります。1つ目は、トランザクション中は対象の行がロックされるため、確認に時間をかけすぎると、その行を更新しようとするサイト側の処理が待たされることです。2つ目は、ALTER TABLE や TRUNCATE などの DDL は、その時点で暗黙に確定(コミット)されることです。3つ目は、MyISAM のテーブルはトランザクションに対応していないことです。古いサイトでは一部のテーブルが MyISAM のままの場合があるため、SHOW TABLE STATUS の Engine 列で確かめておきます。
mysql コマンドで作業するなら、--safe-updates オプション(または SET SQL_SAFE_UPDATES = 1;)を使うと、キー列を使った WHERE も LIMIT も無い UPDATE と DELETE を拒否してくれます。書き忘れの事故を防ぐ網として役立ちます。
WordPress のデータを直接書き換えるときの落とし穴
シリアライズされた値を REPLACE で置換しない
ドメイン変更のときに UPDATE wp_options SET option_value = REPLACE(option_value, '旧ドメイン', '新ドメイン') のような SQL を見かけますが、これは避けてください。ウィジェットやテーマの設定などは PHP のシリアライズ形式で保存されており、s:20:"..." のように文字数を含んでいます。文字列の長さが変わると文字数と合わなくなり、設定が読めずに初期化されたように見えることがあります。URL の置換は、シリアライズを解釈して書き換える wp search-replace(まず --dry-run で件数を確認)を使います。
関連テーブルに残骸が残る
投稿を DELETE FROM wp_posts WHERE ... で消すと、wp_postmeta のカスタムフィールド、wp_term_relationships のカテゴリーの関連付け、コメントなどが残ります。カテゴリーの件数表示もずれます。投稿やコメントの削除は、関連データも合わせて処理する WordPress の関数や WP-CLI に任せるのが安全です。
# 例: リビジョンを関連データごと削除(実行前にバックアップ)
wp post list --post_type=revision --format=ids
wp post delete $(wp post list --post_type=revision --format=ids) --force
キャッシュが古いまま残る
オブジェクトキャッシュ(Redis や Memcached)を使っているサイトでは、SQL で書き換えても WordPress はキャッシュの古い値を返し続けることがあります。直接書き換えた後は wp cache flush でキャッシュを空にするか、PHP から clean_post_cache() などで対象を消します。ページキャッシュのプラグインを使っている場合はそちらも削除します。
PHP から書き換えるなら $wpdb の専用メソッド
自作プラグインのテーブルを書き換えるときは、$wpdb->insert()、$wpdb->update()、$wpdb->delete() を使うと、値のエスケープを WordPress に任せられます。
global $wpdb;
$table = $wpdb->prefix . 'myplugin_logs';
// 追加: 値と、各値の型(%d=整数, %s=文字列)
$wpdb->insert(
$table,
array( 'user_id' => get_current_user_id(), 'action' => 'export' ),
array( '%d', '%s' )
);
$new_id = $wpdb->insert_id;
// 更新: 第3引数が WHERE 条件
$wpdb->update(
$table,
array( 'action' => 'export_done' ),
array( 'id' => $new_id ),
array( '%s' ),
array( '%d' )
);
// 削除
$deleted = $wpdb->delete( $table, array( 'id' => $new_id ), array( '%d' ) );
if ( false === $deleted ) {
error_log( 'myplugin: delete failed: ' . $wpdb->last_error );
}
戻り値は影響した行数で、失敗すると false です。0 件(該当なし)と失敗を区別するため、false === $result で判定します。管理画面のボタンなどから実行する処理では、nonce の検証(check_admin_referer() など)と current_user_can() による権限チェックを先に行います。投稿・ユーザー・オプションなどコアのデータは、wp_insert_post()、update_post_meta()、update_option() などの API で書き換えるのが基本です。
まとめ
- INSERT は列名を明示する。重複時の挙動は ON DUPLICATE KEY UPDATE などで明示する
- UPDATE と DELETE は、同じ WHERE の SELECT で件数と中身を確かめてから実行する
- バックアップを取り、トランザクションで ROLLBACK できる状態で作業する。DDL は暗黙に確定される
--safe-updatesで WHERE の書き忘れを防ぐ。大量削除は LIMIT で分割する- シリアライズされた値は REPLACE ではなく
wp search-replaceで置換する - 投稿やコメントの削除、コアデータの更新は WordPress の API や WP-CLI に任せ、直接書き換えた後はキャッシュを消す