首页 / 资讯中心 / 文章详情

Laravel Get、Cursor、Chunk、Offset 对比:TaoToken 统一 Key 下的大数据量查询实测

Laravel Get、Cursor、Chunk、Offset 对比:TaoToken 统一 Key 下的大数据量查询实测 ★ FEATURED ARTICLE
1. 四万条数据跑下来Laravel 取数方式差在哪Laravel 里从数据库把一批记录读出来写法看着都差不多get()、cursor()、chunk()、offset()-limit()四种方式在代码里可能只差一个方法名但真到几万条数据量级内存和耗时的差距会大到让你怀疑是不是换了台机器。这篇就围绕 Laravel Get、Cursor、Chunk、Offset 对比这个主题把四种取数方式在万级数据下的真实表现摊开讲清楚顺带把 TaoToken 统一 Key 接入 API 跑基准脚本的完整流程给你让你自己也能复现这套压测。先说清楚这四种方式各自是什么、适合谁get()最直白一次性把符合条件的所有记录查出来塞进一个 Collection 返回。适合数据量小、你确实需要全部数据在内存里做二次处理的场景比如导出几百条报表、后台列表页。cursor()返回的是生成器Generator底层用 PDO 的游标逐行读取理论上内存占用极低。适合遍历大表做逐条处理比如给十万用户逐个发通知、逐条同步到别的系统。chunk()按固定大小分页查询每查一批处理一批处理完释放。适合批量更新、批量迁移这类既想控制内存又想拿到「一批」数据的场景。offset()-limit()是手写分页自己算偏移量循环查。适合需要精确控制分页边界、或者要跟外部系统对接分页协议的场景。问题在于很多教程只告诉你「大数据用 chunk」但没告诉你到底多大算大、四种方式的内存峰值差多少倍、什么时候 cursor 反而会炸。我拿一张四万多条记录的表实测了一遍数据摆出来你就明白了。这次实测的环境是本地 Laravel 项目数据库里一张测试表 42629 条记录PHP 内存限制沿用默认的 256MB。基准脚本就是给每种方式单独写一个测试方法用microtime(true)掐耗时用memory_get_peak_usage(true)读内存峰值。为了让脚本能通过统一入口调用模型我用 TaoToken 的 API Key 做了一层统一接入这样基准脚本里请求模型、拉数据、记录日志都走同一个 Key不用在多个服务之间来回切配置。实测结果先给结论细节后面拆取数方式耗时WiFi内存峰值20 万条时表现get()2.43s150MB内存溢出cursor()2.23s13MB内存溢出chunk(1000)5.12s2MB稳健offsetlimit6.16s2MB稳健耗时排序是 offset chunk get cursor内存占用排序是 get cursor offset chunk。注意这两个排序是反的——最快的 cursor 内存不是最低内存最低的 chunk 耗时几乎翻倍。这就是为什么不能一句「大数据用 chunk」糊弄过去。2. TaoToken 统一 Key 接入把基准脚本的请求入口收拢在跑压测之前先把请求入口统一掉。为什么要用 TaoToken因为基准脚本里除了查数据库往往还要调用模型接口做数据校验、生成测试报告、或者把压测结果回传分析。如果每个环节都单独配一套 Key 和 Base URL脚本会变得很难维护换环境时到处改配置。TaoToken 提供统一的 API 入口一个 Key 就能覆盖模型对话、编码辅助这些调用基准脚本里只认一个配置源。TaoToken 是什么、能做什么它是一个统一的大模型 API 接入服务把不同模型的调用收敛到同一个 Base URL 和同一套 Key 管理下。适合谁适合像我这样在本地跑脚本、需要频繁调用模型接口做辅助处理又不想在代码里散落一堆密钥的开发者。你可以在官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 了解整体能力API 入口是 https://taotoken.net/api这个地址不加 UTM 参数直接配到代码里。接入的核心就三件套Base URL、API Key、Model ID。这三样在基准脚本里对应三个配置项缺一不可。很多人配的时候只填了 Key 忘了 Base URL或者 Model ID 写了个不存在的名字结果请求直接 401 或者报模型找不到。下面我把三件套的配置方式给全。先拿 Key。进控制台创建 API Key路径是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 在 API Keys 页面新建一个复制出来。这个 Key 就是后面所有请求的凭证别硬编码进版本库放.env里。然后确认 Base URL。统一用https://taotoken.net/api注意结尾不要多加斜杠也不要在后面拼/v1之类的路径具体路径由 SDK 或请求库自己处理。Model ID 按你实际要用的模型填比如做编码辅助就填对应的编码模型 ID做通用对话就填对话模型 ID。这三个值配好基准脚本里的模型调用就能跑通。如果你用的是 Claude Code 这类编码工具配置方式略有不同需要走 settings 文件。这块我在下一节的可复制配置里一起给包括 JSON 和 TOML 两种格式路径和原文保持一致你直接抄就行。这里提醒一个容易踩的坑TaoToken 是统一接入服务不是让你绕过正常调用流程的东西配置时老老实实按文档填 Base URL 和 Key不要自己拼奇怪的地址。另外基准脚本里如果同时有数据库查询和模型调用建议把模型调用的超时单独设长一点因为压测时数据库查询会占住 PHP 进程模型请求排队可能变慢。3. 可复制的迁移与压测配置这一节给你能直接抄的配置和脚本。分三块TaoToken 的 settings 配置、基准测试的迁移文件、四种取数方式的压测脚本。先给 TaoToken 在 Claude Code 里的 settings 配置。路径是~/.claude/settings.json内容如下{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: 你的_TaoToken_API_Key, ANTHROPIC_MODEL: 你的_Model_ID } }如果你用的是 TOML 格式的配置比如某些 Codex 风格的配置文件~/.codex/config.toml对应写法是[model_providers.taotoken] base_url https://taotoken.net/api api_key 你的_TaoToken_API_Key model 你的_Model_ID注意三件套一个都不能少Base URL 填https://taotoken.net/apiKey 填控制台拿到的Model ID 填你实际要用的模型。少任何一个请求都会失败。配完可以用claude命令启动后随便问一句能正常返回就说明通了。接下来是基准测试用的迁移文件。假设我们建一张test_records表字段简单点够压测就行// database/migrations/2024_01_01_000000_create_test_records_table.php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create(test_records, function (Blueprint $table) { $table-id(); $table-string(name)-index(); $table-integer(score)-default(0); $table-text(payload)-nullable(); $table-timestamps(); }); } public function down(): void { Schema::dropIfExists(test_records); } };跑php artisan migrate建表。然后写一个填充脚本塞四万多条数据进去方便复现// database/seeders/TestRecordSeeder.php namespace Database\Seeders; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; class TestRecordSeeder extends Seeder { public function run(): void { $rows []; for ($i 1; $i 42629; $i) { $rows[] [ name record_ . $i, score random_int(1, 100), payload str_repeat(x, 200), created_at now(), updated_at now(), ]; if (count($rows) 1000) { DB::table(test_records)-insert($rows); $rows []; } } if (!empty($rows)) { DB::table(test_records)-insert($rows); } } }php artisan db:seed --classTestRecordSeeder跑完表里就有四万多条。注意payload字段我故意塞了 200 个字符这样单条记录体积接近真实业务内存差异才明显。如果你只存几个整数字段get() 的内存峰值会低很多测出来的对比就不够典型。然后是四种取数方式的压测脚本放在一个 Artisan 命令里最方便// app/Console/Commands/BenchmarkQuery.php namespace App\Console\Commands; use Illuminate\Console\Command; use App\Models\TestRecord; class BenchmarkQuery extends Command { protected $signature bench:query {mode}; protected $description 压测四种取数方式; public function handle(): void { $mode $this-argument(mode); $start microtime(true); $num 0; match ($mode) { get $this-runGet($num), cursor $this-runCursor($num), chunk $this-runChunk($num), offset $this-runOffset($num), default $this-error(未知模式), }; $end microtime(true); $memory memory_get_peak_usage(true) / 1024 / 1024; $this-info(记录数: {$num}); $this-info(耗时: . ($end - $start) . 秒); $this-info(内存峰值: . $memory . MB); } private function runGet(int $num): void { foreach (TestRecord::get() as $v) { $num; } } private function runCursor(int $num): void { foreach (TestRecord::cursor() as $v) { $num; } } private function runChunk(int $num): void { TestRecord::chunk(1000, function ($rs) use ($num) { foreach ($rs as $v) { $num; } }); } private function runOffset(int $num): void { $limit 1000; $count TestRecord::count(); $page (int) ceil($count / $limit); for ($i 1; $i $page; $i) { $offset ($i - 1) * $limit; $list TestRecord::offset($offset)-limit($limit)-get(); foreach ($list as $v) { $num; } } } }跑的时候分别执行php artisan bench:query get php artisan bench:query cursor php artisan bench:query chunk php artisan bench:query offset每次跑之前建议重启一下 PHP 进程或者用php artisan每次都是新进程天然隔离避免上一次的内存峰值影响下一次。memory_get_peak_usage(true)读的是进程生命周期内的峰值同一个进程里连续跑四种方式后面的会被前面的峰值污染所以一定要分开跑。4. 验证请求与成功结果配置和脚本都就位后先验证 TaoToken 的请求通不通再验证压测脚本能跑出结果。验证 TaoToken 请求最直接的方式是用 curl 打一下模型对话接口。先确认你的 Key 有效curl -X POST https://taotoken.net/api/v1/messages \ -H Content-Type: application/json \ -H x-api-key: 你的_TaoToken_API_Key \ -H anthropic-version: 2023-06-01 \ -d { model: 你的_Model_ID, max_tokens: 64, messages: [{role: user, content: 回复 ok 两个字}] }如果返回里带了正常的文本内容说明 Base URL、Key、Model ID 三件套都对。如果返回 401往下看第五节排错。你也可以直接在模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里手动发一条消息能收到回复就说明账号和 Key 没问题再回到脚本里排查。验证压测脚本先跑get模式php artisan bench:query get正常输出类似记录数: 42629 耗时: 2.4276258945465 秒 内存峰值: 150.0078125 MB再跑cursorphp artisan bench:query cursor输出记录数: 42629 耗时: 2.2276830673218 秒 内存峰值: 13.05859375 MBchunk和offset同理分别会看到 5 秒多和 6 秒多、内存 2MB 左右的结果。四个都跑通说明你的环境和我实测的环境基本一致数据可比。这里有个验证细节chunk和offset的内存峰值都是 2MB这个 2MB 基本是 PHP 进程本身的基线开销说明这两种方式在处理过程中没有把整批数据堆在内存里。而get的 150MB 和cursor的 13MB 才是真正被数据撑起来的部分。cursor 的 13MB 比 get 低了一个数量级但比 chunk 高原因是游标读取时 PDO 和 Laravel 的模型实例仍会保留一部分对象不是完全零开销。如果你想验证 20 万条时的溢出行为把 seeder 里的循环上限改成 200000重新填充再跑get和cursor你会看到Allowed memory size of 268435456 bytes exhausted (tried to allocate 4096 bytes)这就是 256MB 内存限制被撑爆的典型报错。而同样 20 万条chunk和offset依然能跑完内存峰值还是 2MB 上下。这个对比是选型的关键依据。5. 本篇常见错排查压测过程中容易撞上的报错就那么几个逐个说清楚。401 Unauthorized / invalid api key这是 TaoToken 接入最常见的错。原因通常是 Key 没填对、Key 前后带了空格、或者把 Base URL 和 Key 填反了。检查settings.json或config.toml里的ANTHROPIC_AUTH_TOKEN是不是控制台复制出来的完整 Key注意别把https://taotoken.net/api填到 Key 的位置。另外确认 Base URL 结尾没有多余的斜杠https://taotoken.net/api/和https://taotoken.net/api在某些客户端里行为不一样按文档用不带斜杠的。local proxy failed / connection refused这个报错说明请求根本没发出去卡在本地网络层。常见原因是本地配了某个代理端口但那个端口没在跑或者环境变量HTTP_PROXY、HTTPS_PROXY指向了一个失效的地址。检查一下 shell 里的代理环境变量env | grep -i proxy看看有没有残留配置有的话清掉再试。基准脚本里如果用了 Guzzle也要确认没有硬编码代理。reading choices / 返回结构解析失败这个错通常出现在你按 OpenAI 格式解析响应但实际返回的是 Anthropic 格式或者反过来。TaoToken 的接口按你调用的模型类型返回对应结构/v1/messages走的是 Anthropic 风格响应里是content数组如果你按choices[0].message.content去取就会报 reading choices 相关的错。解决办法是对照实际返回的 JSON 结构调整解析代码别照搬另一套 SDK 的示例。OAuth / 认证方式不匹配有些编码工具默认走 OAuth 登录流程而不是 API Key。如果你在 Claude Code 里配了 TaoToken 的 Key 却还提示 OAuth 相关错误检查是不是工具版本把认证方式锁死了。正确做法是在 settings 里显式配置ANTHROPIC_AUTH_TOKEN和ANTHROPIC_BASE_URL让工具走 Key 认证而不是 OAuth。三件套Base URL Key Model ID配全这类认证错基本能消掉。Allowed memory size exhausted这个不是配置错是选型错。get()和cursor()在 20 万条时都会溢出因为 get 一次性加载全部cursor 虽然逐行读但模型实例和 PDO 缓冲仍会累积。解决办法就是换chunk()或offset()-limit()把单次处理量控制在 1000 到 5000 条之间。chunk 的大小可以调chunk(2000)比chunk(1000)少一半查询次数但单批内存翻倍按你的内存余量权衡。chunk 里更新数据导致漏处理这个坑很隐蔽。如果你在chunk回调里修改了用于排序或过滤的字段下一批的查询条件可能就变了导致部分记录被跳过。解决办法是 chunk 时显式指定排序列比如chunk(1000, $callback, id)并且不要在回调里改动 id。或者改用chunkById它按主键游标推进不受数据变动影响。6. 选型建议与后续接入把实测数据和排错经验合起来看选型逻辑其实很清晰。数据量在 10 万条以下、你需要遍历全部记录做逐条处理cursor()是效率和内存的平衡点2 秒出头跑完四万条内存 13MB比 get 省了十倍内存速度还略快。但注意 cursor 不能用于需要「拿到一批数据做批量操作」的场景它一次只给你一条。数据量超过 10 万条或者你不确定未来会不会涨到 20 万直接上chunk()或offset()-limit()。这两种内存峰值都压在 2MB20 万条也不会溢出代价是耗时比 cursor 多一倍左右。chunk 比 offset 略快因为 offset 每页都要重新算 count 和偏移深分页时数据库扫描成本更高。如果分页逻辑不复杂优先 chunk。get()只在数据量明确很小几千条以内、且你确实需要整个 Collection 在内存里做排序、过滤、聚合时才用。四万条就 150MB这个内存增长是线性的数据量翻倍内存就翻倍很容易撞上 256MB 限制。至于 TaoToken 的接入基准脚本跑通之后你可以把同样的三件套配置用到日常编码里。长期做编码和 Agent 任务的可以看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 把模型调用统一到一个 Key 下管理。需要新建或轮换 Key 的去 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 接入细节对照文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。验证模型是否正常直接在模型对话 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里发一条消息最快。最后留一个实操建议压测脚本别只跑一次就下结论。数据库有查询缓存第一次跑和第二次跑的耗时可能差不少。每种模式至少跑三次取中间值并且每次跑之前清一下查询缓存或者换个进程。我实测时 WiFi 和手机热点两组数据差异明显同样 cursorWiFi 2.23 秒热点 22.7 秒说明网络和机器负载对结果影响很大你自己的环境跑出来的绝对值可能和我的不一样但四种方式之间的相对关系是稳定的——内存排序和耗时排序不会变。
阅读完成 · 觉得有帮助?
咨询建站