为何DB::transaction()内代码执行速度远快于非事务场景?
问题描述
我有一个数据库seeder,需要向数据库中填充1万+条数据。当未使用DB::transaction包裹代码运行时,while循环执行耗时超过5分钟;而将代码包裹在DB::transaction中运行时,耗时不到1分钟,性能提升超5倍。请问这一性能提升的来源是什么?
事务包裹写法
DB::transaction(function () { $fp = gzopen(database_path() . '/seeders/cars.json.gz', 'r'); $i = 0; while ($json_text = fgets($fp)) { $json = json_decode($json_text, true); DB::table('cars')->insert([ 'full_name' => $json['full_name'], 'slug_name' => $json['slug_name'], 'year' => $json['Ano'], 'model' => array_key_exists('model', $json) ? $json['model'] : null, 'name' => $json['name'], 'brand_id' => Brand::where('name', $json['brand'])->first()->id, 'content' => $json_text, 'created_at' => new \Datetime(), 'updated_at' => new \Datetime() ]); echo "Inserted " . ++$i . " cars\n"; } fclose($fp); });
未使用事务包裹写法
$fp = gzopen(database_path() . '/seeders/cars.json.gz', 'r'); $i = 0; while ($json_text = fgets($fp)) { $json = json_decode($json_text, true); DB::table('cars')->insert([ 'full_name' => $json['full_name'], 'slug_name' => $json['slug_name'], 'year' => $json['Ano'], 'model' => array_key_exists('model', $json) ? $json['model'] : null, 'name' => $json['name'], 'brand_id' => Brand::where('name', $json['brand'])->first()->id, 'content' => $json_text, 'created_at' => new \Datetime(), 'updated_at' => new \Datetime() ]); echo "Inserted " . ++$i . " cars\n"; } fclose($fp);
性能提升的核心来源
- 自动提交模式的重复IO开销:MySQL等绝大多数关系型数据库默认开启*自动提交(autocommit)*模式,没有手动声明事务时,每一条INSERT语句都会被判定为独立事务,执行完成后立刻触发持久化提交流程。1万条数据就会产生1万次独立提交:写事务日志、强制刷盘、同步事务状态、释放锁,零散的磁盘随机IO成本极高。
- 批量提交的IO合并收益:用
DB::transaction包裹后,所有INSERT操作都属于同一个事务,只会在代码块执行完成后做1次最终提交。执行期间所有数据修改先在内存中完成,事务日志可以批量顺序刷盘,把1万次零散IO合并成1次批量IO,这是性能提升的最核心原因。 - 锁管理开销降低:非事务模式下每条插入都要独立申请行锁、提交后立刻释放,反复加锁释放锁本身有不小的CPU调度开销;单事务模式下锁只需要在最终提交时统一释放,省去了大量重复的锁管理操作。
- 事务日志写入量减少:每次事务提交都要写入对应的日志元数据、做持久化校验,1万次小事务的日志写入总量、磁盘fsync调用次数远高于1次大事务,这个差距在机械硬盘场景下会被进一步放大。
补充提示:如果单事务操作的数据量达到百万级以上,可能出现undo log过长、内存占用过高的问题,万级数据量用单事务包裹批量写入是完全安全的,也是行业通用的批量写入优化方案。
内容的提问来源于stack exchange,提问作者Kos-Mos
相关产品推荐
相关产品推荐

