在 Yii2 中批量添加数据(批量插入 MySQL),核心是利用 ActiveRecordbatchInsert() 方法或 Query 构建器的 batchInsert(),避免循环调用 save() 导致的性能问题(减少数据库连接次数,一次 SQL 插入多条数据)。

以下是 3 种实用方案(按推荐优先级排序),覆盖不同场景(简单批量插入、带验证、关联数据批量插入),并兼容你的 Yii2 2.0.53 版本:

一、核心前提

假设模型为 User(对应表 sp_user),需批量插入的字段为 nameagestatus,待插入数据格式如下:

// 原始批量数据(可含多余字段,后续会过滤)
$batchData = [
    ['name' => '张三', 'age' => 20, 'status' => 1, 'extra' => '无效字段'],
    ['name' => '李四', 'age' => 22, 'status' => 1, 'extra' => '无效字段'],
    ['name' => '王五', 'age' => 25, 'status' => 0, 'extra' => '无效字段'],
];

方案 1:ActiveRecord::batchInsert()(最简洁,推荐)

Yii2 ActiveRecord 静态方法 batchInsert(),直接构造批量插入 SQL,性能最优(一次 SQL 执行),支持自动过滤无效字段。

用法示例:

use app\models\User;

// 步骤 1:定义允许插入的字段(避免无效字段报错)
$allowedFields = ['name', 'age', 'status'];

// 步骤 2:过滤批量数据中的无效字段(仅保留允许的字段)
$validBatchData = [];
foreach ($batchData as $item) {
    // 仅保留 $allowedFields 中的字段,自动过滤 extra 等无效字段
    $validItem = array_intersect_key($item, array_flip($allowedFields));
    $validBatchData[] = $validItem;
}

// 步骤 3:批量插入(核心方法)
$rowCount = User::batchInsert(
    $allowedFields, // 允许插入的字段(必须与表字段一致)
    $validBatchData // 过滤后的批量数据
)->execute();

// 结果:$rowCount 为成功插入的行数(此处为 3)
echo "批量插入成功,共插入 {$rowCount} 条数据";

生成的 SQL(性能最优):

INSERT INTO `sp_user` (`name`, `age`, `status`) 
VALUES ('张三', 20, 1), ('李四', 22, 1), ('王五', 25, 0);

特点:

  • 性能最好(一次 SQL 插入多条,无循环连接数据库);
  • 需手动指定允许插入的字段(避免无效字段);
  • 不触发模型的 beforeSave()/afterSave() 钩子,不执行数据验证(需提前确保数据合法)。

方案 2:带数据验证的批量插入(安全优先)

若需对批量数据进行验证(如 name 必填、age 为整数),需循环验证每条数据,再批量插入,兼顾安全与性能。

用法示例:

use app\models\User;
use yii\db\Exception;

$allowedFields = ['name', 'age', 'status'];
$validBatchData = [];

// 步骤 1:循环验证每条数据(触发模型规则)
foreach ($batchData as $item) {
    $model = new User();
    // 加载数据并过滤无效字段
    $model->load(['User' => $item], '');
    // 验证数据(触发模型 rules() 中的规则)
    if ($model->validate()) {
        // 仅保留允许插入的字段,加入有效数据集合
        $validItem = $model->getAttributes($allowedFields);
        $validBatchData[] = $validItem;
    } else {
        // 处理验证失败的情况(如打印错误信息)
        echo "数据验证失败:" . json_encode($model->errors) . "\n";
    }
}

// 步骤 2:批量插入验证通过的数据
if (!empty($validBatchData)) {
    try {
        $rowCount = User::batchInsert($allowedFields, $validBatchData)->execute();
        echo "批量插入成功,共插入 {$rowCount} 条数据";
    } catch (Exception $e) {
        echo "插入失败:" . $e->getMessage();
    }
}

特点:

  • 触发模型验证(rules())和属性过滤(getAttributes()),数据更安全;
  • 验证失败的数据会被跳过,不影响整体插入;
  • 性能略低于方案 1(多了循环验证步骤),但仍优于循环 save()

方案 3:Query 构建器批量插入(兼容非 ActiveRecord 场景)

若不依赖 ActiveRecord 模型(如临时表、视图),可直接用 Query 构建器的 batchInsert(),用法与方案 1 类似,但更灵活。

用法示例:

use yii\db\Query;
use yii\db\Exception;

$allowedFields = ['name', 'age', 'status'];
$validBatchData = [];

// 过滤无效字段(同方案 1)
foreach ($batchData as $item) {
    $validItem = array_intersect_key($item, array_flip($allowedFields));
    $validBatchData[] = $validItem;
}

// 批量插入(指定表名和字段)
try {
    $rowCount = (new Query())
        ->insert('sp_user', $allowedFields) // 表名 + 字段
        ->batchInsert($validBatchData) // 批量数据
        ->execute();
    echo "批量插入成功,共插入 {$rowCount} 条数据";
} catch (Exception $e) {
    echo "插入失败:" . $e->getMessage();
}

特点:

  • 不依赖 ActiveRecord 模型,直接操作表名;
  • 语法简洁,性能与方案 1 一致;
  • 同样不触发模型钩子和验证,需手动确保数据合法。

二、关键优化与避坑要点

1. 性能优化:控制单次插入数量

MySQL 对单次插入的条数有默认限制(取决于 max_allowed_packet 配置),若批量数据超过 1000 条,建议分批次插入(避免 SQL 过长导致失败):

$batchSize = 1000; // 每批插入 1000 条
$totalData = $validBatchData;
$totalCount = count($totalData);

for ($i = 0; $i < $totalCount; $i += $batchSize) {
    // 截取每批数据
    $batch = array_slice($totalData, $i, $batchSize);
    User::batchInsert($allowedFields, $batch)->execute();
}

2. 自动过滤无效字段(简化写法)

若不想手动定义 $allowedFields,可通过模型的 attributes() 自动获取所有字段(对应数据库表字段):

// 自动获取模型所有有效字段(无需手动写 $allowedFields)
$allowedFields = $model->attributes(); // 或 User::getTableSchema()->columnNames;

// 过滤批量数据
foreach ($batchData as $item) {
    $validItem = array_intersect_key($item, array_flip($allowedFields));
    $validBatchData[] = $validItem;
}

3. 处理自增 ID

若表有自增 ID(如 id),无需在 $allowedFields 中包含 id,MySQL 会自动生成自增 ID。

4. 事务支持(确保数据一致性)

若批量插入需保证「要么全成功,要么全失败」,需包裹事务:

use yii\db\Transaction;

$db = User::getDb();
$transaction = $db->beginTransaction(Transaction::READ_COMMITTED);

try {
    // 批量插入
    $rowCount = User::batchInsert($allowedFields, $validBatchData)->execute();
    $transaction->commit(); // 全部成功,提交事务
    echo "批量插入成功,共插入 {$rowCount} 条数据";
} catch (Exception $e) {
    $transaction->rollBack(); // 部分失败,回滚事务
    echo "插入失败:" . $e->getMessage();
}

5. 兼容 JSON 字段(若有)

若批量插入包含 JSON 字段(如 profile),需确保数据为数组或 JSON 字符串,Yii2 会自动处理:

$batchData = [
    ['name' => '张三', 'profile' => ['age' => 20, 'city' => '北京']], // 数组格式
    ['name' => '李四', 'profile' => json_encode(['age' => 22, 'city' => '上海'])], // JSON 字符串格式
];
// 直接批量插入,Yii2 会自动将数组转为 JSON 字符串
User::batchInsert(['name', 'profile'], $batchData)->execute();

三、场景对比与选择

方案优点缺点适用场景
方案 1(ActiveRecord::batchInsert)性能最优,简洁无验证,无钩子数据已验证、无需触发模型逻辑
方案 2(带验证)数据安全,支持验证性能略低需验证数据(如用户输入数据)
方案 3(Query 构建器)不依赖 ActiveRecord无模型相关功能临时表、视图、非模型场景

推荐优先级:

  • 大多数场景(数据合法、无需验证)→ 方案 1;
  • 用户输入数据、需验证 → 方案 2;
  • 非 ActiveRecord 场景 → 方案 3。

四、最终简化写法(方案 1 + 自动过滤字段 + 事务)

use app\models\User;
use yii\db\Exception;
use yii\db\Transaction;

$batchData = [/* 你的批量数据 */];
$model = new User();
$allowedFields = $model->attributes(); // 自动获取所有有效字段
$validBatchData = [];

// 自动过滤无效字段
foreach ($batchData as $item) {
    $validItem = array_intersect_key($item, array_flip($allowedFields));
    $validBatchData[] = $validItem;
}

// 事务 + 批量插入
$db = User::getDb();
$transaction = $db->beginTransaction(Transaction::READ_COMMITTED);

try {
    if (!empty($validBatchData)) {
        $rowCount = User::batchInsert($allowedFields, $validBatchData)->execute();
    }
    $transaction->commit();
    echo "批量插入成功,共插入 {$rowCount} 条数据";
} catch (Exception $e) {
    $transaction->rollBack();
    echo "插入失败:" . $e->getMessage();
}

这种写法兼顾了 性能、安全性、简洁性,能自动过滤无效字段,避免「字段不存在」报错,同时通过事务保证数据一致性,适合绝大多数批量添加场景。

一、PHP SG11 是什么?

SG11 是一款 PHP 代码加密/混淆工具(全称:SourceGuardian 11),核心作用是对 PHP 源代码进行加密、混淆和授权保护,防止代码被篡改、盗用或逆向破解。

它的本质是:将人类可读的 PHP 源码(明文)转换为机器可执行但人类难以理解的加密格式,同时提供运行时解密机制(需配合 SG11 扩展),确保加密后的代码能在服务器正常运行,但无法被轻易还原为原始源码。

二、核心功能(为什么开源项目会用它)

开源项目使用 SG11,核心诉求是 “开源但不完全开放核心”“保护商业权益”,具体场景如下:

1. 保护核心逻辑/商业机密(最核心用途)

很多“开源项目”并非 100% 开源:

  • 项目主体功能开源(吸引用户、共建生态),但 核心算法、付费模块、关键业务逻辑 是团队的核心资产(比如 SaaS 对接逻辑、付费插件的核心功能)。
  • 若这些核心代码明文开源,可能被竞争对手抄袭、恶意篡改,或被用户绕过付费机制(比如破解授权)。
  • SG11 加密后,代码无法被直接阅读和修改,能有效阻挡大部分非专业破解,保护商业利益。

2. 防止代码篡改与恶意使用

开源项目的明文代码可被任意修改:

  • 恶意用户可能篡改代码植入后门、挖矿脚本,或修改版权信息后二次分发(冒充原创)。
  • 加密后的代码无法直接修改,即使强行修改也会导致运行报错,确保项目在用户环境中运行的是“官方纯净版”,保障项目声誉和用户安全

3. 实现授权控制(付费开源项目常用)

很多开源项目采用“开源免费+付费增值”模式:

  • SG11 支持结合授权机制(比如绑定服务器 IP、域名、有效期),加密后的代码需验证授权密钥才能运行。
  • 例如:开源项目的基础版免费(明文),高级功能模块(加密)需付费购买授权码才能启用,避免用户“白嫖”付费功能。

4. 轻量化保护,不影响运行效率

SG11 属于 “运行时解密”,加密后的代码在执行时由 SG11 扩展实时解密(解密过程在内存中完成,不落地明文),相比其他加密工具

  • 性能损耗极低(几乎不影响项目运行速度);
  • 兼容性强(支持 PHP 5.2+ 到 PHP 8.2+ 全版本,支持 Windows/Linux 等主流系统);
  • 部署简单(仅需在服务器安装 SG11 扩展,无需修改项目架构)。

三、开源项目使用 SG11 的争议与注意事项

虽然 SG11 能保护核心利益,但也存在一些争议,需理性看待:

  1. “开源精神”的冲突:部分开源社区认为“加密代码违背开源的开放透明原则”,因此纯公益开源项目(无商业诉求)很少使用;但“商业开源项目”(如 Laravel 生态的部分付费扩展、企业级开源软件)使用较为普遍,属于“开源商业模式的折中方案”。
  2. 依赖扩展风险:加密后的代码必须在安装了 SG11 扩展的服务器上才能运行,增加了用户的部署成本(需手动安装扩展),若扩展版本不兼容可能导致代码无法运行。
  3. 无法完全防破解:SG11 能阻挡普通用户和初级破解者,但专业破解者可通过内存dump、逆向扩展等方式还原代码(只是门槛极高,多数场景下足够安全)。

总结

SG11 是 PHP 项目的 “代码保镖”,核心价值是“加密保护+授权控制”。开源项目使用它,本质是在“开源生态”和“商业利益”之间找平衡——既通过开源吸引用户、共建社区,又通过加密保护核心资产,避免被抄袭或恶意使用,尤其适合“商业开源”(开源免费+付费增值)模式。

在 Git 中,CRLF(Carriage Return + Line Feed) 是 Windows 系统的换行符,而 Linux/macOS 用 LF(Line Feed)。Git 默认会自动转换换行符(Windows 下拉取时 LF→CRLF,提交时 CRLF→LF),若想禁用这种自动转换(即 “不设置 CRLF”),核心是通过 Git 配置关闭换行符自动处理,具体操作如下:
一、核心配置:禁用 CRLF 自动转换
通过 core.autocrlf 配置控制换行符转换,禁用需设置为 false,支持全局配置(所有仓库)或局部配置(当前仓库)。

  1. 全局配置(推荐,所有 Git 仓库生效)
    打开终端 / 命令行,执行:

    git config --global core.autocrlf false
  2. 局部配置(仅当前仓库生效)
    进入项目的 Git 仓库根目录,执行:

    git config core.autocrlf false

    配置说明:
    core.autocrlf false:Git 不做任何换行符转换,工作区文件的换行符完全由你本地编辑器 / 系统决定(Windows 保留 CRLF,Linux/macOS 保留 LF);
    对比默认值 true(Windows 下自动转换)和 input(仅提交时 CRLF→LF,拉取不转换),false 是彻底禁用转换。
    二、补充配置:避免 Git 标记文件为 “已修改”(可选)
    若禁用转换后,Git 仍误判文件因换行符变化为 “已修改”,需配置 core.safecrlf 关闭换行符检查:

    # 全局禁用换行符检查(推荐)
    git config --global core.safecrlf false
    core.safecrlf

    说明:
    true(默认):提交时若存在混合换行符,Git 会报错阻止提交;
    false:关闭检查,允许混合换行符提交

检查并会阻止提交

git config --global core.autocrlf true
git config --global --unset core.safecrlf

仅恢复 core.autocrlf 默认值

Windows 系统中 Git 默认core.autocrlf为true,若之前设为false,执行以下命令单独恢复该配置:

git config --global core.autocrlf true

1. 克隆主仓库时直接拉取子模块(首次拉取代码时,默认会拉取子模块,推荐)

# --recursive 递归拉取所有子模块
git clone --recursive git@gitcode.com:xxx/supo-admin.git

2. 已克隆主仓库,后续同步子模块

# 初始化子模块(若未初始化)
git submodule init

# 拉取子模块代码并更新到最新版本
git submodule update --remote

3. 常用子模块维护命令

# 3-1. 更新子模块到远程最新版本
# 只更新 admin-web 子模块
git submodule update --remote admin-web

# 或更新所有子模块
git submodule update --remote

# 3-2. 进入子模块操作(如提交子模块代码)
# 进入子模块目录
cd admin-web
# 拉取子模块的远程更新
git pull origin 子模块分支名

git add .
git commit -m "fix: 子模块修复bug"

# 推送子模块的代码到远程
git push origin master

4. 移除子模块(如需删除)

# 4-1. 解除主仓库对子模块的跟踪
git submodule deinit -f admin-web

# 4-2. 删除子模块的本地目录
rm -rf admin-web

# 4-3. 删除主仓库中 .git 目录下的子模块配置
rm -rf .git/modules/admin-web

# 4-4. 从 .gitmodules 中删除该子模块的配置(或手动编辑 .gitmodules 删除对应段落)
git rm --cached admin-web

# 4-5. 提交删除操作
git commit -m "feat: 移除 admin-web 子模块"
git push

5. 添加子模块

git submodule https://gitcode.com/xxx/merchant-web.git merchant-web

恢复到项目 未执行 git submodule init 的状态,后续可再次拉子模块代码

要让 Git 子模块目录恢复到“未初始化/未拉取代码”的状态(和执行 git submodule init 前一致,仅保留子模块配置,删除本地代码),核心是 保留子模块的 Git 配置(.gitmodules.git/config 中的记录),但删除子模块的本地代码目录和 Git 跟踪文件。以下是具体步骤:

核心逻辑

目标状态:

  • 保留 .gitmodules 文件(子模块配置记录);
  • 保留 .git/config 中可能存在的子模块配置(可选,若之前执行过 init);
  • 删除子模块的本地代码目录(如 submodule-dir/);
  • 删除 Git 对其子模块的跟踪文件(.git/modules/[子模块名])。

具体操作步骤

假设你的子模块目录名为 submodule-dir(替换为实际子模块目录名):

1. 先确保子模块目录无未提交的修改(可选,避免数据丢失)

如果子模块目录中有自己的修改,先备份或提交(否则删除后会丢失):

# 进入子模块目录查看状态
cd submodule-dir
git status
# 若有未提交文件,可备份或提交(根据需求选择)

2. 删除子模块的本地代码目录

直接删除子模块的物理目录(这是“看起来没拉取代码”的核心):

# 回到项目根目录
cd ..
# 删除子模块目录(本地代码全部删除)
rm -rf submodule-dir/

3. 删除 Git 对其子模块的跟踪文件

Git 会将子模块的版本信息、配置等存储在 .git/modules/[子模块名] 目录中,必须删除才能彻底恢复到“未初始化”状态:

# 删除 Git 跟踪的子模块元数据(替换为实际子模块名)
rm -rf .git/modules/submodule-dir/

4. (可选)保留子模块配置(关键!和 git submodule init 前一致)

如果想保留 .gitmodules 中的配置(后续可重新执行 git submodule init && git submodule update 拉取代码),无需删除 .gitmodules 文件

若之前执行过 git submodule init.git/config 中会有子模块的配置记录,若想完全恢复到“从未 init”的状态,可删除该记录:

# 取消 .git/config 中对子模块的配置(可选,不删也不影响“无代码”状态)
git config --unset-all submodule.submodule-dir

最终效果验证

操作完成后,项目状态如下:

  • 子模块目录 submodule-dir/ 不存在(或为空);
  • .gitmodules 文件保留(子模块配置还在);
  • 执行 git submodule status 会显示类似 -xxxxxx submodule-dir (未初始化) 的提示(说明子模块未拉取代码);
  • 后续若需重新拉取子模块,只需执行:

    git submodule init  # 初始化(读取 .gitmodules 配置)
    git submodule update  # 拉取子模块代码

简化命令(一键操作,替换为实际子模块名)

# 替换 submodule-dir 为你的子模块目录名
SUBMODULE_NAME="submodule-dir" && rm -rf $SUBMODULE_NAME && rm -rf .git/modules/$SUBMODULE_NAME && git config --unset-all submodule.$SUBMODULE_NAME

总结

通过“删除子模块目录 + 删除 Git 跟踪元数据”,即可让子模块恢复到“未拉取代码”的初始状态,和未执行 git submodule init/update 时完全一致,且保留了后续重新拉取的配置。