MySQL 索引合并优化实践

news2024/9/22 3:37:28

在生产环境的数据库中,经常会看到有些 SQL 的 where 条件包含:普通索引等值 + 主键范围查询 + order by limit。明明走普通索引效率更高,但是选择走了索引合并,本文就对这种索引合并的情况研究一下。

作者:张洛丹,热衷于数据库技术,不断探索,期望未来能够撰写更有深度的文章,输出更有价值的内容!

爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

本文约 3000 字,预计阅读需要 10 分钟。

前言

在生产环境的数据库中,经常会看到有些 SQL 的 where 条件包含:普通索引等值 + 主键范围查询 + order by limit。明明走普通索引效率更高,但是选择走了索引合并,本文就对这种索引合并的情况研究一下。

Index Merge 官方介绍

The Index Merge access method retrieves rows with multiple range scans and merges their results into one.

一般对于一个单表,优化器选择一个索引,但在索引合并的情况下,优化器可以使用多个索引来获取数据并对其结果进行合并。

注意:这里实际上是先通过二级索引获取到主键,对主键进行排序合并,然后根据主键回表,来避免随机 IO。

归并排序算法

在介绍索引合并的方式及算法前,先来简单看下归并排序算法,以可以更好地理解 MySQL 中的索引合并。

归并算法是分治思想的应用,递归地将列表分成更小的子列表,将子列表进行排序,然后合并来得到有序列表。

参考阅读:图解归并排序

下面是几种常见算法的时间复杂度:可以看到归并排序还是比较快的。

MySQL 中的索引合并

在 MySQL 中,索引合并算法有下面几种:

  • index_merge_intersection:交集,对应执行计划 Extra:Using intersect(...),对应源码中的 QUICK_ROR_INTERSECT_SELECT 类。
  • index_merge_union:并集,对应执行计划 Extra:Using union(...),对应源码中的 QUICK_ROR_UNION_SELECT 类。
  • index_merge_sort_union:有序并集,对应执行计划 Extra:Using sort_union(...),对应源码中的 QUICK_INDEX_MERGE_SELECT 类。

这里可以看到在前两种方式中,实现类名都有 ROR 关键字。ROR 的含义是 Rowid-Ordered Retrieval,表示单个索引返回的结果集是按照主键有序排列的。根据归并排序算法,进行合并结果集的时候就省去了递归排序的步骤,而只需要将有序列表合并即可。

而对于第三种方式,由于返回的结果不是按照主键排序的,则需要先进行归并排序。

对于前两种情况,在下面条件返回的结果集主键是有序的,则其 where 条件需要满足下面的条件:

  • 二级索引的条件满足:where 条件需要有所有的索引字段,且是等值匹配。例如一个索引 idx(a,b,c),则使用该索引的 where 条件需要是:a=? and b=? and c=?
  • 主键范围查询

其中,用到交集(intersection)算法的,通常是用 and 连接的各索引条件,经过索引合并后的主键结果集,比走一个索引的主键结果集更小的情况下比较有优势;使用到并集(union)算法的,通常是 or 连接的各部分条件,走两个索引比走全表扫描成本更低的情况下有优势。

而对于第三种情况,在不能使用前两种算法的情况下,如果经过计算走索引合并的成本更低,会选择,该种算法需要去重和排序,这里去重和排序使用到树结构,强行去扒了一点点源码,放在这里。

sql opt_range.cc
int QUICK_INDEX_MERGE_SELECT::read_keys_and_merge()

-- 该函数的作用是:
在索引合并中,扫描使用到的所有索引(除了CPK-Clustered Primary key,即主键)并将其rowid合并

如果某个记录能从主键范围条件中扫描获取到,则跳过该行

否则后续执行unique_add方法,该方法向树结构中插入元素,实现了排序和去重

/* skip row if it will be retrieved by clustered PK scan */
    if (pk_quick_select && pk_quick_select->row_in_ranges())
      continue;

    cur_quick->file->position(cur_quick->record);
    result= unique->unique_add((char*)cur_quick->file->ref);
    if (result)
      DBUG_RETURN(1);


unique_add方法,在其中调用了tree_insert
inline bool unique_add(void *ptr)
  {
    DBUG_ENTER("unique_add");
    DBUG_PRINT("info", ("tree %u - %lu", tree.elements_in_tree, max_elements));
    if (tree.elements_in_tree > max_elements && flush())
      DBUG_RETURN(1);
    DBUG_RETURN(!tree_insert(&tree, ptr, 0, tree.custom_arg));
  }


-- tree_insert中,在遍历树的过程中,有一行代码,
如下:表示如果不是树的null节点,则进行比较元素是否和要插入的值相等,相等则跳出循环(这样也就保证了树上不会有重复的值)
for (;;)
  {
    if (element == &tree->null_element ||
    (cmp = (*tree->compare)(custom_arg, ELEMENT_KEY(tree,element),
                                key)) == 0)
      break;
      ...

综上,以下三种算法的示例 SQL:

-- 可用到 index_merge_intersection/index_merge_union 算法
SELECT * FROM innodb_table
  WHERE primary_key < 10 AND/OR key1 = 20;


SELECT * FROM innodb_table
  WHERE (key1_part1 = 1 AND key1_part2 = 2) AND/OR key2 = 2;

-- 可用到 index_merge_sort_union 算法
SELECT * FROM innodb_table
  WHERE (key1_part1 = 2 or key1_part1 = 7) or key2_part1 = 4

SELECT * FROM innodb_table
  WHERE key1_part1 < 10 OR key2_part1 < 20;

一个特殊的情况是,index_merge_intersection 情况下,其中有一个条件是主键范围,这个情况主键条件只起到数据过滤的作用,而并不需要将满足该条件的记录取出再做合并,具体条件是:在遍历二级索引取出 rowid 时,判断该 rowid 是否在主键范围内,如果是则保留,否则忽略这个 rowid

这里,再回到文章开头遇到的问题,即 where 条件包含:普通索引等值 + 主键范围查询 + order by limit,很多情况走单个二级索引要比走索引合并更快。

其主要原因在于:

  • 走索引合并需要将满足二级索引条件的记录都扫描出来。
  • 走单个二级索引,由于 limit 的存在,实际上并不需要扫描满足条件的全部记录,而在获取到满足条件的记录后就停止扫描,因此在一些情况下效果会更好。

这里,实际上 MySQL 在计算成本的时候,先没有考虑 limit 语句,但是成本计算完之后,对于一些场景会有 rechecking_index_usage 的动作,其中有一个 recheck_reason 是:LOW_LIMIT,其判断逻辑是,当 limit 的行数小于表的“扇出”(rows_fetched * filter) 。经过一些逻辑判断是否需要改变执行计划。

recheck index 的代码如下:

扒出一段源码,如下:也就是在满足一些条件的情况下,会重新检查时候可以使用一个索引来完成,其中包括索引合并的情况
    if ((tab->type() == JT_ALL || tab->type() == JT_RANGE ||
            tab->type() == JT_INDEX_MERGE || tab->type() == JT_INDEX_SCAN) &&
            tab->use_quick != QS_RANGE)
    {/*
            We plan to scan (table/index/range scan).
            Check again if we should use an index. We can use an index if:

            1a) There is a condition that range optimizer can work on, and
            1b) There are non-constant conditions on one or more keys, and
            1c) Some of the non-constant fields may have been read
                already. This may be the case if this is not the first
                table in the join OR this is a subselect with
                non-constant conditions referring to an outer table
                (dependent subquery)
                or,
            2a) There are conditions only relying on constants
            2b) This is the first non-constant table
            2c) There is a limit of rows to read that is lower than
                the fanout for this table, predicate filters included
                (i.e., the estimated number of rows that will be
                produced for this table per row combination of
                previous tables)
            2d) The query is NOT run with FOUND_ROWS() (because in that
                case we have to scan through all rows to count them anyway)
          */
          enum { DONT_RECHECK, NOT_FIRST_TABLE, LOW_LIMIT }
          recheck_reason= DONT_RECHECK;

          assert(tab->const_keys.is_subset(tab->keys()));

          const join_type orig_join_type= tab->type();
          const QUICK_SELECT_I *const orig_quick= tab->quick();

          if (cond &&                                                // 1a
              (tab->keys() != tab->const_keys) &&                      // 1b
              (i > 0 ||                                              // 1c
               (join->select_lex->master_unit()->item &&
                cond->used_tables() & OUTER_REF_TABLE_BIT)))
            recheck_reason= NOT_FIRST_TABLE;
          else if (!tab->const_keys.is_clear_all() &&                // 2a
                   i == join->const_tables &&                        // 2b
                   (join->unit->select_limit_cnt <
                    (tab->position()->rows_fetched *
                     tab->position()->filter_effect)) &&               // 2c
                   !join->calc_found_rows)                             // 2d
            recheck_reason= LOW_LIMIT;


    ...

if (recheck_reason == LOW_LIMIT)
            {
              int read_direction= 0;

              /*
                If the current plan is to use range, then check if the
                already selected index provides the order dictated by the
                ORDER BY clause.
              */
              if (tab->quick() && tab->quick()->index != MAX_KEY)
              {
                const uint ref_key= tab->quick()->index;

                read_direction= test_if_order_by_key(join->order,
                                                     tab->table(), ref_key);
                /*
                  If the index provides order there is no need to recheck
                  index usage; we already know from the former call to
                  test_quick_select() that a range scan on the chosen
                  index is cheapest. Note that previous calls to
                  test_quick_select() did not take order direction
                  (ASC/DESC) into account, so in case of DESC ordering
                  we still need to recheck.
                */
                if ((read_direction == 1) ||
                    (read_direction == -1 && tab->quick()->reverse_sorted()))
                {
                  recheck_reason= DONT_RECHECK;
                }
              }
              /*
                We do a cost based search for an ordering index here. Do this
                only if prefer_ordering_index switch is on or an index is
                forced for order by
              */
              if (recheck_reason != DONT_RECHECK &&
                  (tab->table()->force_index_order ||
                   thd->optimizer_switch_flag(
                       OPTIMIZER_SWITCH_PREFER_ORDERING_INDEX)))
              {
                int best_key= -1;
                ha_rows select_limit= join->unit->select_limit_cnt;

                /* Use index specified in FORCE INDEX FOR ORDER BY, if any. */
                if (tab->table()->force_index)
                  usable_keys.intersect(tab->table()->keys_in_use_for_order_by);

                /* Do a cost based search on the indexes that give sort order */
                test_if_cheaper_ordering(tab, join->order, tab->table(),
                                         usable_keys, -1, select_limit,
                                         &best_key, &read_direction,
                                         &select_limit);
                if (best_key < 0)
                  recheck_reason= DONT_RECHECK; // No usable keys
                else
                {
                  // Only usable_key is the best_key chosen
                  usable_keys.clear_all();
                  usable_keys.set_bit(best_key);
                  interesting_order= (read_direction == -1 ? ORDER::ORDER_DESC :
                                      ORDER::ORDER_ASC);
                }
              }
            } 

案例

一个常见的走索引合并反而效率更差的场景:select * from t where c1 ='d' and id>=600000 order by id limit 1000;


-- 表结构
CREATE TABLE `t` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c1` char(64) NOT NULL,
  `c2` char(64) NOT NULL,
  `c3` char(64) NOT NULL,
  `c4` char(64) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_c1` (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=978914 DEFAULT CHARSET=utf8mb4


-- 涉及到的条件记录
mysql> select count(*) from t where c1='d';
+----------+
| count(*) |
+----------+
|    65536 |
+----------+
1 row in set (0.12 sec)
mysql> select count(*) from t where id>=600000;
+----------+
| count(*) |
+----------+
|   313385 |
+----------+
1 row in set (0.39 sec)
mysql> select count(*) from t where c1 ='d' and id>=600000;
+----------+
| count(*) |
+----------+
|    26112 |
+----------+
1 row in set (0.11 sec)

-- 这里有个注意的地方:实际上不加order by id,返回的记录已经是有序的了,但是执行计划中还是有Using filesort,需要进行额外的排序
mysql> explain select * from t where c1 ='d' and id>=600000  order by id limit 1000;
+----+-------------+-------+------------+-------------+----------------+----------------+---------+------+-------+----------+--------------------------------------------------------------+
| id | select_type | table | partitions | type        | possible_keys  | key            | key_len | ref  | rows  | filtered | Extra                                                        |
+----+-------------+-------+------------+-------------+----------------+----------------+---------+------+-------+----------+--------------------------------------------------------------+
|  1 | SIMPLE      | t     | NULL       | index_merge | PRIMARY,idx_c1 | idx_c1,PRIMARY | 260,4   | NULL | 26667 |   100.00 | Using intersect(idx_c1,PRIMARY); Using where; Using filesort |
+----+-------------+-------+------------+-------------+----------------+----------------+---------+------+-------+----------+--------------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)


-- 执行时间:170ms
1000 rows in set (0.17 sec)

-- slowlog中可以看到其扫描行数为:27112 
# Time: 2024-06-13T19:00:38.741184+08:00
# User@Host: root[root] @ VMS184912 [10.61.250.57]  Id:   148
# Query_time: 0.174327  Lock_time: 0.000802 Rows_sent: 1000  Rows_examined: 27112
SET timestamp=1718276438;
select * from t where c1 ='d' and id>=600000  order by id limit 1000;


-- 如果去掉order by语句:10ms,其执行计划仍然是索引合并但是没有了Using filesort
mysql> explain select * from t where c1 ='d' and id>=600000 limit 1000;
+----+-------------+-------+------------+-------------+----------------+----------------+---------+------+-------+----------+----------------------------------------------+
| id | select_type | table | partitions | type        | possible_keys  | key            | key_len | ref  | rows  | filtered | Extra                                        |
+----+-------------+-------+------------+-------------+----------------+----------------+---------+------+-------+----------+----------------------------------------------+
|  1 | SIMPLE      | t     | NULL       | index_merge | PRIMARY,idx_c1 | idx_c1,PRIMARY | 260,4   | NULL | 26667 |   100.00 | Using intersect(idx_c1,PRIMARY); Using where |
+----+-------------+-------+------------+-------------+----------------+----------------+---------+------+-------+----------+----------------------------------------------+
1 row in set, 1 warning (0.00 sec)
-- 执行时间
1000 rows in set (0.01 sec)

-- slow log内容:这里只有1000行,并不需要读取全部满足二级索引的记录
# Time: 2024-06-13T19:00:59.217427+08:00
# User@Host: root[root] @ VMS184912 [10.61.250.57]  Id:   148
# Query_time: 0.010773  Lock_time: 0.000145 Rows_sent: 1000  Rows_examined: 1000
SET timestamp=1718276459;
select * from t where c1 ='d' and id>=600000 limit 1000;

-- 如果原本的sql强制走二级索引
mysql> explain select * from t force index(idx_c1) where c1 ='d' and id>=600000  order by id limit 1000;
+----+-------------+-------+------------+-------+---------------+--------+---------+------+-------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key    | key_len | ref  | rows  | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+--------+---------+------+-------+----------+-----------------------+
|  1 | SIMPLE      | t     | NULL       | range | idx_c1        | idx_c1 | 260     | NULL | 53334 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+--------+---------+------+-------+----------+-----------------------+
1 row in set, 1 warning (0.01 sec)



-- 执行时间:10ms
1000 rows in set (0.01 sec)



对于慢日志中row_examinded的解释:
/**
    Number of rows read and/or evaluated for a statement. Used for
    slow log reporting.

    An examined row is defined as a row that is read and/or evaluated
    according to a statement condition, including in
    create_sort_index(). Rows may be counted more than once, e.g., a
    statement including ORDER BY could possibly evaluate the row in
    filesort() before reading it for e.g. update.
  */

针对这种情况的优化:

  1. 评估是否需要 order by 语句,在使用二级索引的一些情况下(如本例中),返回的结果本身已经是按主键顺序显示,则可省略 order by 语句。

注意:这种方式和使用到的索引有关,如果使用到的二级索引条件不能使主键有序排列则可能返回结果不是有序的,如果业务需要绝对保证顺序,不建议用该方式。

  1. id 范围条件改为 id+0>= ,使 SQL 走二级索引,一些情况可能需要再 order by 的 id 条件上加 id+0,根据实际需要评估。

总结

通常情况下对于一张表的访问,MySQL 选择一个索引,在 where 条件中 range condition 满足下面条件的情况下,有可能使用到两个索引,即索引合并:

  1. 二级索引的条件满足:where 条件需要有所有的索引字段,且是等值匹配。例如一个索引 idx(a,b,c),则使用该索引的 where 条件需要是:a=? and b=? and c=?
  2. 主键范围查询。

参考链接

  • https://dev.mysql.com/doc/refman/8.4/en/index-merge-optimization.html
  • http://mysql.taobao.org/monthly/2023/07/02/
  • http://mysql.taobao.org/monthly/2021/06/03/
  • https://www.orczhou.com/index.php/2013/01/mysql-source-code-query-optimization-index-merge/
  • https://www.orczhou.com/index.php/2012/11/mysql-source-code-range-optimize-data-structure/
  • https://www.orczhou.com/index.php/2013/03/mysql-source-code-query-optimization-index-merge-structure-cost/
  • https://dev.mysql.com/worklog/task/?id=6986
  • https://juejin.cn/post/7071865447108313095

更多技术文章,请访问:https://opensource.actionsky.com/

关于 SQLE

SQLE 是一款全方位的 SQL 质量管理平台,覆盖开发至生产环境的 SQL 审核和管理。支持主流的开源、商业、国产数据库,为开发和运维提供流程自动化能力,提升上线效率,提高数据质量。

✨ Github:https://github.com/actiontech/sqle

📚 文档:https://actiontech.github.io/sqle-docs/

💻 官网:https://opensource.actionsky.com/sqle/

👥 微信群:请添加小助手加入 ActionOpenSource

🔗 商业支持:https://www.actionsky.com/sqle

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

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

相关文章

细数目标管理的坑:避免陷阱,实现高效执行

目标管理作为一种被广泛采用的管理方法&#xff0c;通过明确的目标设定和追踪&#xff0c;提升组织绩效和员工动力。然而&#xff0c;正如任何管理工具一样&#xff0c;目标管理也并非完美无缺&#xff0c;其在实际应用中往往伴随着一系列潜在的“坑”。 一、目标设定&#x…

如何使用DataGear零编码快速制作MQTT物联网实时数据看板

DataGear是一个开源免费的数据可视化分析平台&#xff0c;企业版在开源版基础上开发&#xff0c;新增了诸多企业级特性&#xff0c;包括&#xff1a;MySQL及更多部署数据库支持、MQTT/WebSocket/Redis/MongoDB数据集、OAuth2.0/CAS/JWT/LDAP统一登录支持、前后端敏感信息加密传…

ArcGIS 数据服务在三维 Cesium/SuperMap 项目中使用遇到的一些问题及其解决方法

ArcGIS 数据服务在三维 Cesium/SuperMap 项目中使用遇到的一些问题及其解决方法 一、三维系统支持的 ArcGIS 服务及其投影 1、动态服务 ArcGIS 动态服务的数据&#xff0c;支持任意投影在三维系统中加载。 2、切片服务 ArcGIS 切片服务仅支持 3857(web 墨卡托投影)&#x…

C++ 设计模式(1. 单例模式)

单例模式是一种创建型设计模式&#xff0c; 它的核心思想是保证一个类只有一个实例&#xff0c;并提供一个全局访问点来访问这个实例。 特点 全局访问点的意思是&#xff0c;为了让其他类能够获取到这个唯一实例&#xff0c;该类提供了一个全局访问点&#xff08;通常是一个静态…

锐特驱动器ECR系列IO输出高电平配置

设置极性&#xff1a;常闭值0 默认输出极性常开&#xff0c;平时高组态或无电平输出&#xff0c;点通工作时输出低电平&#xff1b;常闭平时低电平&#xff0c;工作时输出高电平&#xff1b; 常开常闭概念具体可参考&#xff1a; http://t.csdnimg.cn/TIsW9 设置输出功能&…

如何用Java SpringBoot+Vue搭建校内跑腿业务系统?实战教程解析

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

​​​​​​​STM32通过SPI硬件读写W25Q64

目录 STM32通过SPI硬件读写W25Q64 1. STM32的SPI外设简介 2. STM32的SPI框图 2.1 数据寄存器和移位寄存器&#xff08;左上角部分&#xff09; 控制逻辑&#xff08;其余右下角的部分&#xff09; 3.STM32的SPI基本框图 4. STM32的SPI主模式全双工连续传输 时序图 5. S…

网安新声 | 从微软“狂躁许可”漏洞事件看安全新挑战与应对策略

网安加社区【网安新声】栏目&#xff0c;汇聚网络安全领域的权威专家与资深学者&#xff0c;紧跟当下热点安全事件、剖析前沿技术动态及政策导向&#xff0c;以专业视野和前瞻洞察&#xff0c;引领行业共同探讨并应对新挑战的策略与可行路径。 近期&#xff0c;微软披露了一个最…

AIGC企业知识库系统的全方位应用

在知识爆炸的时代&#xff0c;企业如同航行在浩瀚信息海洋中的巨轮&#xff0c;每一滴知识的浪花都可能成为推动其破浪前行的动力。而 AIGC企业知识库系统可以帮助企业精准捕捞、高效利用这些宝贵资源&#xff0c;不仅重塑了企业知识管理的版图&#xff0c;更引领了一场前所未有…

秋招力扣Hot100刷题总结——回溯

回溯问题通常应用于解决排列组合等问题&#xff0c;需要注意的是回溯函数中的参数、结束条件、遍历开始顺序等。 回溯三部曲&#xff1a; &#xff08;1&#xff09;确定递归函数的参数。 &#xff08;2&#xff09;确定递归函数的终止条件。 &#xff08;3&#xff09;确定单层…

错误:Input string was not in a correct format.

之前写的桌面端&#xff0c;在国内客户电脑运行着没问题&#xff0c;到欧洲国家电脑上就挂掉了 原因&#xff1a;TM 小数点不是. 而是, 是逗号&#xff0c;不明觉厉 解决办法&#xff1a; 1、更改客户电脑配置 这里把逗号改成.就行了 但是这种办法比较笨&#xff0c;总不能…

视频检索技术为电子商务直播领域带来了前所未有的革新

视频检测在这个场景中指的是通过视频流实时识别和检索直播中销售人员展示的商品。这涉及到从连续的视频帧中分析和识别商品的视觉内容&#xff0c;通常与语音和文本数据结合&#xff0c;以提高识别准确性。 技术原理 文本引导的注意机制&#xff1a;这一机制通过直播中销售人员…

初始redis:List

列表 List 相当于数组或者顺序表。 对于List来说&#xff0c;两侧都可以插入和删除&#xff0c;时间复杂度是O(1)。 有很多的操作&#xff0c;比如 llen 可以获取List的长度&#xff0c;lrem 可以删除元素 &#xff0c;lrange可以去一个字符串 &#xff0c; lindex可以根据下标…

MBR20100FCT-ASEMI无人机专用MBR20100FCT

编辑&#xff1a;ll MBR20100FCT-ASEMI无人机专用MBR20100FCT 型号&#xff1a;MBR20100FCT 品牌&#xff1a;ASEMI 封装&#xff1a;TO-220F 批号&#xff1a;最新 恢复时间&#xff1a;35ns 最大平均正向电流&#xff08;IF&#xff09;&#xff1a;20A 最大循环峰值…

Leetcode344. 反转字符串(双指针-对撞)

题目描述&#xff1a; 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 示例&#xff1a; 示例 1&#xff1a; 输…

比铁饭碗还好的专业,未来人才缺口超大,而且就业压力还小!

高考是许多学生心中的一件大事&#xff0c;高考成绩的好坏&#xff0c;直接决定着进入什么样的大学&#xff0c;或者选择什么样的专业。**而且在当今这个日新月异的时代&#xff0c;选择一个既有发展前景又相对稳定的职业成为了许多学生和家长的关注焦点。**其实我国有部分大学…

Python爬虫——简单网页抓取(实战案例)小白篇

Python 爬虫是一种强大的工具&#xff0c;用于从网页中提取数据。这里&#xff0c;我将通过一个简单的实战案例来展示如何使用 Python 和一些流行的库&#xff08;如 requests 和 BeautifulSoup&#xff09;来抓取网页数据。 实战案例&#xff1a;抓取一个新闻网站的头条新闻标…

UIAbility组件的启动模式

UIAbility的启动模式是指UIAbility实例在启动时的不同呈现状态。针对不同的业务场景&#xff0c;系统提供了三种启动模式&#xff1a; 1、singleton启动模式 单实例模式&#xff0c;也是默认情况下的启动模式。系统中只存在唯一一个该UIAbility实例&#xff0c;即在最近任务列…

BMS杂谈1

1、LTC凌特和ADI是一家 凌力尔特&#xff08;Linear Technology&#xff09;是一家模拟芯片公司&#xff0c;成立于1981年&#xff0c;由‌Bob Swanson和‌Bob Dobkin创立&#xff0c;总部位于硅谷。2016年&#xff0c;凌力尔特被ADI公司以约150亿美元的价格收购。收购完成后&a…

如何免费获取乡镇级边界数据geoJson数据

如何免费获取乡镇级边界数据geoJson数据 我们可以通过 阿里云数据可视化平台 &#xff0c;可以获取到中国各个省份/区级/县级的json数据&#xff0c;但是区级和县级&#xff0c;并没有包含街道和乡镇的数据 获取乡镇级边界数据 1.下载bigemap全能版 安装好后选择你要导出的…