前言
最近解决了个比较棘手的问题,由于排查过程挺有意思,于是就以此为素材写出了本篇文章。
Bug现场
首先,这个问题其实并不难解决,但是这个问题引发的现象倒是挺有意思。先描述一下现象吧,笔者在一次处理客户MySQL问题时遇到客户的MySQL的sys库不能用了并抛出一下错误:
mysql> SELECT * FROM sys.processlist;
ERROR 1356 (HY000): View 'sys.processlist' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them
排查常见问题
先定位几个常见问题:
权限不够
sys库functions和procedures丢失
mysqldump全备后跨版本恢复(会发生问题2的现象)
mysql升级没有执行mysql_upgrade(会发生问题2的现象)
首先排查权限问题是否有权限。
mysql> SHOW GRANTS FOR root@'localhost';
+---------------------------------------------------------------------+
| Grants for root@localhost |
+---------------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION |
| GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION |
+---------------------------------------------------------------------+
2 rows in set (0.00 sec)
明显并不是,接着排查是否是sys库相关的functions和procedures丢失了?
mysql> SELECT * FROM mysql.proc;
Empty set (0.00 sec)
mysql> SHOW PROCEDURE STATUS WHERE Db = 'sys';
Empty set (0.00 sec)
mysql> SHOW FUNCTION STATUS WHERE Db = 'sys';
Empty set (0.00 sec)
sys库functions和procedures丢失了,那不就是问题3就是问题4,甩给mysqldump全备和升级没有执行mysql_upgrade。
带着疑问于开始漫长的排查过程。经过对客户的刨根问题,发现并没有上述情况的发生。用户备份习惯都是全备(-A),且都是备份恢复后出现sys库ERROR 1356,检查用户MySQL环境主要几大版本分布MySQL 5.7.13,MySQL 5.7.25,MySQL 5.7.28,于是把问题定位到了mysqldump的备份上。
先备份还原一把看看
笔者强烈认为是客户跨版本造成的,给客户来点证据。先验证一波同版本MySQL使用mysqldump全备恢复后,到底会不会出现sys库ERROR 1356。
# 备份前先检测一波sys库,确认完全OK后开整。
mysql> SELECT * FROM sys.version;
+-------------+---------------+
| sys_version | mysql_version |
+-------------+---------------+
| 1.5.2 | 5.7.31-log |
+-------------+---------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM sys.processlist;
ERROR 1356 (HY000): View 'sys.processlist' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them
mysql> SELECT COUNT(*) FROM mysql.proc;
+----------+
| COUNT(*) |
+----------+
| 48 |
+----------+
1 row in set (0.00 sec)
# 使用我们经常用的那坨命令备份所有库
mysqldump --all-databases --set-gtid-purged=OFF --master-data=2 --single-transaction --routines --events --triggers --max_allowed_packet=256M > all.sql
# 备份完毕后我们开始恢复数据
mysql -uroot -S /tmp/mysql.sock < all.sql
# 恢复完毕后,检测一波sys库
mysql> SELECT * FROM sys.processlist;
ERROR 1356 (HY000): View 'sys.processlist' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them
mysql> SELECT COUNT(*) FROM mysql.proc;
+----------+
| COUNT(*) |
+----------+
| 0 |
+----------+
1 row in set (0.00 sec)
mysql> SHOW PROCEDURE STATUS WHERE Db = 'sys';
Empty set (0.00 sec)
mysql> SHOW FUNCTION STATUS WHERE Db = 'sys';
Empty set (0.00 sec)
啪啪啪打脸,竟然同版本也会出现?那究竟是什么问题?
再看看其它版本
经过对MySQL 5.7.13,MySQL 5.7.21,MySQL 5.7.25,MySQL 5.7.28,MySQL 5.7.31,几个版本测试全备躺枪,这一看,发现个奇怪的现象,他们唯一的共性就是无论怎么备份怎么还原只要使用了--all-databases(-A)那就是ERROR 1356。这不禁让笔者陷入了沉思。
寻找突破点
既然通用规律只有使用--all-databases(-A)会ERROR 1356,那就看看他到底备份了什么东西。于是喊上同事一起less看了下,上下扫了两眼。突然发现:
备份sql文件里DROP掉了mysql.proc。
后CREATE了一个新的mysql.proc。
LOCK TABLES和UNLOCK TABLES中间居然没有备份CREATE ROUTINE任何数据?
这不就是相当于每次导入全备都给我一个没有任何sys schema routines的全新mysql.proc表?那这不就异常的尴尬?
--
-- Table structure for table `proc`
--
DROP TABLE IF EXISTS `proc`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `proc` (
`db` char(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '',
`name` char(64) NOT NULL DEFAULT '',
`type` enum('FUNCTION','PROCEDURE') NOT NULL,
`specific_name` char(64) NOT NULL DEFAULT '',
`language` enum('SQL') NOT NULL DEFAULT 'SQL',
`sql_data_access` enum('CONTAINS_SQL','NO_SQL','READS_SQL_DATA','MODIFIES_SQL_DATA') NOT NULL DEFAULT 'CONTAINS_SQL',
`is_deterministic` enum('YES','NO') NOT NULL DEFAULT 'NO',
`security_type` enum('INVOKER','DEFINER') NOT NULL DEFAULT 'DEFINER',
`param_list` blob NOT NULL,
`returns` longblob NOT NULL,
`body` longblob NOT NULL,
`definer` char(93) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '',
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`sql_mode` set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES','IGNORE_SPACE','NOT_USED','ONLY_FULL_GROUP_BY','NO_UNSIGNED_SUBTRACTION','NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB','NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40','ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES','STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES','ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER','HIGH_NOT_PRECEDENCE','NO_ENGINE_SUBSTITUTION','PAD_CHAR_TO_FULL_LENGTH') NOT NULL DEFAULT '',
`comment` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`character_set_client` char(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`collation_connection` char(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`db_collation` char(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`body_utf8` longblob,
PRIMARY KEY (`db`,`name`,`type`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stored Procedures';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `proc`
--
LOCK TABLES `proc` WRITE;
/*!40000 ALTER TABLE `proc` DISABLE KEYS */;
/*!40000 ALTER TABLE `proc` ENABLE KEYS */;
UNLOCK TABLES;
真相大白
在官方文档sys-schema-usage页面有这样一段话(这里直接引用官方原文):
However, those statements display the definitions in relatively unformatted form. To view object definitions with more readable formatting, access the individual .sql files found under the scripts/sys_schema in MySQL source distributions. Prior to MySQL 5.7.28, the sources are maintained in a separate distribution available from the sys schema development website at https://github.com/mysql/mysql-sys.
Neither mysqldump nor mysqlpump dump the sys schema by default. To generate a dump file, name the sys schema explicitly on the command line using either of these commands:
mysqldump --databases --routines sys > sys_dump.sql
mysqlpump sys > sys_dump.sql
To reinstall the schema from the dump file, use this command:
mysql < sys_dump.sql
官方文档明确的告诉我们不会备份sys库。但在使用mysqldump在执行--all-databases会清空mysql.proc导致sys无法正常使用;这是一个BUG,并且只存在于MySQL5.7。
bug连接:
解决方案和使用场景
针对这个BUG整理了4个解决方案;可供参考,根据实际环境场景进行选择使用。
mysql_upgrade install or upgrade sys schema
这个方案适用于sys库已经因为mysqldump导入而损坏的情况下使用。
# 删除 sys schema (An error occurs if a sys schema exists but has no version view)
mysql> DROP DATABASE sys;
# 这个时候sys schema不应该存在
mysql> SHOW DATABASES;
# 最后,执行mysql_upgrade sys schema以恢复正常
mysql_upgrade --upgrade-system-tables --skip-verbose --force
mysql> SHOW DATABASES;
mysql> SELECT COUNT(*) FROM mysql.proc;
注意:mysql_upgrade在修理sys库的时候还同时修理mysql库和用户库表(期间加锁且速度一般),有极小可能会误伤;使用mysql_upgrade的时候要加上 --upgrade-system-tables,不然会扫描用户库表。
全备时同时备份sys库
这个方案适用于需要还原的数据库,sys库也不太正常的情况下使用;在全备后额外再备份一份sys库用于修复。
mysqldump -A --set-gtid-purged=OFF --master-data=2 --single-transaction --routines --events --triggers > all.sql
mysqldump --databases --routines sys > sys_dump_`mysql -V|awk '{print $5}'|cut -b 1-6`.sql
注意:不适用于做主从时使用它。
使用databases全备
这个方案适用于所有场景的全备需求,100%安全。
select_databases="
SELECT
GROUP_CONCAT(schema_name SEPARATOR ' ')
FROM
information_schema.schemata
WHERE
schema_name NOT IN ('performance_schema','information_schema');"
databases=`mysql -NBe "$select_databases"`
mysqldump --set-gtid-purged=OFF --master-data=2 --single-transaction --routines --events --triggers --max_allowed_packet=256M --databases > all.sql
使用mysql-sys开源代码
如果你的数据库sys全部中招了,又是生产库。那你只能用这个方法;
mysql-sys中记录了sys库的创建语句将文件下载到本地,然后根据数据库版本,执行以下命令即可。
# 安装前操作,内容是禁用掉sql_log_bin,不记录到日志中。
mysql> source before_setup.sql
# 创建sys库,实际会调用其他文件夹中的sql语句来进行表、视图、存储过程、触发器的创建
mysql> source sys_57.sql
# 安装后的操作,内容是将sql_log_bin恢复到操作前的状态
mysql> source after_setup.sql
最佳实践
MySQL5.7.7至今(2020/11/02)的所有5.7的小版本,在使用mysqldump在执行--all-databases都会清空mysql.proc导致sys无法正常使用。
mysql> SELECT * FROM sys.processlist; ERROR 1356 (HY000): View 'sys.processlist' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them
bug连接:官方文档明确的告诉我们不会备份sys库。但在使用mysqldump在执行--all-databases会清空mysql.proc导致sys无法正常使用;这是一个BUG,并且只存在于MySQL5.7。
如果因为使用看--all-databases参数已经造成sys异常报错,这样做可以修复其异常:
mysql_upgrade --upgrade-system-tables --skip-verbose --force
#注意:使用mysql_upgrade的时候要加上 --upgrade-system-tables。不然会扫描用户库表,期间加锁且速度一般。
因为在MySQL 5.7上使用mysqldump在执行--all-databases会清空mysql.proc导致sys无法正常使用;所以我们要规避这种状况的发生。一定不要在MySQL5.7上使用--all-databases。
最正确的全备姿势:采用--databases 后跟information_schema.schemata 查询出所有需要的库进行备份;这样是最安全,且又完全等同于--databases的效果;完全没有任何副作用。
select_databases="
SELECT
GROUP_CONCAT(schema_name SEPARATOR ' ')
FROM
information_schema.schemata
WHERE
schema_name NOT IN ('performance_schema','information_schema');"
databases=`mysql -NBe "$select_databases"`
mysqldump --set-gtid-purged=OFF --master-data=2 --single-transaction --routines --events --triggers --max_allowed_packet=256M --databases $select_databases > all.sql
试试MySQL8
测试MySQL 8.0.0至MySQL 8.0.20全系列不受影响,具体原因是从MySQL 8.0.0起就移除了mysql.proc这张表。具体查阅官方文档:
Previously, tables in the mysql system database were visible to DML and DDL statements. As of MySQL 8.0, data dictionary tables are invisible and cannot be modified or queried directly. However, in most cases there are corresponding INFORMATION_SCHEMA tables that can be queried instead. This enables the underlying data dictionary tables to be changed as server development proceeds, while maintaining a stable INFORMATION_SCHEMA interface for application use.
如果还有疑问
那就顺便看一眼mysqldump的源码吧(这个源码的设计也挺有意思,准备放到后面的文章里面),先过一眼这个变量。
/**
First mysql version supporting the information schema.
*/
#define FIRST_INFORMATION_SCHEMA_VERSION 50003
/**
Name of the information schema database.
*/
#define INFORMATION_SCHEMA_DB_NAME "information_schema"
/**
First mysql version supporting the performance schema.
*/
#define FIRST_PERFORMANCE_SCHEMA_VERSION 50503
/**
Name of the performance schema database.
*/
#define PERFORMANCE_SCHEMA_DB_NAME "performance_schema"
/**
First mysql version supporting the sys schema.
*/
#define FIRST_SYS_SCHEMA_VERSION 50707 /* 最早出现sys schema的MySQL版本 5.7.7 */
/**
Name of the sys schema database.
*/
#define SYS_SCHEMA_DB_NAME "sys"
dump所有库表(--all-databases)的源码:
.........
/* 执行dump_all_databases的条件 */
if (opt_alldbs)
{
if (!opt_alltspcs && !opt_notspcs)
dump_all_tablespaces();
dump_all_databases();
}
.........
/* dump_all_databases */
static int dump_all_databases()
{
MYSQL_ROW row;
MYSQL_RES *tableres;
int result=0
/* 获取所有数据库:SHOW DATABASES */
if (mysql_query_with_error_report(mysql, &tableres, "SHOW DATABASES"))
return 1;
while ((row= mysql_fetch_row(tableres)))
{
/* 排除information_schema */
if (mysql_get_server_version(mysql) >= FIRST_INFORMATION_SCHEMA_VERSION &&
!my_strcasecmp(&my_charset_latin1, row[0], INFORMATION_SCHEMA_DB_NAME))
continue;
/* 排除performance_schema */
if (mysql_get_server_version(mysql) >= FIRST_PERFORMANCE_SCHEMA_VERSION &&
!my_strcasecmp(&my_charset_latin1, row[0], PERFORMANCE_SCHEMA_DB_NAME))
continue;
/* 排除sys */
/* 检查当前MySQL的版本是否 >= 最早支持SYS_SCHEMA的版本号。 && row[0] 为 SYS_SCHEMA_DB_NAME 就跳过,不进行备份*/
if (mysql_get_server_version(mysql) >= FIRST_SYS_SCHEMA_VERSION &&
!my_strcasecmp(&my_charset_latin1, row[0], SYS_SCHEMA_DB_NAME))
continue;
if (is_ndbinfo(mysql, row[0]))
continue;
/* dump库中所有表 */
/* 逐一dump每个表 dump_all_tables_in_db */
if (dump_all_tables_in_db(row[0]))
result=1;
}
.........
备份functions和procedures的源码:
/** 此处--all-databases sys库不会传入dump_routines_for_db这个函数。
所以函数里面的备份过程跳过了sys库,也就造成了.sql文件里mysql.proc没有CREATE ROUTINE sys库的现象 */
static uint dump_routines_for_db(char *db)
{
........
/* 0, retrieve and dump functions, 1, procedures */
for (i= 0; i <= 1; i++)
{
/* 执行SHOW FUNCTION/PROCEDURE STATUS WHERE Db = xx,获取所有functions和procedures */
my_snprintf(query_buff, sizeof(query_buff),
"SHOW %s STATUS WHERE Db = '%s'",
routine_type[i], db_name_buff);
if (mysql_query_with_error_report(mysql, &routine_list_res, query_buff))
DBUG_RETURN(1);
if (mysql_num_rows(routine_list_res))
{
while ((routine_list_row= mysql_fetch_row(routine_list_res)))
{
routine_name= quote_name(routine_list_row[1], name_buff, 0);
DBUG_PRINT("info", ("retrieving CREATE %s for %s", routine_type[i],
name_buff));
/* 执行SHOW CREATE FUNCTION/PROCEDURE xxx,获取所有functions、procedures创建语句 */
my_snprintf(query_buff, sizeof(query_buff), "SHOW CREATE %s %s",
routine_type[i], routine_name);
........