Skip to content

MySQL-Shell

MySQL Shell 需要 MySQL 5.7.12 或更高版本的服务器支持基础功能。

MySQL Shell 是 MySQL 官方提供的现代化命令行客户端工具,它提供了强大的数据库备份和恢复功能,相比传统的 mysqldump 工具,MySQL Shell 的备份功能更加高效、灵活。本文将详细介绍如何使用 MySQL Shell 进行数据库备份与恢复操作。

MySQL Shell 概述

  1. 多语言支持
    • SQL 模式:传统 SQL 命令行
    • JavaScript 模式:支持 JS 脚本
    • Python 模式:支持 Python 脚本
  2. 高级功能
    • 数据库对象管理
    • 数据备份与恢复
    • 文档存储支持
    • X Protocol 支持
  3. 跨平台
    • 支持 Linux、Windows 和 macOS

核心优势

  • 并行备份(多线程)
  • 压缩支持(zstd/gzip)
  • 一致性快照
  • 进度跟踪

安装

官网地址:https://downloads.mysql.com/archives/shell/

二进制包

bash
# 下载 MySQL Shell 二进制包
wget https://dev.mysql.com/get/Downloads/MySQL-Shell/mysql-shell-8.0.28-linux-glibc2.12-x86-64bit.tar.gz

# 解压到 /usr/local 目录
tar zxvf mysql-shell-8.0.28-linux-glibc2.12-x86-64bit.tar.gz -C /usr/local/

# 创建软链接
ln -s /usr/local/mysql-shell-8.0.28-linux-glibc2.12-x86-64bit/ /usr/local/mysql-shell

# 添加环境变量
echo 'export PATH=$PATH:/usr/local/mysql-shell/bin' >> ~/.bashrc
source ~/.bashrc

RPM包

bash
# 下载rpm包
wget https://downloads.mysql.com/archives/get/p/43/file/mysql-shell-8.0.42-1.el8.x86_64.rpm

# 安装
rpm -ivh mysql-shell-8.0.20-1.el7.x86_64.rpm

验证安装

bash
mysqlsh --version

mysqlsh 命令

语法格式

bash
mysqlsh [options] [URI] [-- [script_file] [script_args]]

选项

选项描述示例
--uri <connection_string>使用URI格式连接MySQLmysqlsh --uri user@localhost:3306
--host <hostname>指定服务器主机mysqlsh --host 127.0.0.1
--port <port>指定服务器端口mysqlsh --port 33060
--user <username>指定用户名mysqlsh --user root
--password [password]指定密码(可选)mysqlsh --password
--py启动Python模式mysqlsh --py
--js启动JavaScript模式(默认)mysqlsh --js
--sql启动SQL模式mysqlsh --sql
--file <filename>执行脚本文件mysqlsh --file setup.js
--interactive[=full]交互模式(默认)mysqlsh --interactive=full
--dba enableXProtocol启用X协议mysqlsh --dba enableXProtocol

连接 MySQL 服务器

bash
# 方法1:交互式输入密码
mysqlsh root@localhost:3306

# 方法2:命令行指定密码
mysqlsh 'root:password'@localhost:3306

# 方法3:连接时指定数据库
mysqlsh 'root:password'@localhost:3306/testDB

# JavaScript 模式
mysqlsh 'root:password'@localhost:3306 --js

# Python 模式
mysqlsh 'root:password'@localhost:3306 --py

# SQL 模式
mysqlsh 'root:password'@localhost:3306 --sql

\help

通过mysqlsh连接到mysql后的选项

命令/别名功能描述
\help\?显示帮助信息,列出所有可用命令
\quit\q退出 MySQL Shell
\exit\quit,退出 MySQL Shell
\connect\c连接到 MySQL 服务器 (格式: user@host:port)
\disconnect断开当前连接
\reconnect重新连接到上次使用的服务器
\sql切换到 SQL 执行模式
\js切换到 JavaScript 执行模式
\py切换到 Python 执行模式
\status\s显示当前连接状态信息
\use\u设置当前数据库(同 SQL USE语句)
\source\.执行外部 SQL 脚本文件
\warnings启用警告显示
\nowarnings禁用警告显示
\pager设置输出分页器(如 less
\nopager禁用分页器,直接输出到屏幕
\history显示命令历史记录
\show显示数据库对象(表、视图等)
\watch定期执行命令并显示结果(类似 watch命令)
\sleep暂停执行指定的秒数
\system\!执行操作系统命令
\edit\e在编辑器中打开当前命令进行编辑
\option管理 MySQL Shell 配置选项
\dump导出数据库对象(已弃用,推荐使用 util.dumpInstance()
\load导入数据(已弃用,推荐使用 util.loadDump()
\import导入表数据(已弃用,推荐使用 util.importTable()
\export导出表数据(已弃用,推荐使用 util.exportTable()
util访问实用工具模块(备份/恢复/导入导出等)
\dba访问 AdminAPI(InnoDB Cluster 管理)
\cluster管理 MySQL InnoDB Cluster
\rs管理 MySQL ReplicaSet

注意:实际可用命令可能因 MySQL Shell 版本不同而略有差异,建议使用 \help查看您当前版本的完整命令列表。

经典案例

bash
mysqlsh --sql -e "SELECT * FROM testdb.users" > output.txt
# 服务器版本检查
mysqlsh --sql -e "SELECT VERSION()"

# 连接MySQL并执行SQL查询
mysqlsh --sql --user root --host localhost --port 3306 --schema test -e "SELECT * FROM users"

# 使用JavaScript模式创建文档集合
mysqlsh --js
\connect user:pwd@localhost:33060
session.createSchema('my_collection')
db = session.getSchema('my_collection')
db.createCollection('customers')

# 使用Python模式批量导入数据
mysqlsh --py
import mysqlx
session = mysqlx.get_session('user:pwd@localhost:33060')
schema = session.get_schema('test')
table = schema.get_table('employees')
table.insert(['name','age']).values('John',30).execute()

多语言支持

MySQL Shell 支持三种语言模式,可随时切换:

  • 切换语言模式

    sql
    \sql  # 切换到SQL模式
    \js   # 切换到JavaScript模式
    \py   # 切换到Python模式
  • JavaScript示例

    javascript
    var db = session.createSchema('testDB');
    var sqlCreateTable = "CREATE TABLE testDB.myTable (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255))";
    var result = session.sql(sqlCreateTable).execute();
  • Python示例

    python
    session.run_sql('CREATE DATABASE IF NOT EXISTS mydb')
    session.run_sql('USE mydb')
    session.run_sql('CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), age INT)')

util-数据备份

备份级别

  • 实例级备份 (dumpInstance)
  • 数据库级备份 (dumpSchemas)
  • 表级备份 (dumpTables)

选项

通用备份选项(适用于所有备份方法)
选项类型默认值描述
threadsint4并行线程数
compressionstring"zstd"压缩算法:"none""gzip""zstd"
showProgressbooltrue显示进度条
dryRunboolfalse模拟执行(不实际备份)
chunkingbooltrue启用表分块
bytesPerChunkstring"64M"每个分块的最大字节数
maxRatestring"0"最大传输速率(如 "100M"
showMetadatabooltrue在输出中包含元数据
ocimdsboolfalse为Oracle Cloud Infrastructure MySQL服务优化
compatibilityarray[]兼容性选项列表(如 ["force_innodb"]
wheredict{}表过滤条件(如 {"schema.table": "id>1000"}
ddlOnlyboolfalse仅备份DDL(不备份数据)
dataOnlyboolfalse仅备份数据(不备份DDL)
eventsbooltrue备份事件
routinesbooltrue备份存储过程和函数
triggersbooltrue备份触发器
tzUtcbooltrue使用UTC时区
skipConsistencyChecksboolfalse跳过一致性检查
consistentbooltrue获取一致性快照(使用FLUSH TABLES WITH READ LOCK)
dummyboolfalse生成虚拟备份(测试用)
dumpInstance() 特有选项
选项类型默认值描述
usersbooltrue备份用户账户
includeSchemasarraynull包含的数据库列表
excludeSchemasarraynull排除的数据库列表
includeTablesarraynull包含的表列表
excludeTablesarraynull排除的表列表
schemastringnull仅备份指定schema
dumpSchemas() 特有选项
选项类型默认值描述
includeTablesarraynull包含的表列表
excludeTablesarraynull排除的表列表
dumpTables() 特有选项
选项类型默认值描述
allboolfalse备份所有表(忽略表列表)
partitionsarraynull指定分区列表
util.loadDump() 导入选项
选项类型默认值描述
loadIndexesbooltrue是否加载索引
deferTableIndexesstring"off"延迟索引创建:"off""all""fulltext"
loadDdlbooltrue是否执行DDL语句
loadDatabooltrue是否加载数据
skipBinlogboolfalse跳过binlog记录(加速导入)
schemastringnull指定导入到特定schema
ignoreVersionboolfalse忽略MySQL版本检查
resetProgressboolfalse重置之前的导入进度
backgroundboolfalse后台加载数据
characterSetstringnull指定字符集(如 "utf8mb4"
createInvisiblePKsboolfalse为无主键表创建隐藏主键(MySQL 8.0+)
ignoreExistingObjectsboolfalse跳过已存在的对象
stripDefinersboolfalse移除DEFINER子句
analyzeTablesboolfalse导入后执行ANALYZE TABLE
osBucketNamestringnullOCI存储桶名称
osNamespacestringnullOCI命名空间

SQL 模式备份

sql
-- 设置备份选项(需先切换到JS或PY模式设置变量)
\js
var options = {
  threads: 4,
  compression: "zstd",
  showProgress: true
}
\sql

-- 执行备份(实际仍调用JS/PY函数)
\js util.dumpInstance("/backup/full", options);

注意:纯SQL模式无法直接使用util工具,需借助JS/PY模式

JavaScript 模式备份

全实例备份
javascript
util.dumpInstance("/backup/full", {
  threads: 8,                  // 并行线程数
  compression: "zstd",        // 压缩算法(zstd/gzip/none)
  showProgress: true,         // 显示进度条
  ocimds: true,                // 为MySQL云服务优化
  compatibility: ["strip_restricted_grants"], // 兼容性选项
  users: true                  // 包含用户账户
});
单数据库备份
javascript
util.dumpSchemas(["employees"], "/backup/employees", {
  excludeTables: ["employees.salaries"],  // 排除特定表
  ddlOnly: false,                        // 仅结构
  dataOnly: false,                       // 仅数据
  chunking: true                         // 启用分块
});
大表备份(分块)
javascript
util.dumpTables("sakila", ["film", "film_actor"], "/backup/sakila_films", {
  bytesPerChunk: "64M",      // 每块大小
  where: {"sakila.film": "rating='PG-13'"} // 条件过滤
});
分库备份+并行处理
javascript
// 分库备份+并行处理
const databases = ["db1", "db2", "db3"];
databases.forEach(db => {
  util.dumpSchemas([db], `/backup/${db}`, {
    threads: 4,
    compression: "zstd"
  });
});
完整示例
javascript
util.dumpInstance("/backup/full", {
  // 通用选项
  threads: 8,
  compression: "zstd",
  showProgress: true,
  
  // 实例备份选项
  users: true,
  excludeSchemas: ["information_schema", "performance_schema"],
  excludeTables: ["mysql.user"],
  
  // 高级选项
  compatibility: ["strip_restricted_grants", "force_innodb"],
  where: {
    "employees.salaries": "salary > 100000",
    "employees.dept_manager": "1=1"
  },
  
  // 分块控制
  chunking: true,
  bytesPerChunk: "128M",
  
  // 内容过滤
  ddlOnly: false,
  dataOnly: false,
  routines: true,
  triggers: true
});

Python 模式备份

全实例备份
python
util.dump_instance("/backup/full", {
  "threads": 4,
  "compression": "gzip",
  "showProgress": True,
  "dryRun": False  # 模拟执行
})
增量备份(需先做全量备份)
python
util.dump_instance("/backup/incr", {
  "baseDir": "/backup/full",  # 基于全量备份
  "incremental": True,
  "lastSeen": "2023-11-20T15:00:00"  # 上次备份时间
})
备份验证
python
# 验证备份完整性
util.check_for_server_upgrade({
  "targetVersion": "8.0.36",
  "configPath": "/backup/full/@.json",
  "outputFormat": "JSON"
})
全量备份脚本

可在定时任务中设定每天凌晨2点全量备份0 2 * * * /usr/bin/mysqlsh --py -f /scripts/mysql_backup.py

python
#!/usr/bin/env mysqlsh
# -*- coding: utf-8 -*-

import os
import time
import smtplib
from email.mime.text import MIMEText
from datetime import datetime

# 配置部分
CONFIG = {
    "backup_dir": "/data/backups/mysql",
    "retention_days": 7,
    "mysql_uri": "root:password@localhost:3306",
    "compress": "zstd",
    "threads": 4,
    "schemas": ["important_db", "app_db"],  # 空列表表示全库备份
    "exclude_tables": ["app_db.log_data"], # 排除的表
    "smtp": {
        "enabled": True,
        "host": "smtp.example.com",
        "port": 587,
        "user": "backup@example.com",
        "password": "email_password",
        "to": "dba@example.com"
    }
}

def send_email(subject, body):
    if not CONFIG["smtp"]["enabled"]:
        return
        
    msg = MIMEText(body)
    msg['Subject'] = subject
    msg['From'] = CONFIG["smtp"]["user"]
    msg['To'] = CONFIG["smtp"]["to"]
    
    try:
        with smtplib.SMTP(CONFIG["smtp"]["host"], CONFIG["smtp"]["port"]) as server:
            server.starttls()
            server.login(CONFIG["smtp"]["user"], CONFIG["smtp"]["password"])
            server.send_message(msg)
    except Exception as e:
        log(f"邮件发送失败: {str(e)}", level="ERROR")

def log(message, level="INFO"):
    log_entry = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {level}: {message}"
    print(log_entry)
    
    log_file = os.path.join(CONFIG["backup_dir"], "backup.log")
    with open(log_file, "a") as f:
        f.write(log_entry + "\n")

def cleanup_old_backups():
    now = time.time()
    cutoff = now - (CONFIG["retention_days"] * 86400)
    
    deleted = 0
    for filename in os.listdir(CONFIG["backup_dir"]):
        filepath = os.path.join(CONFIG["backup_dir"], filename)
        if os.path.isdir(filepath) and filename.startswith("backup_"):
            file_time = os.path.getmtime(filepath)
            if file_time < cutoff:
                log(f"删除过期备份: {filename}")
                os.system(f"rm -rf {filepath}")
                deleted += 1
    return deleted

def perform_backup():
    # 创建备份目录
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_path = os.path.join(CONFIG["backup_dir"], f"backup_{timestamp}")
    os.makedirs(backup_path, exist_ok=True)
    
    log(f"开始备份到 {backup_path}")
    start_time = time.time()
    
    try:
        # 备份选项
        options = {
            "threads": CONFIG["threads"],
            "compression": CONFIG["compress"],
            "showProgress": False,
            "consistent": True
        }
        
        if CONFIG["exclude_tables"]:
            options["excludeTables"] = CONFIG["exclude_tables"]
        
        # 执行备份
        if CONFIG["schemas"]:
            util.dump_schemas(CONFIG["schemas"], backup_path, options)
        else:
            util.dump_instance(backup_path, options)
            
        # 计算备份大小
        size = sum(os.path.getsize(os.path.join(dirpath, filename)) 
                  for dirpath, _, filenames in os.walk(backup_path) 
                  for filename in filenames) / (1024 * 1024)
        
        elapsed = time.time() - start_time
        log(f"备份成功完成! 大小: {size:.2f} MB, 耗时: {elapsed:.2f} 秒")
        
        # 发送成功通知
        send_email(
            "MySQL备份成功通知",
            f"备份已成功完成:\n路径: {backup_path}\n大小: {size:.2f} MB\n耗时: {elapsed:.2f} 秒"
        )
        
        return True
        
    except Exception as e:
        log(f"备份失败: {str(e)}", level="ERROR")
        send_email(
            "MySQL备份失败通知",
            f"备份失败:\n错误: {str(e)}\n时间: {datetime.now()}"
        )
        return False

if __name__ == "__main__":
    # 初始化MySQL连接
    shell.connect(CONFIG["mysql_uri"])
    
    # 执行备份和清理
    backup_status = perform_backup()
    deleted = cleanup_old_backups()
    
    log(f"已清理 {deleted} 个过期备份")
    log("备份任务完成\n" + "="*50)
增量备份脚本

可在定时任务中设定每小时增量备份 0 * * * * /usr/bin/mysqlsh --py -f /scripts/incremental_backup.py

python
#!/usr/bin/env mysqlsh

import os
import datetime
from mysqlsh import globals

def incremental_backup():
    config = {
        "base_dir": "/data/backups/mysql/full_backup",
        "incr_dir": "/data/backups/mysql/incremental",
        "mysql_uri": "root:password@localhost:3306"
    }
    
    # 获取最近的全量备份
    last_full = sorted([d for d in os.listdir(config["base_dir"]) 
                       if d.startswith("backup_")])[-1]
    base_path = os.path.join(config["base_dir"], last_full)
    
    # 创建增量备份目录
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    incr_path = os.path.join(config["incr_dir"], f"incr_{timestamp}")
    os.makedirs(incr_path, exist_ok=True)
    
    # 连接MySQL
    globals.shell.connect(config["mysql_uri"])
    
    # 执行增量备份
    util.dump_instance(incr_path, {
        "baseDir": base_path,
        "incremental": True,
        "threads": 4,
        "compression": "zstd"
    })

if __name__ == "__main__":
    incremental_backup()

util-恢复数据

JavaScript 恢复

javascript
util.loadDump("/backup/full", {
  // 基本参数
  threads: 8,                     // 启动线程
  showProgress: true,             // 显示进度
  
  // 数据控制
  loadData: true,                 // 明确启用数据加载
  skipBinlog: true,               // 跳过binlog记录
  bytesPerChunk: "64M",           // 数据分块大小
  
  // DDL控制
  loadDdl: true,
  createInvisiblePKs: true,       // 避免与自动建库冲突
  deferTableIndexes: "all",       // 延迟索引创建:"off" | "all" | "fulltext"
  
  // 错误处理
  ignoreExistingObjects: false,   // 忽略已存在对象
  resetProgress: true,            // 清除之前的错误进度
  
  // 字符集
  characterSet: "utf8mb4"
});

Python 恢复

python
util.load_dump("/backup/full", {
  # 基本参数
  "threads": 8,                     # 启动线程
  "showProgress": True,             # 显示进度
  
  # 数据控制
  "loadData": True,                 # 明确启用数据加载
  "skipBinlog": True,               # 跳过binlog记录
  "bytesPerChunk": "64M",           # 数据分块大小
  
  # DDL控制
  "loadDdl": True,
  "createInvisiblePKs": True,       # 避免与自动建库冲突
  "deferTableIndexes": "all",       # 延迟索引创建:"off" | "all" | "fulltext"
  
  # 错误处理
  "ignoreExistingObjects": False,   # 忽略已存在对象
  "resetProgress": True,            # 清除之前的错误进度
  
  # 字符集
  "characterSet": "utf8mb4"
});


util.load_dump("/tmp/backup", {
    "threads": 8,
    "showProgress": True,
    "loadData": True,
    "skipBinlog": True,
    "deferTableIndexes": "all",
    "characterSet": "utf8mb4",
    "resetProgress": True  # 添加此参数以防之前有失败的导入
})

常见问题解决

  1. 内存不足

    javascript
    util.dumpInstance("/backup/full", {
      threads: 2,  // 减少线程
      maxBytesPerTransaction: "1G"  // 限制事务大小
    });
  2. 网络中断恢复

    python
    util.load_dump("/backup/full", {
      "resetProgress": True,  # 重置进度
      "skipConsistencyChecks": True  # 跳过一致性检查
    })
  3. 版本兼容性

    javascript
    util.dumpInstance("/backup/full", {
      compatibility: [
        "strip_restricted_grants",
        "force_innodb",
        "strip_definers"
      ]
    });
  4. local_infile安全限制

    local_infile参数控制是否允许从客户端机器加载文件数据,默认关闭是防止恶意文件注入。MySQL 8.0+ 对此有更严格的安全要求。

    sql
    -- 临时解决
    SET GLOBAL local_infile = ON;
    -- 导入完成后建议恢复设置
    SET GLOBAL local_infile = OFF;
    
    
    -- 永久解决方案(需重启)
    -- 修改配置文件 my.cnf/my.ini
    [mysqld]
    local_infile = ON
    
    -- 然后重启MySQL服务