How to curb the appetite of your BI team
and other useful tricks
By Bryan O’Neal
GoDaddy MySQL DBA
http://www.onealeng.com
Syntax
◦ GRANT {permissions} ON database.table TO ‘user’@’host’
◦ REVOKE {permissions} ON database.table FROM ‘user’@’host’
User@Host
◦ GRANT USAGE ON foobar.* TO ‘foo'@‘bar.com' IDENTIFIED BY ‘Pass1‘
◦ GRANT SELECT ON foobar.* TO ‘foo'@‘foo.com' IDENTIFIED BY ‘Pass2‘
Implicit User Creation
◦ GRANT USAGE ON foobar.* TO ‘foo'@‘bar.com' IDENTIFIED BY ‘Pass1‘
◦ IDENTIFIED BY ‘PASSWORD_IN_PLAIN_TEXT’
◦ IDENTIFIED BY PASSWORD ‘PASSWORD_HASH’
Multi user grants
◦ GRANT SELECT ON foobar.* TO ‘foo'@‘foo.com‘, ‘bar'@‘foo.com‘
IDENTIFIED BY ‘change_me’
Wild cards are object based * or text based %
All Tables in a database
◦ GRANT SELECT ON ‘shard_A01’.* TO 'projectuser'@'%'
IDENTIFIED BY ‘app_passwd';
All databases that match a pattern
◦ GRANT SELECT ON `shard\_%`.* TO 'projectuser'@'%'
IDENTIFIED BY ‘app_passwd';
Wild cards in user names are not allowed
◦ But you can issue grants to more then one user at a
time
Multiple Matches
◦
◦
◦
◦
◦
◦
GRANT
GRANT
GRANT
GRANT
GRANT
GRANT
SELECT
SELECT
SELECT
SELECT
SELECT
SELECT
ON
ON
ON
ON
ON
ON
foobar.*
foobar.*
foobar.*
foobar.*
foobar.*
foobar.*
SELECT USER()
SELECT CURRENT_USER()
Hard CIDR
TO
TO
TO
TO
TO
TO
‘foo'@‘8.8.8.%' IDENTIFIED BY ‘Pass2‘
‘foo'@‘%bar.com' IDENTIFIED BY ‘Pass1‘
‘foo'@‘%foo.com' IDENTIFIED BY ‘Pass2‘
‘foo'@‘%.com' IDENTIFIED BY ‘Pass1‘
‘foo'@‘%' IDENTIFIED BY ‘Pass2‘
‘foo'@‘' IDENTIFIED BY ‘Pass2‘
◦ What if I want to give access to the range of IPs like 8.8.8.128/27?
◦ GRANT USAGE ON foobar.* TO ‘foo'@‘8.8.8.128/255.255.255.224'
@localhost is special
◦ TCP Stack vs Socket (Or named pipe)
Name Resolution
◦
◦
◦
◦
◦
Every time you use a name like foo.com you delay connections
Mutex lock on gethostbyaddr() & gethostbyname() in older systems
This even happens on IP based grants like ‘foo'@‘8.8.8.%‘
Set skip-name-resolve
DNS attacks like CVE-2015-7547
Database
◦ GRANT ALL PRIVILEGES ON database.* TO 'user'@‘10.10.10.10';
Table/View
◦ GRANT ALL PRIVILEGES ON database.table TO 'user'@‘10.10.10.10';
◦ WITH GRANT OPTION
Column
◦ GRANT SELECT (column) ON database.table TO 'user'@‘10.10.10.10';
Routine
◦ GRANT EXECUTE ON database.* TO 'user'@‘10.10.10.10';
◦ GRANT EXECUTE ON PROCEDURE database.procname TO
'user'@‘10.10.10.10';
Proxy users let you have multiple user@host combos use a
single set of permissions.
-- create proxy user
CREATE USER 'employee_ext'@'129.168.%'
IDENTIFIED WITH my_auth_plugin AS
'my_auth_string';
-- create proxied user
CREATE USER 'employee'@'localhost'
IDENTIFIED BY 'employee_pass';
-- grant PROXY privilege for proxy user to
proxied user
GRANT PROXY
ON 'employee'@'localhost'
TO 'employee_ext'@'localhost';
System settings to curb usage for all users
◦
◦
◦
◦
◦
◦
◦
◦
max_connect_errors
max_connections
max_user_connections
max_execution_time (Only in 5.7)
net_write_timeout
net_read_timeout
wait_timeout
interactive_timeout
The clever user can override the session variables post
connection using SET.
FLUSH HOSTS will reset connect errors
Per user limits:
◦ GRANT ALL ON database.* TO ‘baduser’@’10.10.1.2’
WITH MAX_QUERIES_PER_HOUR 20
◦ GRANT ALL ON database.* TO ‘baduser’@’10.10.1.2’
WITH MAX_QUERIES_PER_HOUR 20 MAX_UPDATES_PER_HOUR 5
That’s cool! What else can it do?
◦
◦
◦
◦
MAX_QUERIES_PER_HOUR
MAX_UPDATES_PER_HOUR
MAX_CONNECTIONS_PER_HOUR
MAX_USER_CONNECTIONS
Do Over!
◦ A ‘FLUSH USER RESOURCES’ will reset all limits for all users
How Password Expiration is Set
◦
◦
◦
◦
◦
Global default: default_password_lifetime=1036
ALTER USER 'jeffreyK'@'localhost' PASSWORD EXPIRE DEFAULT;
ALTER USER ‘visa'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;
ALTER USER ‘myDBA'@'localhost' PASSWORD EXPIRE NEVER;
ALTER USER ‘compermiseduser'@'localhost' PASSWORD EXPIRE;
Sandbox
mysql> SELECT 1;
ERROR 1820 (HY000): You must SET PASSWORD before executing this statement
◦ SET PASSWORD FOR 'bob'@'%.marley.org' = PASSWORD('auth_string');
◦ SET PASSWORD = PASSWORD('auth_string');
Benefits to being super
◦ Reserved connections!
Default is a single reserved connection
Patch for 10
mysqld.cc : thd->scheduler->max_connections + 10
◦ License to Kill!
You can kill your own threads but super lets you kill everyone's
◦ Writes with read_only
Patches exist for super_read_only
◦ Purge binlogs, change master, set dynamic global variables, etc.
CREATE TABLESPACE
FILE
PROCESS
RELOAD
REPLICATION CLIENT
REPLICATION SLAVE
SHOW DATABASES
SHUTDOWN
SUPER
DB Governor
◦ Cloud Linux – Usually used for hosting like CPanel
◦ Difficult to maintain with other custom patches
◦ Heavy handed solution
Write Your Own with Percona’s User Stats!
◦
◦
◦
◦
CPU_TIME
BUSY_TIME
BYTES_RECEIVED
BYTES_SENT
+------------------------+
| Field
|
+------------------------+
| USER
|
| TOTAL_CONNECTIONS
|
| CONCURRENT_CONNECTIONS |
| CONNECTED_TIME
|
| BUSY_TIME
|
| CPU_TIME
|
| BYTES_RECEIVED
|
| BYTES_SENT
|
| BINLOG_BYTES_WRITTEN
|
| ROWS_FETCHED
|
| ROWS_UPDATED
|
| TABLE_ROWS_READ
|
| SELECT_COMMANDS
|
| UPDATE_COMMANDS
|
| OTHER_COMMANDS
|
| COMMIT_TRANSACTIONS
|
| ROLLBACK_TRANSACTIONS |
| DENIED_CONNECTIONS
|
| LOST_CONNECTIONS
|
| ACCESS_DENIED
|
| EMPTY_QUERIES
|
| TOTAL_SSL_CONNECTIONS |
+------------------------+
The mysql database holds 5 user tables controlling
permissions:
◦ The user table contains a record for each account known to the server.
The user record for an account lists its global privileges, resource
limits it is subject to, and SSL info.
◦ The db table lists database-specific privileges for accounts.
◦ The tables_priv table lists table-specific privileges for accounts.
◦ The columns_priv table lists column-specific privileges for accounts.
◦ The procs_priv table lists privileges that accounts have for stored
procedures and functions.
If you use Tapestry/Struts
& you use stored procedures
◦ Execute is not enough to use stored procedures or functions
Unless you add SELECT ON mysql.proc
GRANT
priv_type [(column_list)]
[, priv_type [(column_list)]] ...
ON [object_type] object
TO user_specification [, user_specification] ...
[REQUIRE {NONE | tsl_option [[AND] tsl_option] ...}]
[WITH {GRANT OPTION | resource_option} ...]
GRANT PROXY ON user_specification
TO user_specification [, user_specification] ...
[WITH GRANT OPTION]
GRANT priv_type [(column_list)] [, priv_type [(column_list)]] ... ON [object_type] priv_level
TO user_specification [, user_specification] ... [REQUIRE {NONE | tsl_option [[AND] tsl_option]
...}] [WITH {GRANT OPTION | resource_option} ...]
object_type: {
TABLE
| FUNCTION
| PROCEDURE
}
priv_level: {
*
| *.*
| db_name.*
| db_name.tbl_name
| tbl_name
| db_name.routine_name
}
user_specification:
user [ auth_option ]
}
auth_option: {
# As of MySQL 5.7.6
IDENTIFIED BY 'auth_string'
| IDENTIFIED BY PASSWORD 'hash_string'
| IDENTIFIED WITH auth_plugin
| IDENTIFIED WITH auth_plugin BY 'auth_string'
| IDENTIFIED WITH auth_plugin AS 'hash_string'
}
tsl_option: {
SSL
| X509
| CIPHER 'cipher'
| ISSUER 'issuer'
| SUBJECT 'subject'
}
resource_option: {
| MAX_QUERIES_PER_HOUR count
| MAX_UPDATES_PER_HOUR count
| MAX_CONNECTIONS_PER_HOUR count
| MAX_USER_CONNECTIONS count
}
© Copyright 2026 Paperzz