PbootCMS SQLite数据库怎么转MySQL?详细迁移教程

PbootCMS技术 2026-11-09 家兴网络 13 阅读 来源:本站
正文内容 ARTICLE

PbootCMS 默认支持 SQLite 和 MySQL 两种数据库。当网站文章数量不断增加、数据查询压力增大,或者需要进行数据库管理和性能优化时,部分站长会考虑将 SQLite 数据库迁移到 MySQL。本文详细介绍 PbootCMS 从 SQLite 转换为 MySQL 的操作流程,包括数据库备份、MySQL 环境准备、数据迁移、程序配置修改以及迁移后的检查工作,帮助站长安全完成数据库转换。

PbootCMS-SQLite-转-MySQL-迁移教程.jpg

一、为什么要将 PbootCMS SQLite 转换为 MySQL?

很多站长在使用 PbootCMS 搭建企业网站时,初期通常选择 SQLite 数据库。SQLite 不需要单独安装数据库服务,配置简单,占用资源较少,对于内容量不大的企业网站来说比较方便。

但是,随着网站运营时间增长,文章、产品、栏目以及访问记录不断增加,SQLite 在多用户并发访问、数据库管理和维护方面可能逐渐无法满足部分网站的需求。

这时候,可以考虑将 SQLite 数据库转换为 MySQL。

两种数据库各有特点:

  • SQLite:无需独立数据库服务,部署简单,适合轻量级网站。

  • MySQL:支持独立数据库服务,管理工具丰富,适合需要独立管理数据库和应对多用户访问的业务场景。

  • MySQL:方便进行数据库备份、恢复、权限管理以及后续维护。

需要注意,转换为 MySQL 并不意味着网站速度一定会提升。网站实际访问速度还受到服务器配置、PHP环境、缓存机制、SQL查询和网站程序结构等因素影响。

二、PbootCMS SQLite 转 MySQL 前的准备工作

正式迁移之前,建议先做好以下准备。

1. 备份原始网站文件

通过宝塔面板、FTP或者服务器文件管理工具,将整个网站程序下载备份。

重点关注以下内容:

  • 网站根目录下的 PHP 程序文件

  • config 目录中的数据库配置文件

  • data 目录中的 SQLite 数据库文件

  • static、uploads 等静态资源和上传文件目录

不同版本的 PbootCMS 文件结构可能有所区别,具体以实际安装目录为准。

2. 备份 SQLite 数据库

找到网站当前使用的 SQLite 数据库文件,通常是 .db 格式。

将数据库文件单独复制一份,保存到网站根目录之外。

建议在迁移之前暂停后台内容编辑,避免备份之后又产生新的文章或产品数据,导致迁移前后的数据不一致。

3. 准备 MySQL 数据库

登录宝塔面板,进入数据库管理,创建一个新的 MySQL 数据库。

需要记录以下信息:

  • 数据库名称

  • 数据库用户名

  • 数据库密码

  • 数据库服务器地址

  • 数据库端口

建议使用独立数据库账号,并设置合理的访问权限。

三、创建 MySQL 数据库

以宝塔面板为例:

  1. 登录服务器宝塔面板。

  2. 找到左侧菜单中的数据库。

  3. 点击添加数据库。

  4. 数据库类型选择 MySQL。

  5. 设置数据库名称和用户名。

  6. 设置数据库密码。

  7. 确认创建。

创建完成后,可以通过 phpMyAdmin 或其他 MySQL 数据库管理工具查看数据库。

注意:MySQL 版本、字符集和排序规则需要与当前 PbootCMS 程序的兼容要求相匹配。一般中文网站可以优先考虑 utf8mb4 字符集。

四、SQLite 数据如何迁移到 MySQL?

这是整个转换过程中最关键的一步。

SQLite 和 MySQL 虽然都属于关系型数据库,但两者在数据类型、SQL语法、字段定义、自增机制、索引以及默认值处理方面存在差异。

因此,不能简单地把 SQLite 数据库文件改名为 MySQL 数据库文件,也不能直接将 SQLite 文件导入 MySQL。

常见迁移方式有以下两种。

方法一:使用数据库迁移工具

可以借助数据库迁移脚本或专门的数据转换工具,将 SQLite 数据库中的表结构和数据转换为 MySQL 可以识别的格式。

迁移时需要重点检查:

  1. 数据表结构是否完整。

  2. 字段类型是否正确转换。

  3. 主键和自增字段是否正常。

  4. 索引是否正确创建。

  5. 中文内容是否出现乱码。

  6. NULL、默认值和特殊字符是否正常。

  7. 时间字段和日期格式是否一致。

部分工具能够自动处理常见字段类型,但并不能保证完全兼容 PbootCMS 的所有数据库结构。

方法二:使用 PHP 编写数据迁移程序

如果需要更灵活地控制迁移过程,可以编写 PHP 迁移脚本。

基本流程如下:

第一步:通过 PDO 连接 SQLite 数据库。

第二步:获取 SQLite 中的所有数据表。

第三步:读取每张表的字段结构。

第四步:根据字段类型转换规则,生成 MySQL 建表语句。

第五步:逐表读取原始数据,并分批写入 MySQL。

第六步:检查数据条数、字段内容和主键。

第七步:确认迁移完成后,再切换 PbootCMS 数据库配置。

这里需要特别注意:PbootCMS 不同版本的数据库结构和程序代码可能存在差异,迁移脚本必须结合实际数据库结构进行适配,不能直接套用其他 CMS 的数据库转换脚本。

对于数据量较大的网站,建议采用分批读取、分批写入的方式,避免一次性加载大量数据造成 PHP 内存不足或执行超时。

<?php
/**
 * PbootCMS SQLite -> MySQL 数据迁移工具
 * 建议 PHP 7.4+
 *
 * 功能:
 * 1. 自动读取 SQLite 数据表
 * 2. 自动转换字段类型
 * 3. 自动创建 MySQL 数据表
 * 4. 分批迁移数据
 * 5. 尽可能保留主键、自增字段和普通索引
 * 6. 统计迁移数量
 *
 * 注意:
 * 仅支持 CLI 命令行运行,禁止直接通过浏览器访问。
 */

// ==================== 1. 数据库配置 ====================

// SQLite 原数据库文件路径
$sqliteFile = __DIR__ . '/data/data.db';

// MySQL 连接配置
$mysqlConfig = [
    'host'     => '127.0.0.1',
    'port'     => '3306',
    'database' => 'pbootcms',
    'username' => 'root',
    'password' => '修改为你的数据库密码',
    'charset'  => 'utf8mb4',
];

// 每批迁移数量
$batchSize = 500;

// ==================== 2. 安全检查 ====================

// 必须通过 CLI 执行
if (PHP_SAPI !== 'cli') {
    exit("错误:禁止通过浏览器运行此脚本!
");
}

// 检查 PDO 扩展
if (!extension_loaded('pdo') ||
    !extension_loaded('pdo_sqlite') ||
    !extension_loaded('pdo_mysql')) {
    exit("错误:请检查 PDO、pdo_sqlite、pdo_mysql 扩展是否启用!
");
}

// 检查 SQLite 文件
if (!is_file($sqliteFile)) {
    exit("错误:找不到 SQLite 数据库文件:{$sqliteFile}
");
}

// ==================== 3. 辅助函数 ====================

// SQLite 标识符转义
function sqliteQuote($name)
{
    return '"' . str_replace('"', '""', $name) . '"';
}

// MySQL 标识符转义
function mysqlQuote($name)
{
    return '`' . str_replace('`', '``', $name) . '`';
}

// 输出日志
function logMessage($message)
{
    echo '[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL;
}

// SQLite 字段类型转换为 MySQL
function convertType($type)
{
    $type = strtoupper(trim($type));

    if (preg_match('/^VARCHARs*(s*(d+)s*)$/', $type, $m)) {
        $length = (int)$m[1];
        return 'VARCHAR(' . min(max($length, 1), 16383) . ')';
    }

    if (preg_match('/^CHARs*(s*(d+)s*)$/', $type, $m)) {
        return 'CHAR(' . min(max((int)$m[1], 1), 255) . ')';
    }

    if (strpos($type, 'BIGINT') !== false) {
        return 'BIGINT';
    }

    if (strpos($type, 'INT') !== false) {
        return 'BIGINT';
    }

    if (strpos($type, 'BOOL') !== false) {
        return 'TINYINT(1)';
    }

    if (strpos($type, 'REAL') !== false ||
        strpos($type, 'FLOA') !== false ||
        strpos($type, 'DOUB') !== false) {
        return 'DOUBLE';
    }

    if (strpos($type, 'DECIMAL') !== false ||
        strpos($type, 'NUMERIC') !== false) {
        return 'DECIMAL(30,10)';
    }

    if (strpos($type, 'BLOB') !== false) {
        return 'LONGBLOB';
    }

    if (strpos($type, 'DATE') !== false ||
        strpos($type, 'TIME') !== false) {
        return 'DATETIME';
    }

    if (strpos($type, 'CHAR') !== false ||
        strpos($type, 'CLOB') !== false ||
        strpos($type, 'TEXT') !== false) {
        return 'LONGTEXT';
    }

    // SQLite 未声明类型或其他类型
    return 'LONGTEXT';
}

// ==================== 4. 连接数据库 ====================

try {

    $sqlite = new PDO(
        'sqlite:' . $sqliteFile,
        null,
        null,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]
    );

    $mysqlDsn = sprintf(
        'mysql:host=%s;port=%s;dbname=%s;charset=%s',
        $mysqlConfig['host'],
        $mysqlConfig['port'],
        $mysqlConfig['database'],
        $mysqlConfig['charset']
    );

    $mysql = new PDO(
        $mysqlDsn,
        $mysqlConfig['username'],
        $mysqlConfig['password'],
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]
    );

    logMessage('SQLite 和 MySQL 数据库连接成功');

    // 目标数据库必须为空,防止重复写入或覆盖原数据
    $existingTables = $mysql->query(
        "SELECT COUNT(*) FROM information_schema.tables
         WHERE table_schema = DATABASE()
         AND table_type = 'BASE TABLE'"
    )->fetchColumn();

    if ((int)$existingTables > 0) {
        exit("错误:目标 MySQL 数据库不为空,请使用新建的空数据库!
");
    }

    // 暂时关闭外键检查
    $mysql->exec('SET FOREIGN_KEY_CHECKS=0');

    // ==================== 5. 获取 SQLite 数据表 ====================

    $tables = $sqlite->query(
        "SELECT name FROM sqlite_master
         WHERE type='table'
         AND name NOT LIKE 'sqlite_%'
         ORDER BY name"
    )->fetchAll(PDO::FETCH_COLUMN);

    if (!$tables) {
        exit("没有发现需要迁移的数据表!
");
    }

    logMessage('发现 ' . count($tables) . ' 张数据表');

    $totalRows = 0;
    $successTables = 0;

    // ==================== 6. 循环迁移数据表 ====================

    foreach ($tables as $table) {

        logMessage("开始迁移数据表:{$table}");

        // 获取字段结构
        $columns = $sqlite->query(
            'PRAGMA table_info(' . sqliteQuote($table) . ')'
        )->fetchAll(PDO::FETCH_ASSOC);

        if (!$columns) {
            logMessage("跳过空结构数据表:{$table}");
            continue;
        }

        $definitions = [];
        $columnNames = [];
        $primaryKeys = [];
        $autoIncrement = false;

        // 分析字段
        foreach ($columns as $column) {

            $name = $column['name'];
            $type = $column['type'];

            $columnNames[] = $name;

            $mysqlType = convertType($type);

            $definition = mysqlQuote($name) . ' ' . $mysqlType;

            $isPrimary = ((int)$column['pk'] > 0);

            // SQLite INTEGER 单字段主键转换为 MySQL 自增主键
            if (
                strtoupper(trim($type)) === 'INTEGER' &&
                $isPrimary &&
                count(array_filter($columns, function ($item) {
                    return (int)$item['pk'] > 0;
                })) === 1
            ) {
                $definition = mysqlQuote($name) .
                    ' BIGINT NOT NULL AUTO_INCREMENT';
                $autoIncrement = true;
            } else {

                if ((int)$column['notnull'] === 1 || $isPrimary) {
                    $definition .= ' NOT NULL';
                }

                // 保留原始默认值
                if ($column['dflt_value'] !== null) {
                    $definition .= ' DEFAULT ' . $column['dflt_value'];
                }
            }

            $definitions[] = $definition;

            if ($isPrimary) {
                $primaryKeys[(int)$column['pk']] = $name;
            }
        }

        // 创建联合主键
        if ($primaryKeys) {

            ksort($primaryKeys);

            $pkFields = array_map(
                'mysqlQuote',
                array_values($primaryKeys)
            );

            $definitions[] = 'PRIMARY KEY (' .
                implode(',', $pkFields) . ')';
        }

        // 创建 MySQL 数据表
        $createSQL = 'CREATE TABLE ' .
            mysqlQuote($table) .
            " (
" .
            implode(",
", $definitions) .
            "
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";

        $mysql->exec($createSQL);

        logMessage("数据表创建成功:{$table}");

        // ==================== 7. 迁移数据 ====================

        $fields = array_map('mysqlQuote', $columnNames);

        $placeholders = array_fill(
            0,
            count($columnNames),
            '?'
        );

        $insertSQL = 'INSERT INTO ' .
            mysqlQuote($table) .
            ' (' . implode(',', $fields) . ') VALUES (' .
            implode(',', $placeholders) . ')';

        $insertStmt = $mysql->prepare($insertSQL);

        $selectStmt = $sqlite->query(
            'SELECT * FROM ' . sqliteQuote($table)
        );

        $tableRows = 0;
        $mysql->beginTransaction();

        try {

            while ($row = $selectStmt->fetch(PDO::FETCH_ASSOC)) {

                $values = [];

                foreach ($columnNames as $columnName) {
                    $values[] = $row[$columnName];
                }

                $insertStmt->execute($values);

                $tableRows++;

                // 分批提交,控制事务大小
                if ($tableRows % $batchSize === 0) {
                    $mysql->commit();
                    $mysql->beginTransaction();

                    logMessage(
                        "{$table} 已迁移 {$tableRows} 条记录"
                    );
                }
            }

            if ($mysql->inTransaction()) {
                $mysql->commit();
            }

        } catch (Throwable $e) {

            if ($mysql->inTransaction()) {
                $mysql->rollBack();
            }

            throw $e;
        }

        logMessage("数据表 {$table} 迁移完成,共 {$tableRows} 条");

        // ==================== 8. 迁移普通索引 ====================

        $indexes = $sqlite->query(
            'PRAGMA index_list(' . sqliteQuote($table) . ')'
        )->fetchAll(PDO::FETCH_ASSOC);

        foreach ($indexes as $index) {

            $indexName = $index['name'];

            // 跳过 SQLite 自动生成的索引
            if (strpos($indexName, 'sqlite_autoindex') === 0) {
                continue;
            }

            // 部分索引无法直接兼容
            if (isset($index['partial']) && (int)$index['partial'] === 1) {
                logMessage("跳过部分索引:{$indexName}");
                continue;
            }

            $indexColumns = $sqlite->query(
                'PRAGMA index_info(' . sqliteQuote($indexName) . ')'
            )->fetchAll(PDO::FETCH_ASSOC);

            $indexFields = [];
            $unsupported = false;

            foreach ($indexColumns as $indexColumn) {

                // 表达式索引不在本脚本支持范围内
                if (
                    $indexColumn['name'] === null ||
                    (int)$indexColumn['cid'] < 0
                ) {
                    $unsupported = true;
                    break;
                }

                $sourceColumn = null;

                foreach ($columns as $col) {
                    if ($col['name'] === $indexColumn['name']) {
                        $sourceColumn = $col;
                        break;
                    }
                }

                // LONGTEXT 等字段不能直接建立普通 MySQL 索引
                if (
                    $sourceColumn &&
                    preg_match(
                        '/TEXT|CLOB|BLOB/i',
                        $sourceColumn['type']
                    )
                ) {
                    $unsupported = true;
                    break;
                }

                $indexFields[] = mysqlQuote($indexColumn['name']);
            }

            if ($unsupported || !$indexFields) {
                logMessage("跳过不兼容索引:{$indexName}");
                continue;
            }

            $unique = ((int)$index['unique'] === 1)
                ? 'UNIQUE '
                : '';

            try {

                $mysql->exec(
                    'CREATE ' . $unique . 'INDEX ' .
                    mysqlQuote($indexName) .
                    ' ON ' . mysqlQuote($table) .
                    ' (' . implode(',', $indexFields) . ')'
                );

                logMessage("索引迁移成功:{$indexName}");

            } catch (Throwable $e) {

                logMessage(
                    "索引迁移失败:{$indexName}," .
                    $e->getMessage()
                );
            }
        }

        $totalRows += $tableRows;
        $successTables++;

    }

    // ==================== 9. 完成迁移 ====================

    $mysql->exec('SET FOREIGN_KEY_CHECKS=1');

    logMessage('================================');
    logMessage('SQLite 转 MySQL 迁移完成');
    logMessage("成功处理数据表:{$successTables}");
    logMessage("累计迁移记录:{$totalRows}");
    logMessage('================================');

} catch (Throwable $e) {

    if (isset($mysql) && $mysql->inTransaction()) {
        $mysql->rollBack();
    }

    if (isset($mysql)) {
        try {
            $mysql->exec('SET FOREIGN_KEY_CHECKS=1');
        } catch (Throwable $ignored) {
        }
    }

    logMessage('迁移失败:' . $e->getMessage());
    exit(1);
}

五、修改 PbootCMS 数据库配置

数据库数据迁移完成后,需要修改 PbootCMS 的数据库连接配置。

不同版本的 PbootCMS 配置文件路径和配置项可能有所区别,常见配置文件位于 config 目录下。

操作前先备份原始配置文件。

找到当前 SQLite 数据库配置,将数据库类型切换为 MySQL,并填写前面创建的数据库连接信息。

配置内容需要根据当前 PbootCMS 版本实际使用的配置格式填写,不能直接复制其他版本的配置文件覆盖。

重点检查以下参数:

  • 数据库驱动类型

  • 数据库服务器地址

  • 数据库端口

  • 数据库名称

  • 数据库用户名

  • 数据库密码

  • 数据库字符集

修改完成后保存文件。

建议先在测试环境中验证数据库连接,确认连接正常后再切换正式网站。

六、PbootCMS 数据库转换完成后如何检查?

数据库迁移并不代表整个工作已经结束,还需要进行全面测试。

建议重点检查以下项目:

1. 前台网站访问

打开网站首页、栏目页、文章详情页和产品详情页,确认页面能够正常显示。

2. 后台登录

进入 PbootCMS 后台,检查管理员账号是否能够正常登录。

3. 文章数据检查

检查文章标题、正文、发布时间、栏目归属以及文章数量是否正常。

4. 产品数据检查

查看产品分类、产品详情、产品图片和相关字段是否完整。

5. 图片资源检查

确认原有上传图片、产品图片和附件能够正常访问。

6. 网站功能检查

测试搜索、留言、表单提交、分页、栏目导航等功能。

7. URL 地址检查

检查原有网站链接是否发生变化,确保文章和产品页面的 URL 规则保持一致,避免影响已有搜索引擎收录。

8. 数据写入检查

在测试环境中新增一篇文章,修改一条产品信息,再次访问前台,确认 MySQL 数据库能够正常读写。

七、PbootCMS SQLite 转 MySQL 常见问题

1. 数据库转换后网站提示连接失败怎么办?

首先检查数据库名称、用户名、密码和服务器地址是否填写正确。

其次检查服务器 MySQL 服务是否正常运行,以及 PHP 是否启用了对应的 PDO MySQL 扩展。

还需要检查当前 PbootCMS 版本是否支持所使用的 MySQL 版本。

2. 数据迁移后中文出现乱码怎么办?

通常与数据库字符集、连接字符集或字段编码有关。

建议检查 SQLite 原始数据、MySQL 数据库和 PHP 数据库连接的字符集设置,中文网站可以考虑统一使用 utf8mb4。

如果原始数据已经发生编码损坏,单纯修改字符集无法自动恢复原始内容。

3. 转换后文章数量不一致怎么办?

需要检查迁移脚本是否遗漏数据表、是否存在批次中断,以及是否有数据写入失败的情况。

建议逐表对比 SQLite 和 MySQL 的数据记录数量,并重点检查文章表、产品表、栏目表和用户表。

4. 转换后后台可以登录,但前台页面报错怎么办?

这种情况可能与部分数据表没有迁移完整、字段类型不兼容、SQL语法差异或程序数据库适配有关。

建议查看 PHP 错误日志和数据库错误日志,根据具体报错定位问题。

5. SQLite 转 MySQL 后网站速度会更快吗?

不一定。

MySQL 在多用户并发访问、数据库管理和复杂查询方面具有一定优势,但数据库转换并不是网站性能优化的全部。

如果网站打开速度较慢,还需要检查:

  • 数据库查询是否存在重复执行。

  • 文章列表是否一次性读取过多数据。

  • 网站是否启用合理的缓存机制。

  • PHP版本及服务器资源是否满足运行要求。

  • 图片是否经过压缩和优化。

  • 网站程序是否存在不必要的数据库查询。

八、PbootCMS 数据库转换要注意什么?

PbootCMS 从 SQLite 转换为 MySQL,主要包括原始数据库备份、MySQL 数据库创建、数据结构转换、数据迁移、程序配置修改以及迁移后的功能测试。

对于文章数量较多、需要独立管理数据库或有后续扩展需求的网站,可以根据实际情况考虑使用 MySQL。

但需要提醒的是,数据库迁移涉及网站核心数据,操作之前一定要备份完整的网站文件和数据库。建议先在测试环境完成转换,确认前后台功能、数据完整性和页面链接均正常后,再切换正式网站。

数据库转换不是简单修改一个配置文件,而是一次完整的数据迁移工作。只有确保数据完整、程序兼容和网站功能正常,才能算真正完成 SQLite 到 MySQL 的转换。

关键词:PbootCMS数据库转换, PbootCMS SQLite转MySQL, PbootCMS数据库迁移, PbootCMS MySQL配置, PbootCMS网站优化

相关推荐

RELATED