MySQL5.7 使用 mysqldump 重要避坑事項【轉】


背景

筆者在一次處理客戶 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

首先,這個問題其實並不難解決,但是這個問題引發的現象倒是挺有意思。

 

排查常見問題

先定位幾個常見問題:

1. 權限不夠;

2. sys 庫 functions 和 procedures 丟失;

3. mysqldump 全備后跨版本恢復【會發生問題 2 的現象】;

4. 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,5.7.25,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,5.7.21,5.7.25,5.7.28,5.7.31 等幾個版本測試全備躺槍。奇怪的現象是他們唯一的共性就是無論怎么備份怎么還原只要使用了 --all-databases (-A) 就報 ERROR 1356。這不禁讓筆者陷入了沉思。

 

尋找突破點

既然通用規律只有使用 --all-databases (-A) 會 ERROR 1356,那就看看他到底備份了什么東西。於是喊上同事一起 less 看了下,上下掃了兩眼。突然發現:

1. 備份 SQL 文件里 DROP 掉了 mysql.proc

2. 后CREATE了一個新的 mysql.proc

3. LOCK TABLESUNLOCK 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】

https://dev.mysql.com/doc/refman/5.7/en/sys-schema-usage.html

頁面有這樣一段話(這里直接引用官方原文):

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:

sys:

https://dev.mysql.com/doc/refman/5.7/en/sys-schema.html

mysqldump:

https://dev.mysql.com/doc/refman/5.7/en/mysqldump.html

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,並且只存在於 MySQL 5.7.x !

BUG 連接:

  • https://bugs.mysql.com/bug.php?id=86807
  • https://bugs.mysql.com/bug.php?id=92631
  • https://bugs.mysql.com/bug.php?id=83259
  • https://github.com/mysql/mysql-server/commit/ded3155def2ba3356017c958c49ff58c2cae1830

 

解決方案和使用場景

針對這個 BUG 整理了 4 個解決方案可供參考,根據實際環境場景進行選擇使用。

1、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,不然會掃描用戶庫表。

2、全備時同時備份 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

 

 

注意:不適用於做主從時使用它。

3、使用 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

 

 

4、使用 mysql-sys 開源代碼

如果你的數據庫 sys 全部中招了,又是生產庫。那你只能用這個方法;

mysql-sys:

https://github.com/mysql/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

 

 

 

【加餐 1】試試 MySQL 8

測試 MySQL 8.0.0 至 MySQL 8.0.20 全系列不受影響,具體原因是從 MySQL 8.0.0 起就移除了 mysql.proc 這張表。具體查閱官方文檔:

1. data-dictionary-usage-differences:

https://dev.mysql.com/doc/refman/8.0/en/data-dictionary-usage-differences.html

2. news-8-0-0:

https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-0.html

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.

【加餐 2】如果還有疑問?

那就順便看一眼 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);
........

轉自

MySQL5.7 使用 mysqldump 重要避坑事項 https://www.toutiao.com/i6894154794278224395/

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM