ProxySQL MySQL MySQL · DBA · Advanced

ProxySQLQuery Rules & Routing

Master ProxySQL query rules — full column reference, read/write split, user-based routing, query blocking, rewriting and traffic mirroring with examples.

Query rules are the most powerful feature of ProxySQL. They are regex-based rules that inspect incoming SQL queries and route them to specific hostgroups, modify them, block them, or cache them. Rules are evaluated in order by rule_id — the first matching rule wins (unless apply=0).

SQL — Basic Read/Write Rules
-- Rule 1: Route SELECT FOR UPDATE to master (writes inside transactions)
INSERT INTO mysql_query_rules
  (rule_id, active, match_pattern, destination_hostgroup, apply, comment)
VALUES
  (1, 1, '^SELECT .* FOR UPDATE', 10, 1, 'SELECT FOR UPDATE → Master');

-- Rule 2: Route all other SELECTs to replicas
INSERT INTO mysql_query_rules
  (rule_id, active, match_pattern, destination_hostgroup, apply, comment)
VALUES
  (2, 1, '^SELECT', 20, 1, 'All SELECTs → Replicas');

-- Apply
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
ColumnDescription
rule_idUnique ID — rules evaluated in ascending order
active1 = rule is active; 0 = disabled
usernameMatch only queries from this MySQL user (NULL = all users)
schemanameMatch only queries for this schema/database
client_addrMatch queries from this client IP
proxy_addrMatch queries arriving on this ProxySQL IP
proxy_portMatch queries arriving on this ProxySQL port
digestMatch by query digest hash
match_digestRegex match against query digest (normalized query)
match_patternRegex match against full query text
negate_match_pattern1 = invert the regex match
re_modifiersRegex modifiers (e.g. CASELESS)
flagINRule only applies if connection flag equals this
flagOUTSet connection flag to this value after match
apply1 = stop processing after this rule; 0 = continue to next
destination_hostgroupRoute to this hostgroup
cache_ttlCache result for this many ms (NULL = no cache)
cache_empty_resultCache even empty resultsets
reconnectForce reconnect to new backend
timeoutQuery-specific timeout in ms
retriesNumber of retries on failure
delayDelay query execution by this many ms
mirror_hostgroupMirror query to this hostgroup (traffic shadowing)
mirror_flagOUTSet flag for mirrored connection
error_msgReturn this error message instead of routing
OK_msgReturn success without executing query
sticky_connKeep connection sticky to same backend
multiplexOverride multiplexing for this rule
logLog matching queries to query log
replace_patternReplace matched part of query with this string
commentFree text description
SQL — Table-specific Routing
-- Always read from master for orders table (consistency)
INSERT INTO mysql_query_rules
  (rule_id, active, match_pattern, destination_hostgroup, apply, comment)
VALUES
  (10, 1, 'orders', 10, 1, 'All queries on orders table → Master');

LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
SQL — User-based Routing
-- All queries from reports_user go to replica hostgroup
INSERT INTO mysql_query_rules
  (rule_id, active, username, destination_hostgroup, apply, comment)
VALUES
  (20, 1, 'reports_user', 20, 1, 'reports_user always → Replicas');

LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
SQL — Query Firewall
-- Block DROP TABLE
INSERT INTO mysql_query_rules
  (rule_id, active, match_pattern, error_msg, apply, comment)
VALUES
  (100, 1, '^DROP TABLE', 'Query blocked by ProxySQL firewall', 1, 'Block DROP TABLE');

-- Block queries without WHERE clause on large tables
INSERT INTO mysql_query_rules
  (rule_id, active, match_pattern, error_msg, apply, comment)
VALUES
  (101, 1, '^DELETE FROM .* WHERE', NULL, 1, 'Allow DELETE with WHERE');

LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
SQL — Query Rewriting
-- Add SQL_NO_CACHE hint to all SELECTs
INSERT INTO mysql_query_rules
  (rule_id, active, match_pattern, replace_pattern, apply, comment)
VALUES
  (200, 1, '^(SELECT)', '\1 SQL_NO_CACHE', 1, 'Disable query cache');

-- Rewrite old table name to new table name
INSERT INTO mysql_query_rules
  (rule_id, active, match_pattern, replace_pattern, apply, comment)
VALUES
  (201, 1, 'old_table_name', 'new_table_name', 0, 'Table rename rewrite');

LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
SQL — Rule Stats
-- See which rules are being hit and how many times
SELECT rule_id, match_pattern, destination_hostgroup,
       cache_ttl, apply, hits, comment
FROM stats.stats_mysql_query_rules
ORDER BY hits DESC;

-- View query digest (normalized queries and their stats)
SELECT hostgroup, schemaname, username,
       digest_text, count_star, sum_time, min_time, max_time
FROM stats.stats_mysql_query_digest
ORDER BY count_star DESC
LIMIT 20;
💡 Note: Use match_digest instead of match_pattern when possible — it is faster because ProxySQL normalizes the query once and matches against the digest.
  1. Most specific rules first (lowest rule_id)
  2. SELECT FOR UPDATE before generic SELECT
  3. User-specific rules before generic rules
  4. Table-specific rules before generic rules
  5. Catch-all rules last (highest rule_id)