PolarDB-X 源码解读系列:DML 之 INSERT IGNORE 流程

在上一篇源码阅读中,我们介绍了 INSERT 的执行流程。而 INSERT IGNORE 与 INSERT 不同,需要对插入值判断是否有 Unique Key 的冲突,并忽略有冲突的插入值。因此本文将进一步介绍 PolarDB-X 中 INSERT IGNORE 的执行流程,其根据插入的表是否有 GSI 也有所变化。

下推执行
如果插入的表只有一张主表,没有 GSI,那么只需要将 INSERT IGNORE 直接发送到对应的物理表上,由 DN 自行忽略存在冲突的值。在这种情况下,INSERT IGNORE 的执行过程和 INSERT 基本上相同,读者可以参考之前的源码阅读文章。

逻辑执行
而在有 GSI 的情况下,就不能简单地将 INSERT IGNORE 分别下发到主表和 GSI 对应的物理分表上,否则有可能出现主表和 GSI 数据不一致的情况。举个例子:

create table t1 (a int primary key, b int, global index g1(b) dbpartition by hash(b)) dbpartition by hash(a);
insert ignore into t1 values (1,1),(1,2);

对于插入的两条记录,它们在主表上位于同一个物理表(a 相同),但是在 GSI 上位于不同的物理表(b 不相同),如果直接下发 INSERT IGNORE 的话,主表上只有 (1,1) 能够成功插入(主键冲突),而在 GSI 上 (1,1) 和 (1,2) 都能成功插入,于是 GSI 比主表多了一条数据。

针对这种情况,一种解决方案是根据插入值中的 Unique Key,先到数据库中 SELECT 出有可能冲突的数据到 CN,然后在 CN 判断冲突的值并删除。

进行 SELECT 的时候,最简单的方式就是将所有的 SELECT 直接发送到主表上,但是主表上可能没有对应的 Unique Key,这就导致 SELECT 的时候会进行全表扫描,影响性能。所以在优化器阶段,我们会根据 Unique Key 是在主表还是 GSI 上定义的来确定相应的 SELECT 需要发送到主表还是 GSI,具体代码位置:

com.alibaba.polardbx.optimizer.core.planner.rule.OptimizeLogicalInsertRule#groupUkByTable

protected Map<String, List<List<String>>> groupUkByTable(LogicalInsertIgnore insertIgnore,ExecutionContext executionContext) {// 找到每个 Unique Key 在主表和哪些 GSI 中存在Map<Integer, List<String>> ukAllTableMap = new HashMap<>();for (int i = 0; i < uniqueKeys.size(); i++) {List<String> uniqueKey = uniqueKeys.get(i);for (Map.Entry<String, Map<String, Set<String>>> e : writableTableUkMap.entrySet()) {String currentTableName = e.getKey().toUpperCase();Map<String, Set<String>> currentUniqueKeys = e.getValue();boolean found = false;for (Set<String> currentUniqueKey : currentUniqueKeys.values()) {if (currentUniqueKey.size() != uniqueKey.size()) {continue;}boolean match = currentUniqueKey.containsAll(uniqueKey);if (match) {found = true;break;}}if (found) {ukAllTableMap.computeIfAbsent(i, k -> new ArrayList<>()).add(currentTableName);}}}// 确定是在哪一个表上进行 SELECTfor (Map.Entry<Integer, List<String>> e : ukAllTableMap.entrySet()) {List<String> tableNames = e.getValue();if (tableNames.contains(primaryTableName.toUpperCase())) {tableUkMap.computeIfAbsent(primaryTableName.toUpperCase(), k -> new ArrayList<>()).add(uniqueKeys.get(e.getKey()));} else {final boolean onlyNonPublicGsi =tableNames.stream().noneMatch(tn -> GlobalIndexMeta.isPublished(executionContext, sm.getTable(tn)));boolean found = false;for (String tableName : tableNames) {if (!onlyNonPublicGsi && GlobalIndexMeta.isPublished(executionContext, sm.getTable(tableName))) {tableUkMap.computeIfAbsent(tableName, k -> new ArrayList<>()).add(uniqueKeys.get(e.getKey()));found = true;break;} else if (onlyNonPublicGsi && GlobalIndexMeta.canWrite(executionContext, sm.getTable(tableName))) {tableUkMap.computeIfAbsent(tableName, k -> new ArrayList<>()).add(uniqueKeys.get(e.getKey()));found = true;break;}}}}return tableUkMap;}

而到了执行阶段,我们在 LogicalInsertIgnoreHandler 中处理 INSERT IGNORE。我们首先会进入 getDuplicatedValues 函数,其通过下发 SELECT 的方式查找表中已有的冲突的 Unique Key 的记录。我们将下发的 SELECT 语句中选择的列设置为 (value_index, uk_index, pk)。其中 value_index 和 uk_index 均为的常量。

举个例子,假设有表:

CREATE TABLE `t` (`id` int(11) NOT NULL,`a` int(11) NOT NULL,`b` int(11) NOT NULL,PRIMARY KEY (`id`),UNIQUE GLOBAL KEY `g_i_a` (`a`) COVERING (`id`) DBPARTITION BY HASH(`a`)
) DBPARTITION BY HASH(`id`)

以及一条 INSERT IGNORE 语句:

INSERT IGNORE INTO t VALUES (1,2,3),(2,3,4),(3,4,5);

假设在 PolarDB-X 中执行时,其会将 Unique Key 编号为

0: id
1: g_i_a

INSERT IGNORE 语句中插入的每个值分别编号为

0: (1,2,3)
1: (2,3,4)
2: (3,4,5)

那么对于 (2,3,4) 的 UNIQUE KEY 构造的 GSI 上的 SELECT 即为:

查询 GSI

SELECT 1 as `value_index`, 1 as `uk_index`, `id`
FROM `g_i_a_xxxx`
WHERE `a` in 3;

假设表中已经存在 (5,3,6),那么这条 SELECT 的返回结果即为 (1,1,5)。此外,由于不同的 Unique Key 的 SELECT 返回格式是相同的,所以我们会将同一个物理库上不同的SELECT 查询 UNION 起来发送,以一次性得到多个结果,减少 CN 和 DN 之间的交互次数。只要某个 Unique Key 有重复值,我们就能根据 value_index 和 uk_index 确定是插入值的哪一行的哪个 Unique Key 是重复的。

当得到所有的返回结果之后,我们对数据进行去重。我们将上一步得到的冲突的的值放入一个 SET 中,然后顺序扫描所有的每一行插入值,如果发现有重复的就跳过该行,否则就将该行也加入到 SET 中(因为插入值之间也有可能存在相互冲突)。去重完毕之后,我们就得到了所有不存在冲突的值,将这些值插入到表中之后就完成了一条 INSERT IGNORE 的执行。

逻辑执行的执行流程:

com.alibaba.polardbx.repo.mysql.handler.LogicalInsertIgnoreHandler#doExecute

protected int doExecute(LogicalInsert insert, ExecutionContext executionContext,LogicalInsert.HandlerParams handlerParams) {// ...try {Map<String, List<List<String>>> ukGroupByTable = insertIgnore.getUkGroupByTable();List<Map<Integer, ParameterContext>> deduplicated;List<List<Object>> duplicateValues;// 获取表中已有的 Unique Key 冲突值duplicateValues = getDuplicatedValues(insertIgnore, LockMode.SHARED_LOCK, executionContext, ukGroupByTable,(rowCount) -> memoryAllocator.allocateReservedMemory(MemoryEstimator.calcSelectValuesMemCost(rowCount, selectRowType)), selectRowType, true,handlerParams);final List<Map<Integer, ParameterContext>> batchParameters =executionContext.getParams().getBatchParameters();// 根据上一步得到的结果,去掉 INSERT IGNORE 中的冲突值deduplicated = getDeduplicatedParams(insertIgnore.getUkColumnMetas(), insertIgnore.getBeforeUkMapping(),insertIgnore.getAfterUkMapping(), RelUtils.getRelInput(insertIgnore), duplicateValues,batchParameters, executionContext);if (!deduplicated.isEmpty()) {insertEc.setParams(new Parameters(deduplicated));} else {// All duplicatedreturn affectRows;}// 执行 INSERTtry {if (gsiConcurrentWrite) {affectRows = concurrentExecute(insertIgnore, insertEc);} else {affectRows = sequentialExecute(insertIgnore, insertEc);}} catch (Throwable e) {handleException(executionContext, e, GeneralUtil.isNotEmpty(insertIgnore.getGsiInsertWriters()));}} finally {selectValuesPool.destroy();}return affectRows;}

RETURNING 优化
上一节提到的 INSERT IGNORE 的逻辑执行方式,虽然保证了数据的正确性,但是也使得一条 INSERT IGNORE 语句至少需要 CN 和 DN 的两次交互才能完成(第一次 SELECT,第二次 INSERT),影响了 INSERT IGNORE 的执行性能。

目前的 DN 已经支持了 AliSQL 的 RETURNING 优化,其可以在 DN 的 INSERT IGNORE 执行完毕之后返回成功插入的值。利用这一功能,PolarDB-X 对 INSERT IGNORE 进行了进一步的优化:直接将 INSERT IGNORE 下发,如果在主表和 GSI 上全部成功返回,那么就说明插入值中没有冲突,于是就成功完成该条 INSERT IGNORE 的执行;否则就将多插入的值删除。

执行时,CN 首先会根据上文中的语法下发带有 RETURNING 的物理 INSERT IGNORE 语句到 DN,比如:

call dbms_trans.returning("a", "insert into t1_xxxx values(1,1)");

其中返回列是主键,用来标识插入的一批数据中哪些被成功插入了;t1_xxxx 是逻辑表 t1 的一个物理分表。当主表和 GSI 上的所有 INSERT IGNORE 执行完毕之后,我们计算主表和 GSI 中成功插入值的交集作为最后的结果,然后删除多插入的值。这部分代码在

com.alibaba.polardbx.repo.mysql.handler.LogicalInsertIgnoreHandler#getRowsToBeRemoved

private Map<String, List<List<Object>>> getRowsToBeRemoved(String tableName,Map<String, List<List<Object>>> tableInsertedValues,List<Integer> beforePkMapping,List<ColumnMeta> pkColumnMetas) {final Map<String, Set<GroupKey>> tableInsertedPks = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);final Map<String, List<Pair<GroupKey, List<Object>>>> tablePkRows =new TreeMap<>(String.CASE_INSENSITIVE_ORDER);tableInsertedValues.forEach((tn, insertedValues) -> {final Set<GroupKey> insertedPks = new TreeSet<>();final List<Pair<GroupKey, List<Object>>> pkRows = new ArrayList<>();for (List<Object> inserted : insertedValues) {final Object[] groupKeys = beforePkMapping.stream().map(inserted::get).toArray();final GroupKey pk = new GroupKey(groupKeys, pkColumnMetas);insertedPks.add(pk);pkRows.add(Pair.of(pk, inserted));}tableInsertedPks.put(tn, insertedPks);tablePkRows.put(tn, pkRows);});// Get intersect of inserted valuesfinal Set<GroupKey> distinctPks = new TreeSet<>();for (GroupKey pk : tableInsertedPks.get(tableName)) {if (tableInsertedPks.values().stream().allMatch(pks -> pks.contains(pk))) {distinctPks.add(pk);}}// Remove values which not exists in at least one insert resultsfinal Map<String, List<List<Object>>> tableDeletePks = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);tablePkRows.forEach((tn, pkRows) -> {final List<List<Object>> deletePks = new ArrayList<>();pkRows.forEach(pkRow -> {if (!distinctPks.contains(pkRow.getKey())) {deletePks.add(pkRow.getValue());}});if (!deletePks.isEmpty()) {tableDeletePks.put(tn, deletePks);}});return tableDeletePks;}

与上一节的逻辑执行的“悲观执行”相比,使用 RETURNING 优化的 INSERT IGNORE 相当于“乐观执行”,如果插入的值本身没有冲突,那么一条 INSERT IGNORE 语句 CN 和 DN 间只需要一次交互即可;而在有冲突的情况下,我们需要下发 DELETE 语句将主表或 GSI 中多插入的值删除,于是 CN 和 DN 间需要两次交互。可以看出,即便是有冲突的情况,CN 和 DN 间的交互次数也不会超过上一节的逻辑执行。因此在无法直接下推的情况下,INSERT IGNORE 的执行策略是默认使用 RETURNING 优化执行。

当然 RETURNING 优化的使用也有一些限制,比如插入的 Value 有重复主键时就不能使用,因为这种情况下无法判断具体是哪一行被成功插入,哪一行需要删除;具体可以阅读代码中的条件判断。当不能使用 RETURNING 优化时,系统会自动选择上一节中的逻辑执行方式执行该条 INSERT IGNORE 语句以保证数据的正确性。

使用 RETURNING 优化的执行流程:

com.alibaba.polardbx.repo.mysql.handler.LogicalInsertIgnoreHandler#doExecute

protected int doExecute(LogicalInsert insert, ExecutionContext executionContext,LogicalInsert.HandlerParams handlerParams) {// ...// 判断能否使用 RETURNING 优化boolean canUseReturning =executorContext.getStorageInfoManager().supportsReturning() && executionContext.getParamManager().getBoolean(ConnectionParams.DML_USE_RETURNING) && allDnUseXDataSource && gsiCanUseReturning&& !isBroadcast && !ComplexTaskPlanUtils.canWrite(tableMeta);if (canUseReturning) {canUseReturning = noDuplicateValues(insertIgnore, insertEc);}if (canUseReturning) {// 执行 INSERT IGNORE 并获得返回结果final List<RelNode> allPhyPlan =new ArrayList<>(replaceSeqAndBuildPhyPlan(insertIgnore, insertEc, handlerParams));getPhysicalPlanForGsi(insertIgnore.getGsiInsertIgnoreWriters(), insertEc, allPhyPlan);final Map<String, List<List<Object>>> tableInsertedValues =executeAndGetReturning(executionContext, allPhyPlan, insertIgnore, insertEc, memoryAllocator,selectRowType);// ...// 生成 DELETEfinal boolean removeAllInserted =targetTableNames.stream().anyMatch(tn -> !tableInsertedValues.containsKey(tn));if (removeAllInserted) {affectedRows -=removeInserted(insertIgnore, schemaName, tableName, isBroadcast, insertEc, tableInsertedValues);if (returnIgnored) {ignoredRows = totalRows;}} else {final List<Integer> beforePkMapping = insertIgnore.getBeforePkMapping();final List<ColumnMeta> pkColumnMetas = insertIgnore.getPkColumnMetas();// 计算所有插入值的交集final Map<String, List<List<Object>>> tableDeletePks =getRowsToBeRemoved(tableName, tableInsertedValues, beforePkMapping, pkColumnMetas);affectedRows -=removeInserted(insertIgnore, schemaName, tableName, isBroadcast, insertEc, tableDeletePks);if (returnIgnored) {ignoredRows +=Optional.ofNullable(tableDeletePks.get(insertIgnore.getLogicalTableName())).map(List::size).orElse(0);}}handlerParams.optimizedWithReturning = true;if (returnIgnored) {return ignoredRows;} else {return affectedRows;}} else {handlerParams.optimizedWithReturning = false;}// ... }

最后以一个例子来展现 RETURNING 优化的执行流程与逻辑执行的不同。通过 /+TDDL:CMD_EXTRA(DML_USE_RETURNING=TRUE)/ 这条 HINT,用户可以手动控制是否使用 RETURNING 优化。

首先建表并插入一条数据:

CREATE TABLE `t` (`id` int(11) NOT NULL,`a` int(11) NOT NULL,`b` int(11) NOT NULL,PRIMARY KEY (`id`),UNIQUE GLOBAL KEY `g_i_a` (`a`) COVERING (`id`) DBPARTITION BY HASH(`a`)
) DBPARTITION BY HASH(`id`);INSERT INTO t VALUES (1,3,3);

再执行一条 INSERT IGNORE:

INSERT IGNORE INTO t VALUES (1,2,3),(2,3,4),(3,4,5);

其中 (1,2,3) 与 (1,3,3) 主键冲突,(2,3,4) 与 (1,3,3) 对于 Unique Key g_i_a 冲突。如果是 RETURNING 优化:

可以看到 PolarDB-X 先进行了 INSERT IGNORE,再将多插入的数据删除:(1,2,3) 在主表上冲突在 UGSI 上成功插入,(2,3,4) 在 UGSI 上冲突在主表上成功插入,因此分别下发对应的 DELETE 到 UGSI 和主表上。

如果关闭 RETURNING 优化,逻辑执行:


可以看到 PolarDB-X 先进行了 SELECT,再将没有冲突的数据 (3,4,5) 插入。

小结
本文介绍了 PolarDB-X 中 INSERT IGNORE 的执行流程。除了 INSERT IGNORE 之外,还有一些 DML 语句在执行时也需要进行重复值的判断,比如 REPLACE、INSERT ON DUPLICATE KEY UPDATE 等,这些语句在有 GSI 的情况下均采用了逻辑执行的方式,即先进行 SELECT 再进行判重、更新等操作,感兴趣的读者可以自行阅读相关代码。

作者:潜璟

原文链接

本文为阿里云原创内容,未经允许不得转载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/510406.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

原根(详解+代码实现+例题+快速求解一个数的原根)

1.原根定义 假设一个数g对于P来说是原根&#xff0c;那么g^i mod P的结果两两不同,且有 1<g<P, 1<i<P,那么g可以称为是P的一个原根简单来说&#xff0c;g^i mod p ≠ g^j mod p &#xff08;p为素数&#xff09;其中i≠j且i, j介於1至(p-1)之间则g为p的原根。简单的…

文娱行业搜索最佳实践

内容搜索的价值主要体现在两个方面&#xff1a; 对用户而言&#xff0c;用户将搜索作为寻找内容的工具&#xff0c;目标是“搜的到&#xff0c;搜的准”。用户更关心搜索结果的相关性、时效性和多样性。 对平台而言&#xff0c;搜索是内容消费、流量引导的核心入口&#xff0…

一文搞懂 SAE 日志采集架构

日志&#xff0c;对于一个程序的重要程度不言而喻。无论是作为排查问题的手段&#xff0c;记录关键节点信息&#xff0c;或者是预警&#xff0c;配置监控大盘等等&#xff0c;都扮演着至关重要的角色。是每一类&#xff0c;甚至每一个应用程序都需要记录和查看的重要内容。而在…

无需编写一行代码,实现任何方法的流量防护能力

背景 微服务的稳定性一直是开发者非常关注的话题。随着业务从单体架构向分布式架构演进以及部署方式的变化&#xff0c;服务之间的依赖关系变得越来越复杂&#xff0c;业务系统也面临着巨大的高可用挑战。疫情期间&#xff0c;大家可能都经历过以下的场景&#xff1a; 线上预…

使用日志上下文聚合插件使能上下文查询及 Livetail

背景 在排查业务故障时&#xff0c;用户往往需要查看业务日志文件来定位问题。然而&#xff0c;当用户在使用SLS收集业务日志时&#xff0c;同一个Logstore中往往存放着不同的日志&#xff08;例如同一台主机上不同目录下的文件&#xff0c;抑或是同一个K8S集群节点上不同容器…

Koordinator v0.7: 为任务调度领域注入新活力

Koordinator[1]继上次v0.6版本[2]发布后&#xff0c;经过 Koordinator 社区的努力&#xff0c;我们迎来了具有重大意义的 v0.7 版本。在这个版本中着重建设了机器学习、大数据场景需要的任务调度能力&#xff0c;例如 Coscheduling、ElasticQuota 和精细化的 GPU 共享调度能力。…

聊聊日志硬扫描,阿里 Log Scan 的设计与实践

日志 Scan 的发展与背景 大数据快速增长的需要 泛日志&#xff08;Log/Trace/Metric&#xff09;是大数据的重要组成&#xff0c;伴随着每一年业务峰值的新脉冲&#xff0c;日志数据量在快速增长。同时&#xff0c;业务数字化运营、软件可观测性等浪潮又在对日志的存储、计算…

注册配置、微服务治理、云原生网关三箭齐发,阿里云 MSE 持续升级

背景 注册中心是日常使用频率很高的微服务组件&#xff0c;通过较低的资源溢价帮助客户缩短微服务的构建周期、提升可用性&#xff1b;微服务治理实现了 0 门槛就能接入全链路灰度、无损上下线、限流降级、环境隔离、数据库治理等能力&#xff0c;轻松完成开源到稳定生产的跨越…

新零售标杆 SKG 全面拥抱 Serverless,实现敏捷交付

项目背景 SKG 公司是一家专注于高端健康产品的研发、设计与制造的企业。专注为消费者提供精致、时尚的高端产品&#xff0c;以及极致的按摩仪产品体验。 随着市场需求的迅速变化&#xff0c;SKG 的 IT 系统也逐渐面临着库存不准确、线上线下渠道无法协同、部署架构不灵活、IT…

Mobius函数计算 定义+代码模板

Mobius函数定义为&#xff0c;输入一个正整数N&#xff0c;当N1时&#xff0c;函数值为1&#xff0c;当N不为1时&#xff0c;首先在稿纸上将它分解质因数&#xff0c;若某质因数的个数大于1&#xff0c;则函数值为0&#xff0c;如N45&#xff0c;453*3*5,3出现了两次&#xff0…

不仅有0.0075元的深度冷归档,更有对下一代云存储的重新定义

前言&#xff1a;重新定义下一代云存储&#xff0c;需要继续保障稳定、安全、可靠和低成本&#xff0c;进一步演进 Serverless 能力&#xff0c;智能适配负载变化&#xff0c;提供智能数据管理能力以及全场景覆盖不断发展的新负载。 阿里云存储的创新活力&#xff0c;不仅拓展了…

一图看懂镜像

原文链接 本文为阿里云原创内容&#xff0c;未经允许不得转载。

数值方法求积分 详解+模板代码

什么是数值积分 数值积分可以用来求定积分的近似值。对于很多函数来说&#xff0c;我们是可以使用初等函数来表示出其积分的&#xff0c;对于这种函数&#xff0c;只需要求出不定积分然后代入值就能得到定积分了。 可是除此之外还有许多难求的函数和没法使用初等函数表示的函数…

用积木讲运维,这样的IT人太会了

积木的拼搭&#xff0c;是件细致工作。用不同的积木&#xff0c;进行组合变换&#xff0c;小孩子可能会用积木搭高楼、搭汽车、搭公路&#xff0c;而IT人则选择通过搭建小积木&#xff0c;讲解可观测的大乾坤。 大家所熟知的日志服务SLS不只是“日志存储”&#xff0c;更是一个…

再谈数据湖3.0:降本增效背后的创新原动力

前言&#xff1a;2022年3月 31 日&#xff0c;阿里云全球数据湖峰会上&#xff0c;阿里云从“湖管理、湖存储和湖计算“这三个方面&#xff0c;为观众带来了“数据湖 3.0” 的重磅升级方案。在时隔两百多天的云栖大会上&#xff0c;阿里云存储对数据湖的能力&#xff0c;进行了…

原码 反码 补码 详解

一. 机器数和真值 在学习原码, 反码和补码之前, 需要先了解机器数和真值的概念. 1、机器数 一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的&#xff0c;在计算机用一个数的最高位存放符号, 正数为0, 负数为1. 比如&#xff0c;十进制中的数 3 &…

谈谈 PolarDB-X 在读写分离场景的实践

在数据库使用过程中经常会遇到一些场景&#xff1a; 业务写流量一直相对比较稳定&#xff0c;但随着时间&#xff0c;数据不断增加&#xff0c;数据库的压力也会越来越大&#xff0c;写操作会影响到读请求的性能&#xff0c;做任何优化可能都达不到最终的效果&#xff1b;在应…

开源数据库 PolarDB 为什么能捕获娃哈哈的心?

一、娃哈哈的需求 娃哈哈已经使用PostgreSQL多年&#xff0c;使用了大量逻辑复制&#xff0c;且备库仅提供一些业务的只读服务。同时&#xff0c;其重要业务的数据库运行在共享SAN存储上。因此&#xff0c;它存在主备库延迟较大、逻辑复制不稳定且延迟大的痛点。 二、使用Pola…

数据库 PolarDB 开源之路该如何走?听听他们怎么说

阿里巴巴集团副总裁、阿里云数据库事业部负责人李飞飞出席了沙龙并致开场辞&#xff1a;PolarDB 是阿里云的明星产品&#xff0c;做出将PolarDB 开源的决策需要非常大的勇气。将最核心的数据库产品对外开源&#xff0c;且使用了最友好的协议&#xff0c;阿里云是全球头部云厂商…

通过定时 SQL 提取阿里云API 网关访问日志指标

背景 阿里云API网关服务提供API托管服务&#xff0c;提供了强大的适配和集成能力&#xff0c;可以将各种不同的业务系统API实现统一管理。API网关同时支持将API访问日志一键存储到日志服务&#xff0c;通过日志服务强大的查询分析能力&#xff0c;用户可以针对访问日志自定义计…