Php 将列更新为联接列中的值
我正在规范化数据库,需要运行~630k更新。以下是我的表格的基本结构: 领域Php 将列更新为联接列中的值,php,sql,postgresql,laravel,eloquent,Php,Sql,Postgresql,Laravel,Eloquent,我正在规范化数据库,需要运行~630k更新。以下是我的表格的基本结构: 领域 身份证 名字 统计数据 领域 域id 以前,数据库没有domains表,域存储在多个表中,有时作为列表(JSON文本)。我将每个域迁移到domains表中,现在我需要建立与stats表的关系,该表具有domain列。我添加了domain\u id列,并试图以某种方式更新它,以匹配domains表中域的id。stats表有超过2300万行,具有约630k个唯一域(统计数据为每小时一次)。我试着运行一个foreac
- 身份证
- 名字
- 领域
- 域id
domains
表中,现在我需要建立与stats
表的关系,该表具有domain
列。我添加了domain\u id
列,并试图以某种方式更新它,以匹配domains
表中域的id
。stats
表有超过2300万行,具有约630k个唯一域(统计数据为每小时一次)。我试着运行一个foreach,但每个域大约需要2秒钟,再加上大约14天的时间来运行它们
以下是我目前的代码:
首先,我从stats
表中找到domains
表中缺少的所有域,并将它们保存在domains
表中
$statDomains = Stat::select('domain')->groupBy('domain')->lists('domain');
$domains = [];
foreach(array_chunk($statDomains , 1000) as $domains1k){
$domains = array_merge($domains, Domain::whereIn('name', $domains1k)->lists('name'));
}
$missingDomains = [];
foreach(array_diff($statDomains , $domains) as $missingDomain){
$missingDomains[] = ['name' => $missingDomain];
}
if(!empty($missingDomains)){
Domain::insert($missingDomains);
}
接下来,我从domains
表中获取stats
表中存在的所有域,并用该域更新stats
表中的所有行
$domains = [];
foreach(array_chunk($statDomains, 1000) as $domains1k){
$domains +=Domain::whereIn('name', $domains1k)->lists('name', 'id');
}
foreach($domains as $key => $domain){
Stat::where('domain', $domain)->update(['domain_id' => $key]);
}
我会很感激一些有说服力的、查询生成器或者只是原始SQL的东西,它们可以更快地完成更新(最多两个小时?)。我在谷歌上搜索了一下,发现了类似的问题,但无法应用到我的案例中
编辑
我正在运行建议的解决方案。与此同时,我发现迁移的另外两个部分大约需要50分钟。在第一个列表中,我有一个表domain\u list
。它有一个包含JSON编码域的文本列domains
。我正在将这些域移动到domain
表中,并在domain\u list\u domains\u map
表中创建记录。代码如下:
foreach(DomainList::all() as $domainList){
$attach = [];
$domains = json_decode($domainList->domains, true);
foreach($domains as $domain){
$model = Domain::where('name', '=', $domain)->first();
if(is_null($model) && !is_null($domain)){
$model = new Domain();
$model->name = $domain;
$model->save();
}
if(!is_null($model)){
$attach[] = $model->id;
}
}
if(!empty($attach)){
foreach(array_chunk(array_unique($attach), 1000) as $attach1k){
$domainList->domains()->attach($attach1k);
}
}
}
我已经注意到,我可能应该首先找到所有唯一的域并将它们插入到domains表中,但是给出了前面问题的解决方案,我觉得可能有更好的方法在原始SQL中完成所有这些。第二部分非常相似,我可以通过查看第一部分的代码来解决它。该表是categories,它还有一个包含JSON编码域的domains文本列。非常感谢您的帮助
编辑2
下面是我运行的查询,它将现有表复制到一个新表,并填充了domain\u id
列:
CREATE TABLE "stats_new" AS SELECT
"s"."domain",
"d"."id" AS "domain_id"
FROM
"stats" "s"
JOIN "domains" "d" ON ("s"."domain" = "d"."name")
忘记php,转而使用原始sql——循环中的处理记录和多个执行语句会使其速度变慢。而是直接在数据库中运行以下查询:
update stats s set domain_id=(select d.id from domains d where d.name=s.domain);
原始SQL应该快几个数量级 第1步:
插入
在表域中插入所有域名,除非它们已经存在:
插入域(名称)
选择不同的s.domain
从统计数字s
在d.name=s.domain上左连接域d
其中d.name为空;
如果您具有并发写访问权限,则可能存在竞争条件。最简单的解决方案是专门为事务创建域
。否则,您可能会在操作进行到一半时遇到一个唯一的冲突,因为一个并发事务在这两者之间提交了相同的域名。一切都将被逆转
开始;
以独占模式锁定表域;
插入域(名称)
选择不同的s.domain
从统计数字s
在d.name=s.domain上左连接域d
其中d.name为空;
犯罪
域。名称
应该是唯一的
。该约束通过列上的索引实现,这将有助于下一步的性能
更新
要更新部分行而不是全部行,请执行以下操作:更新所有
域\u id
,使其成为域的外键。name
。
但不要使用相关子查询,而是使用。这里快多了
更新统计信息
设置域\u id=d.id
来自域d
其中d.name=s.domain
并且域_id为空;——假设现有ID是正确的。
然后可以删除现在冗余的列stats.domain
:
ALTER TABLE stats DROP column domain;
这非常便宜。该列在系统目录中被标记为死列。在更新或清空行之前,不会删除实际列值
要进一步提高性能,请直接删除操作不需要的所有索引,然后再创建它们-所有索引都在同一事务中
或,要批量更新n行,请执行以下操作:
name
或id
作为列名。这是一种普遍的反模式。模式应该是这样的:
创建表域(
域id串行主键
,域文本唯一不为NULL--猜测它应该是唯一的
);
创建表统计信息(
stats_id串行主键
,域\u id int引用域
--,域文本--可以在上述规范化后删除。
);
欧文的解决方案应该足够好,你应该能够在2小时内完成
如果你有一个很大的统计表,你可能想跳过最后的更新步骤。只是c