- MySQL
MySQLユーザー作成と権限付与|GRANT・SHOW GRANTS
MySQLでユーザーに権限を与える手順とは、CREATE USER でアカウントを作り、GRANT で権限を付け、SHOW GRANTS で確認する3ステップです。 MySQL 8.0以降は GRANT だけでユーザーを作ることはできず、必ず CREATE USER を先に実行します。
この記事では、作成 → 付与 → 確認 → 取り消し → 変更 → 削除の流れを、そのまま貼って流せるSQLと、MySQL 8.0.46で実際に実行した結果つきでまとめました。ERROR 1410・1045・1142・3118 などのエラー全文の再現、localhost と % の違いが原因で起きるログイン失敗、ロールの「付けたのに効かない」問題、用途別の権限テンプレート(読み取り専用・アプリ用・mysqldump用・レプリケーション用)まで、1ページで判断できるように整理しています。
MySQLユーザー・権限操作の早見表
| やりたいこと | SQL | 確認方法 |
|---|---|---|
| ユーザーを作る | CREATE USER 'app'@'localhost' IDENTIFIED BY 'パスワード'; |
SELECT user, host FROM mysql.user; |
| 既にあってもエラーにしない | CREATE USER IF NOT EXISTS ... |
SHOW WARNINGS;(Note 3163) |
| DB全体に権限を付ける | GRANT SELECT, INSERT ON shop.* TO 'app'@'localhost'; |
SHOW GRANTS FOR 'app'@'localhost'; |
| テーブル単位で付ける | GRANT SELECT ON shop.orders TO ...; |
同上 |
| カラム単位で付ける | GRANT SELECT (id, item) ON shop.orders TO ...; |
同上 |
| 権限を取り消す | REVOKE DELETE ON shop.* FROM 'app'@'localhost'; |
同上 |
| 全権限を取り消す | REVOKE ALL PRIVILEGES, GRANT OPTION FROM ...; |
USAGE だけ残る |
| パスワードを変える | ALTER USER 'app'@'localhost' IDENTIFIED BY '新パスワード'; |
ログインで確認 |
| 自分のパスワードを変える | ALTER USER USER() IDENTIFIED BY '新パスワード'; |
同上 |
| パスワードを期限切れにする | ALTER USER ... PASSWORD EXPIRE; |
ERROR 1820 で変更を促される |
| 有効期限を設定する | ALTER USER ... PASSWORD EXPIRE INTERVAL 90 DAY; |
SHOW CREATE USER |
| アカウントを止める | ALTER USER ... ACCOUNT LOCK; |
ERROR 3118 |
| ユーザー名を変える | RENAME USER 'a'@'localhost' TO 'b'@'localhost'; |
mysql.user |
| ユーザーを消す | DROP USER 'app'@'localhost'; |
mysql.user |
| ロールを作って配る | CREATE ROLE 'r'; GRANT 'r' TO ...; SET DEFAULT ROLE ALL TO ...; |
SHOW GRANTS ... USING 'r' |
| 今の接続ユーザーを見る | SELECT USER(), CURRENT_USER(); |
一致しないときはホスト照合を確認 |
ポイントは「ユーザーは '名前'@'ホスト' の組で1つ」ということです。 'app'@'localhost' と 'app'@'%' は別アカウントで、パスワードも権限も別々に持ちます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「MySQLユーザー作成と権限付与|GRANT・SHOW GRANTS」
検証環境と対象バージョン
| 項目 | 内容 |
|---|---|
| MySQL | 8.0.46(Ubuntu パッケージ、--initialize-insecure で初期化) |
| 接続 | ソケット(-S)と TCP(-h127.0.0.1 -P7246)の両方 |
| 既定の認証プラグイン | caching_sha2_password |
skip_name_resolve |
0(既定) |
MySQL 8.0 は2026年4月にサポートが終了しています。 この記事のSQLは、長期サポート版の MySQL 8.4 LTS と 9.7 LTS でも同じ手順で使えます。違いが出るのは次の点です(公式ドキュメントで確認)。
| 項目 | 8.0 | 8.4 | 9.x(9.7含む) |
|---|---|---|---|
CREATE USER → GRANT の手順 |
同じ | 同じ | 同じ |
mysql_native_password |
使える(非推奨) | 既定で無効(起動オプションで有効化) | 削除済み |
default_authentication_plugin 変数 |
あり | 削除(authentication_policy を使う) |
削除 |
SUPER 権限 |
非推奨 | 非推奨 | 非推奨(動的権限へ移行) |
以下の実行結果は、すべて手元の MySQL 8.0.46 で実行したものです。
手順1:ユーザーを作って権限を付け、確認する
まずは一連の流れをコピペで流せる形で示します。root(または CREATE USER 権限を持つユーザー)で接続して実行します。
-- 練習用のデータベースとテーブル
CREATE DATABASE shop;
CREATE TABLE shop.orders (
id INT PRIMARY KEY AUTO_INCREMENT,
member_id INT,
item VARCHAR(50),
price INT,
note VARCHAR(100)
);
INSERT INTO shop.orders (member_id, item, price, note)
VALUES (1, 'notebook', 300, 'a'), (2, 'pen', 120, 'b');
-- 1. ユーザーを作る
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'Str0ng!Pass#2026';
SHOW GRANTS FOR 'app_user'@'localhost';
-- 2. 権限を付ける
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'app_user'@'localhost';
SHOW GRANTS FOR 'app_user'@'localhost';
-- 3. 一部を取り消す
REVOKE DELETE ON shop.* FROM 'app_user'@'localhost';
SHOW GRANTS FOR 'app_user'@'localhost';
実行結果(MySQL 8.0.46):
+----------------------------------------------+
| Grants for app_user@localhost |
+----------------------------------------------+
| GRANT USAGE ON *.* TO `app_user`@`localhost` |
+----------------------------------------------+
+----------------------------------------------------------------------------+
| Grants for app_user@localhost |
+----------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `app_user`@`localhost` |
| GRANT SELECT, INSERT, UPDATE, DELETE ON `shop`.* TO `app_user`@`localhost` |
+----------------------------------------------------------------------------+
+--------------------------------------------------------------------+
| Grants for app_user@localhost |
+--------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `app_user`@`localhost` |
| GRANT SELECT, INSERT, UPDATE ON `shop`.* TO `app_user`@`localhost` |
+--------------------------------------------------------------------+
作成直後の GRANT USAGE ON *.* は「ログインはできるが、何の権限もない」という意味です。 権限がゼロでもこの1行は必ず表示されます。また、MySQL 8.0 では GRANT / REVOKE / CREATE USER は権限テーブルへ即時反映されるため、FLUSH PRIVILEGES は不要です(mysql.user などを直接 UPDATE したときだけ必要)。
付与した権限が実際に効いているかは、作ったユーザーでログインして確かめます。
mysql -u app_user -p -S /tmp/mysql.sock -e "SELECT USER(), CURRENT_USER(); DELETE FROM shop.orders;"
+--------------------+--------------------+
| user() | current_user() |
+--------------------+--------------------+
| app_user@localhost | app_user@localhost |
+--------------------+--------------------+
ERROR 1142 (42000) at line 1: DELETE command denied to user 'app_user'@'localhost' for table 'orders'
REVOKE した DELETE は ERROR 1142 で拒否されました。 USER() は「ログイン時に名乗ったユーザーと接続元」、CURRENT_USER() は「実際に照合されたアカウント」です。この2つがずれているときは、後述のホスト照合を疑います。
手順2:変更・ロック・名前変更・削除
-- パスワード変更
ALTER USER 'app_user'@'localhost' IDENTIFIED BY 'N3w!Pass#2026';
-- アカウントの一時停止と再開
ALTER USER 'app_user'@'localhost' ACCOUNT LOCK;
ALTER USER 'app_user'@'localhost' ACCOUNT UNLOCK;
-- 名前を変える(権限も引き継がれる)
RENAME USER 'app_user'@'localhost' TO 'shop_app'@'localhost';
-- 全権限を外す(アカウントは残る)
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'shop_app'@'localhost';
SHOW GRANTS FOR 'shop_app'@'localhost';
-- 削除
DROP USER 'shop_app'@'localhost';
REVOKE ALL 後の SHOW GRANTS は GRANT USAGE ON *.* TOshop_app@localhost`` の1行だけになりました。
ロックしたアカウントでログインすると、次のエラーになります。
ERROR 3118 (HY000): Access denied for user 'col_user'@'localhost'. Account is locked.
退職・異動などで一時的に止めたいときは DROP USER より ACCOUNT LOCK が便利です。 権限設定を残したまま、戻すときは ACCOUNT UNLOCK だけで済みます。
よく出るエラーの再現と直し方
ERROR 1410:GRANTでユーザーを作ろうとした
MySQL 5.7 までの記事でよく見る「GRANT でユーザー作成と権限付与を同時に行う」書き方は、8.0 以降では使えません。
GRANT SELECT ON shop.* TO 'nobody'@'localhost';
ERROR 1410 (42000): You are not allowed to create a user with GRANT
IDENTIFIED BY を付けると、構文自体が受け付けられません。
GRANT SELECT ON shop.* TO 'nobody'@'localhost' IDENTIFIED BY 'x';
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'IDENTIFIED BY 'x'' at line 1
直し方:CREATE USER → GRANT の2文に分けます。
CREATE USER 'nobody'@'localhost' IDENTIFIED BY 'Nobody#2026';
GRANT SELECT ON shop.* TO 'nobody'@'localhost';
ERROR 1045:パスワード違い、またはホストの取り違え
ERROR 1045 (28000): Access denied for user 'app_user'@'localhost' (using password: YES)
1045 はパスワードが違うときだけでなく、「想定と違うホストのアカウントに照合された」ときにも出ます。 次は 'demo'@'%' と 'demo'@'localhost' を別パスワードで作った例です。
CREATE USER 'demo'@'%' IDENTIFIED BY 'PctPass#1';
CREATE USER 'demo'@'localhost' IDENTIFIED BY 'LocalPass#1';
| 接続方法 | 使ったパスワード | 結果(実行結果) |
|---|---|---|
| ソケット | % 側(PctPass#1) |
ERROR 1045 ... 'demo'@'localhost' |
| ソケット | localhost 側(LocalPass#1) |
demo@localhost で成功 |
TCP -h127.0.0.1 |
% 側(PctPass#1) |
ERROR 1045 ... 'demo'@'localhost' |
'demo'@'localhost' を DROP 後、ソケット |
% 側 |
demo@% で成功 |
MySQL は、ホスト指定がより具体的なアカウントを優先して照合します。 localhost からの接続には 'demo'@'localhost' が先に当たるので、% 側のパスワードでは通りません。また skip_name_resolve=0(既定)の環境では、127.0.0.1 からの TCP 接続も localhost として照合されました。
1045 の切り分けは次の順で確認すると早いです。
| 確認すること | SQL・コマンド |
|---|---|
| 同名ユーザーが複数ホストにないか | SELECT user, host FROM mysql.user WHERE user='demo'; |
| どのアカウントに当たったか | エラー文の 'ユーザー'@'ホスト' を読む |
| パスワードを付けて送ったか | エラー文の using password: YES/NO |
| ロック・期限切れでないか | SELECT account_locked, password_expired FROM mysql.user ...; |
接続まわりのエラー全般(2002・2003など)は、別記事「MySQLコマンド一覧」の接続エラー表とあわせて確認してください。
ERROR 1044・1142・1143:権限が足りない
| コード | 意味 | 実行結果のメッセージ |
|---|---|---|
| 1044 | DB単位の権限がない | Access denied for user 'app_user'@'localhost' to database 'mysql' |
| 1142 | テーブル単位の操作権限がない | CREATE command denied to user 'app_user'@'localhost' for table 't' |
| 1143 | カラム単位の権限がない | UPDATE command denied to user 'col_user'@'localhost' for column 'price' in table 'orders' |
直し方は、エラー文に出た操作(CREATE、UPDATEなど)とスコープ(DB・テーブル・カラム)をそのまま GRANT に書くことです。
ERROR 1396:同じユーザーを作った・ないユーザーを消した
ERROR 1396 (HY000): Operation CREATE USER failed for 'app_user'@'localhost'
ERROR 1396 (HY000): Operation DROP USER failed for 'ghost'@'localhost'
スクリプトで何度も流す場合は IF NOT EXISTS / IF EXISTS を付けます。既存ユーザーに CREATE USER IF NOT EXISTS を流すと、エラーではなく Note になります。
+-------+------+---------------------------------------------------------+
| Level | Code | Message |
+-------+------+---------------------------------------------------------+
| Note | 3163 | Authorization ID 'app_user'@'localhost' already exists. |
+-------+------+---------------------------------------------------------+
IF NOT EXISTS ではパスワードは上書きされません。 パスワードを確実にそろえたいときは、続けて ALTER USER ... IDENTIFIED BY を実行します。
権限の種類とスコープの一覧
GRANT の ON の書き方で、権限の効く範囲(スコープ)が決まります。
| スコープ | ON の書き方 | 保存先テーブル | 主な用途 |
|---|---|---|---|
| グローバル | ON *.* |
mysql.user |
管理者、バックアップ、レプリケーション |
| データベース | ON shop.* |
mysql.db |
アプリ用・読み取り専用 |
| テーブル | ON shop.orders |
mysql.tables_priv |
特定テーブルだけ公開 |
| カラム | SELECT (id, item) ON shop.orders |
mysql.columns_priv |
一部の列だけ見せる |
| ストアドルーチン | ON PROCEDURE shop.p1 |
mysql.procs_priv |
実行だけ許可 |
主な権限と、付けられるスコープは次のとおりです(MySQL 8.4 リファレンス「Privileges Provided by MySQL」で確認)。
| 権限 | できること | グローバル | DB | テーブル | カラム |
|---|---|---|---|---|---|
SELECT |
参照 | ○ | ○ | ○ | ○ |
INSERT |
追加 | ○ | ○ | ○ | ○ |
UPDATE |
更新 | ○ | ○ | ○ | ○ |
DELETE |
削除 | ○ | ○ | ○ | - |
CREATE / DROP / ALTER |
テーブル作成・削除・変更 | ○ | ○ | ○ | - |
INDEX |
インデックス作成・削除 | ○ | ○ | ○ | - |
REFERENCES |
外部キー作成 | ○ | ○ | ○ | ○ |
CREATE VIEW / SHOW VIEW |
ビュー作成・定義参照 | ○ | ○ | ○ | - |
TRIGGER |
トリガー操作 | ○ | ○ | ○ | - |
EXECUTE / CREATE ROUTINE / ALTER ROUTINE |
ストアド実行・作成・変更 | ○ | ○ | - | - |
EVENT |
イベント操作 | ○ | ○ | - | - |
LOCK TABLES |
明示的なテーブルロック | ○ | ○ | - | - |
CREATE TEMPORARY TABLES |
一時テーブル作成 | ○ | ○ | - | - |
PROCESS |
全スレッドの参照 | ○ | - | - | - |
RELOAD |
FLUSH 系の実行 | ○ | - | - | - |
REPLICATION SLAVE |
レプリカからの接続 | ○ | - | - | - |
REPLICATION CLIENT |
レプリケーション状態の参照 | ○ | - | - | - |
CREATE USER |
ユーザー作成・変更・削除 | ○ | - | - | - |
CREATE ROLE / DROP ROLE |
ロール作成・削除 | ○ | - | - | - |
FILE |
サーバー上のファイル読み書き | ○ | - | - | - |
GRANT OPTION |
自分の権限を他人に渡す | ○ | ○ | ○ | ○ |
ALL PRIVILEGES |
そのスコープの全権限(GRANT OPTION除く) | ○ | ○ | ○ | - |
8.0 からは SUPER を細かく分けた「動的権限」が増えています。 代表的なものは SYSTEM_VARIABLES_ADMIN(グローバル変数変更)、CONNECTION_ADMIN(接続制御・KILL)、BINLOG_ADMIN、REPLICATION_SLAVE_ADMIN、BACKUP_ADMIN、ROLE_ADMIN です。新しく管理者を作るときは SUPER ではなく必要な動的権限だけを付けると、8.4・9.x への移行もスムーズです。
カラム単位の権限:実行結果
CREATE USER 'col_user'@'localhost' IDENTIFIED BY 'ColUser#2026';
GRANT SELECT (id, item, price), UPDATE (note) ON shop.orders TO 'col_user'@'localhost';
col_user でログインして実行した結果:
mysql> SELECT id, item, price FROM shop.orders;
+----+----------+-------+
| id | item | price |
+----+----------+-------+
| 1 | notebook | 300 |
| 2 | pen | 120 |
+----+----------+-------+
mysql> SELECT * FROM shop.orders;
ERROR 1142 (42000): SELECT command denied to user 'col_user'@'localhost' for table 'orders'
mysql> UPDATE shop.orders SET price = 1 WHERE id = 1;
ERROR 1143 (42000): UPDATE command denied to user 'col_user'@'localhost' for column 'price' in table 'orders'
カラム権限を付けたテーブルでは SELECT * が使えなくなります。 列名を明示して取得します。列を絞る用途ではビューを作って SELECT を付ける方法も扱いやすく、どちらを使うかは「列の組み合わせが固定ならビュー、ユーザーごとに違うならカラム権限」が目安です。
用途別の権限テンプレート
| 用途 | ホスト | 付ける権限 | スコープ |
|---|---|---|---|
| 読み取り専用(集計・BI) | % または接続元のサブネット |
SELECT, SHOW VIEW |
shop.* |
| アプリケーション | アプリサーバーのIP | SELECT, INSERT, UPDATE, DELETE |
shop.* |
| マイグレーション | 作業端末 | 上記+CREATE, ALTER, DROP, INDEX, REFERENCES |
shop.* |
| バックアップ(mysqldump) | localhost |
SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT, PROCESS, RELOAD |
*.* |
| レプリケーション | レプリカのIP | REPLICATION SLAVE |
*.* |
-- 読み取り専用:有効期限90日、3回失敗で1日ロック
CREATE USER 'report_ro'@'%' IDENTIFIED BY 'ReportRo#2026'
PASSWORD EXPIRE INTERVAL 90 DAY
FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 1;
GRANT SELECT, SHOW VIEW ON shop.* TO 'report_ro'@'%';
-- アプリケーション用(接続元を限定)
CREATE USER 'shop_app'@'10.0.1.%' IDENTIFIED BY 'ShopApp#2026';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'10.0.1.%';
-- バックアップ用(mysqldump)
CREATE USER 'backup'@'localhost' IDENTIFIED BY 'Backup#2026';
GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT, PROCESS, RELOAD
ON *.* TO 'backup'@'localhost';
-- レプリケーション用(暗号化された接続を必須に)
CREATE USER 'repl'@'%' IDENTIFIED BY 'Repl#2026' REQUIRE SSL;
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
mysqldump に必要な権限を確かめた結果
SELECT だけを付けたユーザーで mysqldump を実行すると、次の2つのエラーが出ました。
mysqldump: Error: 'Access denied; you need (at least one of) the PROCESS privilege(s) for this operation' when trying to dump tablespaces
mysqldump: Got error: 1044: Access denied for user 'bk2'@'localhost' to database 'shop' when using LOCK TABLES
同じユーザーでも --single-transaction --no-tablespaces を付けると終了コード 0 で完了しました。公式ドキュメントの説明どおり、--single-transaction を使えば LOCK TABLES は不要、--no-tablespaces を使えば PROCESS は不要です。上のテンプレート(backup ユーザー)では --single-transaction --routines --events で正常に取得できました。オプションの詳細は別記事「mysqldumpバックアップとリストア」を参照してください。
パスワードの失敗回数ロック:実行結果
report_ro に3回続けて違うパスワードを送った結果です。
ERROR 1045 (28000): Access denied for user 'report_ro'@'localhost' (using password: YES)
ERROR 1045 (28000): Access denied for user 'report_ro'@'localhost' (using password: YES)
ERROR 3955 (HY000): Access denied for user 'report_ro'@'localhost'. Account is blocked for 1 day(s) (1 day(s) remaining) due to 3 consecutive failed logins.
ロック中は正しいパスワードでも 3955 になります。 管理者が ALTER USER 'report_ro'@'%' ACCOUNT UNLOCK; を実行すると、すぐにログインできるようになりました。
localhost と % の違い
| ホスト指定 | 照合される接続 | 向いている用途 |
|---|---|---|
'localhost' |
同じサーバー上からのソケット接続(名前解決が有効なら 127.0.0.1 も) | 管理者、バックアップ |
'127.0.0.1' |
IPv4 ループバックへの TCP 接続 | コンテナ内からの TCP 接続 |
'10.0.1.%' |
10.0.1.0/24 からの接続 | アプリサーバー群 |
'%' |
どこからでも | 開発環境、接続元が変わる用途 |
% は便利ですが、同じユーザー名の localhost アカウントがあると、そちらが優先されます。 先ほどの検証のとおり、'demo'@'localhost' を消すと、ソケット接続でも demo@% に照合されました。本番のアプリ用は接続元のIP範囲を書くと、意図しない場所からの接続を防げます。
caching_sha2_password と接続エラー2061
MySQL 8.0 以降の既定の認証方式は caching_sha2_password です。暗号化されていない TCP 接続で、まだキャッシュされていないユーザーがログインすると、ERROR 2061 になります。
mysql -u demo -p -h 127.0.0.1 -P 7246 --ssl-mode=DISABLED
ERROR 2061 (HY000): Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection.
| 対処 | 内容 |
|---|---|
| TLSで接続する(推奨) | --ssl-mode を既定(PREFERRED)のままにする。今回の環境では TLS_AES_256_GCM_SHA384 で接続 |
| 公開鍵を取得する | --get-server-public-key を付ける。今回の環境ではこれで成功 |
| ソケット接続にする | 同一サーバー内なら -S で接続 |
| ドライバを更新する | 古いクライアントライブラリは caching_sha2_password に未対応のことがある |
一度ログインに成功するとサーバー側にキャッシュされ、同じ条件でも次回からは通りました(ALTER USER でパスワードを変えるとキャッシュは消えます)。8.4 では mysql_native_password が既定で無効、9.x では削除されているため、古い認証方式に戻すより、クライアント側を caching_sha2_password 対応にするのが長く使える方法です。
ロールでまとめて権限を管理する
同じ権限を何人にも配るときは、ロール(権限の束)を使います。
CREATE ROLE 'shop_read', 'shop_write';
GRANT SELECT ON shop.* TO 'shop_read';
GRANT INSERT, UPDATE, DELETE ON shop.* TO 'shop_write';
CREATE USER 'hanako'@'localhost' IDENTIFIED BY 'Hanako#2026';
GRANT 'shop_read', 'shop_write' TO 'hanako'@'localhost';
ここで hanako としてログインすると、ロールを付けたのに SELECT が拒否されます。
+----------------+
| CURRENT_ROLE() |
+----------------+
| NONE |
+----------------+
ERROR 1142 (42000): SELECT command denied to user 'hanako'@'localhost' for table 'orders'
ロールは「付与」と「有効化」が別だからです。既定ロールを設定すると、ログイン時に自動で有効になります。
SET DEFAULT ROLE ALL TO 'hanako'@'localhost';
+----------------------------------+
| CURRENT_ROLE() |
+----------------------------------+
| `shop_read`@`%`,`shop_write`@`%` |
+----------------------------------+
+----------+
| count(*) |
+----------+
| 2 |
+----------+
| 方法 | 効く範囲 | 使いどころ |
|---|---|---|
SET DEFAULT ROLE ALL TO ユーザー |
そのユーザーの次回以降のログイン | 通常はこれ |
SET ROLE ALL;(ログイン後に実行) |
今のセッションだけ | 一時的に切り替える |
activate_all_roles_on_login=ON |
サーバー全体 | 全員に自動で有効化したい |
mandatory_roles |
全ユーザーに強制付与 | 共通の参照権限など |
ロールが持つ権限まで展開して確認するには USING を使います。
SHOW GRANTS FOR 'hanako'@'localhost' USING 'shop_read', 'shop_write';
| GRANT USAGE ON *.* TO `hanako`@`localhost` |
| GRANT SELECT, INSERT, UPDATE, DELETE ON `shop`.* TO `hanako`@`localhost` |
| GRANT `shop_read`@`%`,`shop_write`@`%` TO `hanako`@`localhost` |
存在しないロールを付けようとすると ERROR 3523 (HY000): Unknown authorization ID になります。なお、ロールは mysql.user に「ロック済み・パスワード期限切れ」のアカウントとして保存されるため、ロール名でログインすることはできません。PostgreSQL のロール設計と比べたい場合は、別記事「PostgreSQLのロールと権限」も参考になります。
ユーザー一覧と権限を確認するSQL
-- ユーザー一覧(システムアカウントを除く)
SELECT user, host, plugin,
account_locked AS locked, password_expired AS expired,
password_last_changed
FROM mysql.user
WHERE user NOT LIKE 'mysql.%'
ORDER BY user, host;
実行結果(抜粋):
+------------+-----------+-----------------------+--------+---------+-----------------------+
| user | host | plugin | locked | expired | password_last_changed |
+------------+-----------+-----------------------+--------+---------+-----------------------+
| hanako | localhost | caching_sha2_password | N | N | 2026-10-02 00:56:42 |
| shop_read | % | caching_sha2_password | Y | Y | 2026-10-02 00:56:42 |
| shop_write | % | caching_sha2_password | Y | Y | 2026-10-02 00:56:42 |
+------------+-----------+-----------------------+--------+---------+-----------------------+
| 確認したいこと | SQL |
|---|---|
| あるユーザーの権限 | SHOW GRANTS FOR 'ユーザー'@'ホスト'; |
| 自分の権限 | SHOW GRANTS; |
| 作成時の設定(期限・ロック等) | SHOW CREATE USER 'ユーザー'@'ホスト'; |
| DB単位の権限を一覧 | SELECT * FROM information_schema.SCHEMA_PRIVILEGES; |
| テーブル単位の権限を一覧 | SELECT * FROM information_schema.TABLE_PRIVILEGES; |
| カラム単位の権限を一覧 | SELECT * FROM information_schema.COLUMN_PRIVILEGES; |
| ロールの付与関係 | SELECT * FROM mysql.role_edges; |
| 既定ロール | SELECT * FROM mysql.default_roles; |
| 使える権限の一覧 | SHOW PRIVILEGES; |
information_schema の各 PRIVILEGES ビューは権限を1行ずつに分けて返すので、スクリプトでの監査に向いています。
パスワードの有効期限と変更の強制
-- 次回ログイン時に変更させる
ALTER USER 'app_user'@'localhost' PASSWORD EXPIRE;
-- 90日ごとに期限切れ
ALTER USER 'app_user'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;
-- 期限なし
ALTER USER 'app_user'@'localhost' PASSWORD EXPIRE NEVER;
期限切れのユーザーが mysql -e で接続すると、次のように表示されて止まります。
Please use --connect-expired-password option or invoke mysql in interactive mode.
--connect-expired-password で接続すると、パスワード変更以外の文は ERROR 1820 になります。
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
-- 本人がパスワードを変える
ALTER USER USER() IDENTIFIED BY 'N3w!Pass#2026';
アプリ用アカウントに有効期限を付けると、期限が来た時点でアプリの接続が止まります。 アプリ用は PASSWORD EXPIRE NEVER にして別の手順でローテーションし、人が使うアカウントに期限を付ける、という分け方が扱いやすいです。
よくある質問
Q. GRANTの後に FLUSH PRIVILEGES は必要ですか?
A. GRANT / REVOKE / CREATE USER / ALTER USER を使った場合は不要です。権限テーブルを UPDATE などで直接書き換えたときだけ必要になります。
Q. ユーザー作成時に IDENTIFIED BY を書かないとどうなりますか?
A. パスワードなしのアカウントになり、ユーザー名だけでログインできる状態になります。作成時は必ず IDENTIFIED BY でパスワードを指定し、既存のアカウントは SELECT user, host FROM mysql.user WHERE authentication_string = ''; で確認できます。
Q. 'user'@'%' で作ったのに、ローカルからログインできません。
A. 同じユーザー名で localhost のアカウント(匿名ユーザー ''@'localhost' を含む)があると、そちらが優先されます。SELECT user, host FROM mysql.user; で同名・匿名のアカウントがないか確認してください。
Q. MySQL 8.4 や 9.7 でもこの手順で使えますか?
A. 使えます。CREATE USER・GRANT・SHOW GRANTS・ロールの書き方は同じです。mysql_native_password を指定しているアカウントは、8.4 では既定で無効、9.x では使えないため、caching_sha2_password へ移行してから更新してください。
まとめ
- MySQL 8.0以降は
CREATE USER→GRANTの2段階。GRANTだけでは ERROR 1410 - アカウントは
'名前'@'ホスト'の組。localhostと%は別アカウントで、具体的な方が優先される - 権限は
*.*・db.*・db.table・カラムの4スコープ。必要な範囲だけ付ける - 確認は
SHOW GRANTS、ロール込みならUSING、監査はinformation_schemaの各PRIVILEGESビュー - ロールは付与後に
SET DEFAULT ROLEで有効化。付けただけでは ERROR 1142 - mysqldump用は
--single-transaction --no-tablespacesなら権限を絞れる - 8.4・9.7でも同じ手順で使える。認証は
caching_sha2_password対応のクライアントで
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「MySQLユーザー作成と権限付与|GRANT・SHOW GRANTS」
株式会社RJCでは、MySQLのユーザー設計と権限管理を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。