1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-28 08:01:55 +03:00
Files
mariadb/scripts/sys_schema/procedures/ps_trace_statement_digest.sql
Oleksandr Byelkin 6c951fea5f MDEV-39993 Use CREATE OR REPLACE for sys schema routines
mariadb-upgrade silently dropped EXECUTE grants on sys schema stored
functions and procedures. The sys schema install scripts reinstalled
every routine with DROP FUNCTION/PROCEDURE IF EXISTS followed by
CREATE. DROP cascades to delete the routine's rows in
mysql.procs_priv, so any EXECUTE grant a DBA had issued on e.g.
sys.table_exists or sys.quote_identifier was lost every time
mariadb-upgrade reinstalled the sys schema, even though the routine
itself came back unchanged.

Fix: replace DROP ... IF EXISTS + CREATE with CREATE OR REPLACE in
all 53 sys_schema function/procedure files and in the two templates
(templates/function.sql, templates/procedure.sql) so future routines
follow the same pattern. CREATE OR REPLACE PROCEDURE/FUNCTION goes
through sp_drop_routine_internal(), which only deletes the
mysql.proc row and never reaches sp_revoke_privileges() (that is
only called from the explicit DROP PROCEDURE/FUNCTION statement),
so mysql.procs_priv is left untouched and existing grants survive.
This mirrors the pattern already used by sys schema views
(CREATE OR REPLACE ... VIEW, since MDEV-9077), which never had this
problem.

Four of the converted files (functions/format_path.sql,
functions/ps_is_account_enabled_57.sql,
procedures/ps_setup_reset_to_default.sql,
procedures/ps_trace_thread_57.sql) are not referenced by
scripts/sys_schema/CMakeLists.txt; they were converted anyway for
consistency and have no behavioural effect.

As a side effect, a pre-existing UDF whose name collides with a sys
routine name is no longer destroyed before the reinstall fails:
DROP FUNCTION IF EXISTS resolved the UDF namespace first, silently
dropping the UDF and then failing on ER_SP_ALREADY_EXISTS anyway;
CREATE OR REPLACE fails immediately on ER_UDF_EXISTS with the UDF
intact.

scripts/maria_add_gis_sp.sql.in and the sys_config triggers were
deliberately left untouched: the GIS procedures are only
re-installed at bootstrap time (mariadb-upgrade instead patches
their definer in place via UPDATE), and triggers carry no
procs_priv rows, so neither is on the code path this bug is about.

Added mysql-test/main/mysql_upgrade_sys_routine_grants.test, which
grants EXECUTE on sys.table_exists and sys.quote_identifier,
overwrites both routine bodies with a marker to prove the upgrade
actually reinstalls them (rather than the sys schema install being
skipped), runs mariadb-upgrade, and checks both that the grants
survived and that the real routine bodies came back.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 17:07:02 +02:00

323 lines
13 KiB
SQL

-- Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; version 2 of the License.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
DELIMITER $$
CREATE OR REPLACE DEFINER='mariadb.sys'@'localhost' PROCEDURE ps_trace_statement_digest (
IN in_digest VARCHAR(32),
IN in_runtime INT,
IN in_interval DECIMAL(2,2),
IN in_start_fresh BOOLEAN,
IN in_auto_enable BOOLEAN
)
COMMENT '
Description
-----------
Traces all instrumentation within Performance Schema for a specific
Statement Digest.
When finding a statement of interest within the
performance_schema.events_statements_summary_by_digest table, feed
the DIGEST MD5 value in to this procedure, set how long to poll for,
and at what interval to poll, and it will generate a report of all
statistics tracked within Performance Schema for that digest for the
interval.
It will also attempt to generate an EXPLAIN for the longest running
example of the digest during the interval. Note this may fail, as:
* Performance Schema truncates long SQL_TEXT values (and hence the
EXPLAIN will fail due to parse errors)
* the default schema is sys (so tables that are not fully qualified
in the query may not be found)
* some queries such as SHOW are not supported in EXPLAIN.
When the EXPLAIN fails, the error will be ignored and no EXPLAIN
output generated.
Requires the SUPER privilege for "SET sql_log_bin = 0;".
Parameters
-----------
in_digest (VARCHAR(32)):
The statement digest identifier you would like to analyze
in_runtime (INT):
The number of seconds to run analysis for
in_interval (DECIMAL(2,2)):
The interval (in seconds, may be fractional) at which to try
and take snapshots
in_start_fresh (BOOLEAN):
Whether to TRUNCATE the events_statements_history_long and
events_stages_history_long tables before starting
in_auto_enable (BOOLEAN):
Whether to automatically turn on required consumers
Example
-----------
mysql> call ps_trace_statement_digest(\'891ec6860f98ba46d89dd20b0c03652c\', 10, 0.1, true, true);
+--------------------+
| SUMMARY STATISTICS |
+--------------------+
| SUMMARY STATISTICS |
+--------------------+
1 row in set (9.11 sec)
+------------+-----------+-----------+-----------+---------------+------------+------------+
| executions | exec_time | lock_time | rows_sent | rows_examined | tmp_tables | full_scans |
+------------+-----------+-----------+-----------+---------------+------------+------------+
| 21 | 4.11 ms | 2.00 ms | 0 | 21 | 0 | 0 |
+------------+-----------+-----------+-----------+---------------+------------+------------+
1 row in set (9.11 sec)
+------------------------------------------+-------+-----------+
| event_name | count | latency |
+------------------------------------------+-------+-----------+
| stage/sql/checking query cache for query | 16 | 724.37 us |
| stage/sql/statistics | 16 | 546.92 us |
| stage/sql/freeing items | 18 | 520.11 us |
| stage/sql/init | 51 | 466.80 us |
...
| stage/sql/cleaning up | 18 | 11.92 us |
| stage/sql/executing | 16 | 6.95 us |
+------------------------------------------+-------+-----------+
17 rows in set (9.12 sec)
+---------------------------+
| LONGEST RUNNING STATEMENT |
+---------------------------+
| LONGEST RUNNING STATEMENT |
+---------------------------+
1 row in set (9.16 sec)
+-----------+-----------+-----------+-----------+---------------+------------+-----------+
| thread_id | exec_time | lock_time | rows_sent | rows_examined | tmp_tables | full_scan |
+-----------+-----------+-----------+-----------+---------------+------------+-----------+
| 166646 | 618.43 us | 1.00 ms | 0 | 1 | 0 | 0 |
+-----------+-----------+-----------+-----------+---------------+------------+-----------+
1 row in set (9.16 sec)
// Truncated for clarity...
+-----------------------------------------------------------------+
| sql_text |
+-----------------------------------------------------------------+
| select hibeventhe0_.id as id1382_, hibeventhe0_.createdTime ... |
+-----------------------------------------------------------------+
1 row in set (9.17 sec)
+------------------------------------------+-----------+
| event_name | latency |
+------------------------------------------+-----------+
| stage/sql/init | 8.61 us |
| stage/sql/Waiting for query cache lock | 453.23 us |
| stage/sql/init | 331.07 ns |
| stage/sql/checking query cache for query | 43.04 us |
...
| stage/sql/freeing items | 30.46 us |
| stage/sql/cleaning up | 662.13 ns |
+------------------------------------------+-----------+
18 rows in set (9.23 sec)
+----+-------------+--------------+-------+---------------+-----------+---------+-------------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+-------+---------------+-----------+---------+-------------+------+-------+
| 1 | SIMPLE | hibeventhe0_ | const | fixedTime | fixedTime | 775 | const,const | 1 | NULL |
+----+-------------+--------------+-------+---------------+-----------+---------+-------------+------+-------+
1 row in set (9.27 sec)
Query OK, 0 rows affected (9.28 sec)
'
SQL SECURITY INVOKER
NOT DETERMINISTIC
MODIFIES SQL DATA
BEGIN
DECLARE v_start_fresh BOOLEAN DEFAULT false;
DECLARE v_auto_enable BOOLEAN DEFAULT false;
DECLARE v_explain BOOLEAN DEFAULT true;
DECLARE v_this_thread_enabed ENUM('YES', 'NO');
DECLARE v_runtime INT DEFAULT 0;
DECLARE v_start INT DEFAULT 0;
DECLARE v_found_stmts INT;
SET @log_bin := @@sql_log_bin;
SET sql_log_bin = 0;
-- Do not track the current thread, it will kill the stack
SELECT INSTRUMENTED INTO v_this_thread_enabed FROM performance_schema.threads WHERE PROCESSLIST_ID = CONNECTION_ID();
CALL sys.ps_setup_disable_thread(CONNECTION_ID());
DROP TEMPORARY TABLE IF EXISTS stmt_trace;
CREATE TEMPORARY TABLE stmt_trace (
thread_id BIGINT UNSIGNED,
timer_start BIGINT UNSIGNED,
event_id BIGINT UNSIGNED,
sql_text longtext,
timer_wait BIGINT UNSIGNED,
lock_time BIGINT UNSIGNED,
errors BIGINT UNSIGNED,
mysql_errno INT,
rows_sent BIGINT UNSIGNED,
rows_affected BIGINT UNSIGNED,
rows_examined BIGINT UNSIGNED,
created_tmp_tables BIGINT UNSIGNED,
created_tmp_disk_tables BIGINT UNSIGNED,
no_index_used BIGINT UNSIGNED,
PRIMARY KEY (thread_id, timer_start)
);
DROP TEMPORARY TABLE IF EXISTS stmt_stages;
CREATE TEMPORARY TABLE stmt_stages (
event_id BIGINT UNSIGNED,
stmt_id BIGINT UNSIGNED,
event_name VARCHAR(128),
timer_wait BIGINT UNSIGNED,
PRIMARY KEY (event_id)
);
SET v_start_fresh = in_start_fresh;
IF v_start_fresh THEN
TRUNCATE TABLE performance_schema.events_statements_history_long;
TRUNCATE TABLE performance_schema.events_stages_history_long;
END IF;
SET v_auto_enable = in_auto_enable;
IF v_auto_enable THEN
CALL sys.ps_setup_save(0);
UPDATE performance_schema.threads
SET INSTRUMENTED = IF(PROCESSLIST_ID IS NOT NULL, 'YES', 'NO');
-- Only the events_statements_history_long and events_stages_history_long tables and their ancestors are needed
UPDATE performance_schema.setup_consumers
SET ENABLED = 'YES'
WHERE NAME NOT LIKE '%\_history'
AND NAME NOT LIKE 'events_wait%'
AND NAME NOT LIKE 'events_transactions%'
AND NAME <> 'statements_digest';
UPDATE performance_schema.setup_instruments
SET ENABLED = 'YES',
TIMED = 'YES'
WHERE NAME LIKE 'statement/%' OR NAME LIKE 'stage/%';
END IF;
WHILE v_runtime < in_runtime DO
SELECT UNIX_TIMESTAMP() INTO v_start;
INSERT IGNORE INTO stmt_trace
SELECT thread_id, timer_start, event_id, sql_text, timer_wait, lock_time, errors, mysql_errno,
rows_sent, rows_affected, rows_examined, created_tmp_tables, created_tmp_disk_tables, no_index_used
FROM performance_schema.events_statements_history_long
WHERE digest = in_digest;
INSERT IGNORE INTO stmt_stages
SELECT stages.event_id, stmt_trace.event_id,
stages.event_name, stages.timer_wait
FROM performance_schema.events_stages_history_long AS stages
JOIN stmt_trace ON stages.nesting_event_id = stmt_trace.event_id;
SELECT SLEEP(in_interval) INTO @sleep;
SET v_runtime = v_runtime + (UNIX_TIMESTAMP() - v_start);
END WHILE;
SELECT "SUMMARY STATISTICS";
SELECT COUNT(*) executions,
format_pico_time(SUM(timer_wait)) AS exec_time,
format_pico_time(SUM(lock_time)) AS lock_time,
SUM(rows_sent) AS rows_sent,
SUM(rows_affected) AS rows_affected,
SUM(rows_examined) AS rows_examined,
SUM(created_tmp_tables) AS tmp_tables,
SUM(no_index_used) AS full_scans
FROM stmt_trace;
SELECT event_name,
COUNT(*) as count,
format_pico_time(SUM(timer_wait)) as latency
FROM stmt_stages
GROUP BY event_name
ORDER BY SUM(timer_wait) DESC;
SELECT "LONGEST RUNNING STATEMENT";
SELECT thread_id,
format_pico_time(timer_wait) AS exec_time,
format_pico_time(lock_time) AS lock_time,
rows_sent,
rows_affected,
rows_examined,
created_tmp_tables AS tmp_tables,
no_index_used AS full_scan
FROM stmt_trace
ORDER BY timer_wait DESC LIMIT 1;
SELECT sql_text
FROM stmt_trace
ORDER BY timer_wait DESC LIMIT 1;
SELECT sql_text, event_id INTO @sql, @sql_id
FROM stmt_trace
ORDER BY timer_wait DESC LIMIT 1;
IF (@sql_id IS NOT NULL) THEN
SELECT event_name,
format_pico_time(timer_wait) as latency
FROM stmt_stages
WHERE stmt_id = @sql_id
ORDER BY event_id;
END IF;
DROP TEMPORARY TABLE stmt_trace;
DROP TEMPORARY TABLE stmt_stages;
IF (@sql IS NOT NULL) THEN
SET @stmt := CONCAT("EXPLAIN FORMAT=JSON ", @sql);
BEGIN
-- Not all queries support EXPLAIN, so catch the cases that are
-- not supported. Currently that includes cases where the table
-- is not fully qualified and is not in the default schema for this
-- procedure as it's not possible to change the default schema inside
-- a procedure.
--
-- Errno = 1064: You have an error in your SQL syntax
-- Errno = 1146: Table '...' doesn't exist
DECLARE CONTINUE HANDLER FOR 1064, 1146 SET v_explain = false;
PREPARE explain_stmt FROM @stmt;
END;
IF (v_explain) THEN
EXECUTE explain_stmt;
DEALLOCATE PREPARE explain_stmt;
END IF;
END IF;
IF v_auto_enable THEN
CALL sys.ps_setup_reload_saved();
END IF;
-- Restore INSTRUMENTED for this thread
IF (v_this_thread_enabed = 'YES') THEN
CALL sys.ps_setup_enable_thread(CONNECTION_ID());
END IF;
SET sql_log_bin = @log_bin;
END$$
DELIMITER ;