Skip to content

MySQL Tutorial Users, Roles and Security

Roles: Groups of Privileges

Lesson 251 of 308 3 min read By Deepak Paswan Updated Oct 5, 2026

Giving 20 developers the same ten privileges one by one is tedious and error-prone. A role is a named set of privileges. You grant privileges to the role once, and grant the role to users. MySQL 8.0 added roles.

query.sql
SQL
CREATE DATABASE shop;
CREATE TABLE shop.customers (id INT PRIMARY KEY, name VARCHAR(20));
INSERT INTO shop.customers VALUES (1, 'Maya');

CREATE ROLE 'r_read', 'r_write';
GRANT SELECT ON shop.* TO 'r_read';
GRANT INSERT, UPDATE, DELETE ON shop.* TO 'r_write';

CREATE USER 'dev1'@'localhost' IDENTIFIED BY 'D3v!one#Pass';
GRANT 'r_read', 'r_write' TO 'dev1'@'localhost';

SHOW GRANTS FOR 'dev1'@'localhost';
Output
OUTPUT
+--------------------------------------------------------+
| Grants for dev1@localhost                              |
+--------------------------------------------------------+
| GRANT USAGE ON *.* TO `dev1`@`localhost`               |
| GRANT `r_read`@`%`,`r_write`@`%` TO `dev1`@`localhost` |
+--------------------------------------------------------+
2 rows in set

Granting the roles is not the same as having them active. The user’s grants list shows the role grants, and the privileges of the roles show only with USING:

query.sql
SQL
SHOW GRANTS FOR 'dev1'@'localhost' USING 'r_read';
Output
OUTPUT
+--------------------------------------------------------+
| Grants for dev1@localhost                              |
+--------------------------------------------------------+
| GRANT USAGE ON *.* TO `dev1`@`localhost`               |
| GRANT SELECT ON `shop`.* TO `dev1`@`localhost`         |
| GRANT `r_read`@`%`,`r_write`@`%` TO `dev1`@`localhost` |
+--------------------------------------------------------+
3 rows in set

A role is itself an account (a locked one, with host %), and GRANT role TO user records an edge in mysql.role_edges.

Roles must be activated

A role grant does nothing until the role is active in the session. By default, no role is active at login. This is what a real connection as dev1 shows (on MySQL 8.4):

Output
OUTPUT
SELECT CURRENT_ROLE() AS active_roles;     -> NONE
SELECT * FROM shop.customers;              -> ERROR 1142 (42000): SELECT command denied to user 'dev1'@'localhost' for table 'customers'

The user activates roles with SET ROLE:

Output
OUTPUT
SET ROLE 'r_read';
SELECT CURRENT_ROLE();                      -> `r_read`@`%`
SELECT * FROM shop.customers;               -> 1 row
INSERT INTO shop.customers VALUES (2,'Ben');-> ERROR 1142 (42000): INSERT command denied to user 'dev1'@'localhost' for table 'customers'

SET ROLE ALL;
SELECT CURRENT_ROLE();                      -> `r_read`@`%`,`r_write`@`%`
INSERT INTO shop.customers VALUES (2,'Ben');-> works

Forms of SET ROLE:

StatementEffect
SET ROLE 'r_read'Only this role
SET ROLE ALLAll granted roles
SET ROLE ALL EXCEPT 'r_write'All but some
SET ROLE DEFAULTThe user’s default roles
SET ROLE NONENo roles

Default roles

To have roles active automatically at login, set default roles:

query.sql
SQL
SET DEFAULT ROLE 'r_read' TO 'dev1'@'localhost';
SELECT default_role_user FROM mysql.default_roles WHERE user = 'dev1';
Output
OUTPUT
+-------------------+
| default_role_user |
+-------------------+
| r_read            |
+-------------------+
1 row in set

Now dev1 logs in with r_read already active (confirmed by a real session: CURRENT_ROLE() shows ` r_read@% `, and SELECT COUNT(*) works). SET DEFAULT ROLE ALL TO user` makes all granted roles default.

Two server settings change the behaviour:

query.sql
SQL
SELECT @@activate_all_roles_on_login AS activate_all_on_login, @@mandatory_roles AS mandatory_roles;
Output
OUTPUT
+-----------------------+-----------------+
| activate_all_on_login | mandatory_roles |
+-----------------------+-----------------+
|                     0 |                 |
+-----------------------+-----------------+
1 row in set
  • activate_all_roles_on_login = ON activates every granted role at login (convenient, less explicit). Default: OFF.
  • mandatory_roles lists roles that every account receives automatically (for example a read-only baseline).

Role design

A small, clear set of roles beats many one-off grants:

RolePrivileges
app_rwSELECT, INSERT, UPDATE, DELETE on the application database
app_roSELECT on the application database
app_migrateCREATE, ALTER, DROP, INDEX, REFERENCES on the application database
dba_readonlySELECT on all, plus PROCESS and SHOW VIEW

Roles can also be granted to other roles (a role hierarchy):

query.sql
SQL
CREATE ROLE 'r_support';
GRANT 'r_read' TO 'r_support';
GRANT SELECT ON mysql.help_topic TO 'r_support';
SHOW GRANTS FOR 'r_support' USING 'r_read';
Output
OUTPUT
+---------------------------------------------------------+
| Grants for r_support@%                                  |
+---------------------------------------------------------+
| GRANT USAGE ON *.* TO `r_support`@`%`                   |
| GRANT SELECT ON `shop`.* TO `r_support`@`%`             |
| GRANT SELECT ON `mysql`.`help_topic` TO `r_support`@`%` |
| GRANT `r_read`@`%` TO `r_support`@`%`                   |
+---------------------------------------------------------+
4 rows in set

Changing a role

When you GRANT or REVOKE privileges on the role, every user who has the role gets the change at their next role activation (new session or SET ROLE). That is the main benefit: update the role once, and everybody follows.

Drop

query.sql
SQL
DROP ROLE 'r_support';
REVOKE 'r_write' FROM 'dev1'@'localhost';
SHOW GRANTS FOR 'dev1'@'localhost';
Output
OUTPUT
+------------------------------------------+
| Grants for dev1@localhost                |
+------------------------------------------+
| GRANT USAGE ON *.* TO `dev1`@`localhost` |
| GRANT `r_read`@`%` TO `dev1`@`localhost` |
+------------------------------------------+
2 rows in set

Roles and applications

For application accounts, privileges granted directly are simpler, because applications do not issue SET ROLE. If you use a role for an application account, set it as the default role (or enable activate_all_roles_on_login), or the application connects with no privileges and every query fails.

Roles and stored programs

A stored program with SQL SECURITY INVOKER runs with the caller’s active roles. One with DEFINER uses the definer’s roles at the time of execution. Test both.

lightbulb
Prefer roles for people, direct grants for services
Developers and analysts change teams. Roles make that a one-line change. Application accounts rarely change, and direct grants are simpler to read.