A role is a locked, named account that holds privileges. Grant privileges to the role and the role to accounts, and one change reaches every holder:
CREATE ROLE 'shop_read', 'shop_write';
GRANT SELECT ON shop.* TO 'shop_read';
GRANT INSERT, UPDATE, DELETE ON shop.* TO 'shop_write';
CREATE USER 'report'@'localhost' IDENTIFIED BY 'Report#Only-2026';
GRANT 'shop_read' TO 'report'@'localhost';A granted role is inactive until SET ROLE switches it on for the session, or SET DEFAULT ROLE makes it active at every login:
q() { mysql -ureport -p'Report#Only-2026' -Nse "$1" 2>&1 | grep -v Warning; }
q "SELECT CURRENT_ROLE(); SELECT COUNT(*) FROM shop.orders"
q "SET ROLE 'shop_read'; SELECT CURRENT_ROLE(); SELECT COUNT(*) FROM shop.orders"
sudo mysql -e "SET DEFAULT ROLE 'shop_read' TO 'report'@'localhost'"
q "SELECT COUNT(*) FROM shop.orders; UPDATE shop.products SET price = 0"Output
NONE ERROR 1142 (42000) at line 1: SELECT command denied to user 'report'@'localhost' for table 'orders' `shop_read`@`%` 9 9 ERROR 1142 (42000) at line 1: UPDATE command denied to user 'report'@'localhost' for table 'products'
The reporting login is read-only by construction. SHOW GRANTS FOR ... USING 'shop_read' previews a role's effect, activate_all_roles_on_login activates every granted role, and mandatory_roles grants one to everybody (off and empty by default). Roles nest: granting shop_read to shop_write makes the writer a superset.