This is just a quick update of a stored procedure to assist with MySQL Database Maintenance. I originally posted this back in 2012.

The Stored Procedure allows you to run OPTIMIZE TABLE or ANALYZE TABLE on all (or most) tables in a MySQL database.

In this version I’ve added some simple logging and automatic recognition of partitioned tables to take advantage of the manageability improvements they bring.

DELIMITER $$

DROP PROCEDURE IF EXISTS `db_maintenance`$$

CREATE DEFINER=`root`@`%` PROCEDURE `db_maintenance`(
		IN p_mode TINYINT,
		IN p_database VARCHAR(128)
    )
BEGIN

		###################################################
		# Author: Rhys Campbell #
                # Created: 2014-01-18 #
                ###################################################
		DECLARE done TINYINT;
		DECLARE my_table VARCHAR(128);
		DECLARE is_partitioned TINYINT;

		# Table cursor
		DECLARE table_cursor CURSOR FOR SELECT t.TABLE_NAME,
													 IF(t.CREATE_OPTIONS LIKE '%partitioned%', 1, 0) AS is_partitioned
										FROM INFORMATION_SCHEMA.TABLES t
										WHERE t.TABLE_SCHEMA = p_database
										AND NOT EXISTS (SELECT *
														FROM db_maintenance_table_excludes t2
														WHERE t2.database_name = t.TABLE_SCHEMA
														AND t2.table_name = t.TABLE_NAME)
										AND t.TABLE_TYPE = 'BASE TABLE';

		DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

		SET done = 0;

		OPEN table_cursor;

		table_loop: LOOP
		FETCH table_cursor INTO my_table,
								 is_partitioned;

		# Leave the loop if we're done
		IF (done = 1) THEN
			LEAVE table_loop;
		END IF;

			IF (is_partitioned = 0) THEN

				IF(p_mode = 1) THEN # Optimize
					SET @q = CONCAT('OPTIMIZE TABLE ', p_database, '.', my_table);
				ELSE # Analyze
					SET @q = CONCAT('ANALYZE TABLE ', p_database, '.', my_table);
				END IF;

				# Start log statement
				INSERT INTO db_maintenance_log
				(
					db,
					statement,
					started
				)
				VALUES
				(
					p_database,
					@q,
					NOW()
				);

				SET @id = LAST_INSERT_ID();

				PREPARE stmt FROM @q;
				EXECUTE stmt;

				# End log statement
				UPDATE db_maintenance_log
				SET finished = NOW()
				WHERE id = @id;

			ELSE # Partitioned tables
				BEGIN

					DECLARE var_partition_name VARCHAR(255);
					DECLARE partitions_done TINYINT;

					DECLARE partition_cursor CURSOR FOR SELECT PARTITION_NAME
														      FROM INFORMATION_SCHEMA.PARTITIONS
														      WHERE TABLE_SCHEMA = p_database
														      AND TABLE_NAME = my_table;

					DECLARE CONTINUE HANDLER FOR NOT FOUND SET partitions_done = 1;

					SET partitions_done = 0;

					OPEN partition_cursor;

							partition_loop: LOOP

									FETCH partition_cursor INTO var_partition_name;

									# Leave the loop if we're done
									IF (partitions_done = 1) THEN
										LEAVE table_loop;
									END IF;

									IF (p_mode = 1) THEN # Optimize
										SET @q = CONCAT('ALTER TABLE ', p_database, '.', my_table, ' OPTIMIZE PARTITION ', var_partition_name);
									ELSE
										SET @q = CONCAT('ALTER TABLE ', p_database, '.', my_table, ' ANALYZE PARTITION ', var_partition_name);
									END IF;

									# Start log statement
									INSERT INTO db_maintenance_log
									(
										db,
										statement,
										started
									)
									VALUES
									(
										p_database,
										@q,
										NOW()
									);

									SET @id = LAST_INSERT_ID();

									PREPARE stmt FROM @q;
									EXECUTE stmt;

									# End log statement
									UPDATE db_maintenance_log
									SET finished = NOW()
									WHERE id = @id;

								END LOOP partition_loop;

						# Clean up
						CLOSE partition_cursor;
				END;
			END IF;

		END LOOP table_loop;

		# Clean up
		CLOSE table_cursor;

		# Purge old records from the db_maintenance_log table
		DELETE FROM db_maintenance_log
		WHERE started <= DATE_SUB(NOW(), INTERVAL 6 MONTH);

    END$$

DELIMITER ;

Tables are retrieved from INFORMATION_SCHEMA and I use the below table to hold any tables I want to exclude from automatic maintenance.

CREATE TABLE db_maintenance_table_excludes ( database_name VARCHAR(128), table_name VARCHAR(128), PRIMARY KEY ( database_name, table_name ) );

Here’s the DDL for the logging table…

CREATE TABLE `db_maintenance_log` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `db` varchar(128) NOT NULL,
  `statement` varchar(1024) NOT NULL,
  `started` datetime DEFAULT NULL,
  `finished` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_started` (`started`)
)

Usage is as follows…

# Run OPTIMIZE TABLE
CALL `db_maintenance`(1, 'db_name');
# Run ANALYZE TABLE
CALL `db_maintenance`(2, 'db_name');