最近做个批量获取客户公众号粉丝的功能,但是客户粉丝量过万的时候,需要做批量插入处理,但微信每次返回1万。
微信接口文档:https://developers.weixin.qq.com/doc/offiaccount/User_Management/Getting_a_User_List.html
public function actionGetOpenidList()
{
set_time_limit(0);
date_default_timezone_set("PRC");
$storeList = Store::find()->where(['is_delete' => 0,'is_recycle' => 0])->select(['id', 'wechat_app_id'])->asArray()->all();
if (empty($storeList)) {
return;
}
//遍历商户
foreach ($storeList as $k => $v) {
try {
$this->store = $v;
echo ('--start--' . $this->store['id']) . PHP_EOL;
$form = new MemberFrom();
$wx = $form->getWx($this->store['id']);
if(!$wx){
\Yii::error("获取公众号用户openid消息出错:'商户ID'.$this->store['id'].'实例化公众号失败'\n");
continue;
}
$token = $wx->getAccessToken(1); //强制使用公众号token
if(!$token){
\Yii::error("获取公众号用户openid消息出错:'商户ID'.$this->store['id'].'公众号token获取失败'\n");
continue;
}
$fansArr = $form->getOpenidList($token);
$nextOpenid = $fansArr['next_openid'];
$attributes = [];
foreach ($fansArr['data']['openid'] as $item) {
$flag = StoreOpenidUnion::find()->where(['wechat_platform_open_id' => $item, 'store_id' => $this->store['id']])->one();
if ($flag) {
continue;
} else {
$attributes[] = [
$this->store['id'], $item, '', time()
];
}
}
if($attributes){
$insert = \Yii::$app->db->createCommand()->batchInsert(StoreOpenidUnion::tableName(), ['store_id', 'wechat_platform_open_id', 'wechat_union_id', 'add_time'], $attributes)->execute();
usleep(1000000);
if (!$insert) {
\Yii::error("批量插入失败:'商户ID'.$this->store['id'].'\n");
}
unset($attributes);
}
if($fansArr['total']>10000){
//for ($i=0;$i<=floor($fansArr['total']/$fansArr['count'])-1;$i++){
for ($i=0;$i<=floor($fansArr['total']/10000)-1;$i++){
if ($nextOpenid) {
$fansArrMore = $form->getOpenidList($token,$nextOpenid);
$nextOpenid = $fansArrMore['next_openid'];
$attributes1 = [];
foreach ($fansArrMore['data']['openid'] as $item) {
$flag = StoreOpenidUnion::find()->where(['wechat_platform_open_id' => $item, 'store_id' => $this->store['id']])->one();
if ($flag) {
continue;
} else {
$attributes1[] = [
$this->store['id'], $item, '', time()
];
}
}
if($attributes1){
$insert1 = \Yii::$app->db->createCommand()->batchInsert(StoreOpenidUnion::tableName(), ['store_id', 'wechat_platform_open_id', 'wechat_union_id', 'add_time'], $attributes1)->execute();
usleep(1000000);
if (!$insert1) {
\Yii::error("批量插入失败:'商户ID'.$this->store['id'].'\n");
}
unset($attributes1);
}
}
}
}
}catch (\Exception $e) {
$e->getMessage();
}
}
}
疑问,mysql插入过万数据需要多长?插入的形式有哪些?mysql是怎么执行?
- 多线程插入(单表)
- 多线程插入(多表)
- 预处理sql
- 多值插入sql
- 事务( N 条提交一次)
多线程插入单表:为何对同一个表的插入多线程会比单线程快?同一时间对一个表的写操作不应该是独占的吗?
answer:在数据里做插入操作的时候,整体时间的分配是这样的
-
链接耗时 (30%)
-
发送 query 到服务器 (20%)
-
解析 query (20%)
-
插入操作 (10% * 词条数目)
-
插入 index (10% * Index的数目)
-
关闭链接 (10%)
因此真正耗时间的是连接和解析,不是操作!
MySQL 插入数据在写阶段是独占的,但是插入一条数据仍然需要解析、计算、最后才进行写处理,比如要给每一条记录分配自增 id,校验主键唯一键属性,或者其他一些逻辑处理,都是需要计算的,所以说多线程能够提高效率。
预处理方式:
- 普通 SQL,即使用 Statement 接口执行 SQL
- 预处理 SQL,即使用 PreparedStatement 接口执行 SQL
使用 PreparedStatement 接口允许数据库预编译 SQL 语句,以后只需传入参数,避免了数据库每次都编译 SQL 语句,因此性能更好。
多值插入:
-
普通插入 SQL:INSERT INTO TBL_TEST (id) VALUES(1)
-
多值插入 SQL:INSERT INTO TBL_TEST (id) VALUES (1), (2), (3)
使用多值插入 SQL,SQL 语句的总长度减少,即减少了网络 IO,同时也降低了连接次数,数据库一次 SQL 解析,能够插入多条数据。
事务( N 条提交一次)
在一个事务中提交大量 INSERT 语句可以提高性能。将 sql 拼接成字符串,每 1000 条左右提交事务。10w 条数据大概用时 10s!也就是1次0.1秒!