PostgreSQL的学习心得和知识总结(一百五十三)|[performance]将 OR 子句转换为 ANY 表达式

news2024/10/10 1:15:48

注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:

1、参考书籍:《PostgreSQL数据库内核分析》
2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》
3、PostgreSQL数据库仓库链接,点击前往
4、日本著名PostgreSQL数据库专家 铃木启修 网站主页,点击前往
5、参考书籍:《PostgreSQL中文手册》
6、参考书籍:《PostgreSQL指南:内幕探索》,点击前往
7、参考书籍:《事务处理 概念与技术》


1、本文内容全部来源于开源社区 GitHub和以上博主的贡献,本文也免费开源(可能会存在问题,评论区等待大佬们的指正)
2、本文目的:开源共享 抛砖引玉 一起学习
3、本文不提供任何资源 不存在任何交易 与任何组织和机构无关
4、大家可以根据需要自行 复制粘贴以及作为其他个人用途,但是不允许转载 不允许商用 (写作不易,还请见谅 💖)
5、本文内容基于PostgreSQL master源码开发而成


将 OR 子句转换为 ANY 表达式

  • 文章快速说明索引
  • 功能实现背景说明
  • 功能实现源码解析
    • or_to_any_transform_limit
    • process_duplicate_ors
    • transform_or_to_any
      • 原理讲解
      • 调试情况1
      • 调试情况2
      • 调试情况3
      • 调试情况4
  • 社区争议回退原因



文章快速说明索引

学习目标:

做数据库内核开发久了就会有一种 少年得志,年少轻狂 的错觉,然鹅细细一品觉得自己其实不算特别优秀 远远没有达到自己想要的。也许光鲜的表面掩盖了空洞的内在,每每想到于此,皆有夜半临渊如履薄冰之感。为了睡上几个踏实觉,即日起 暂缓其他基于PostgreSQL数据库的兼容功能开发,近段时间 将着重于学习分享Postgres的基础知识和实践内幕。


学习内容:(详见目录)

1、将 OR 子句转换为 ANY 表达式


学习时间:

2024年10月05日 12:16:59


学习产出:

1、PostgreSQL数据库基础知识回顾 1个
2、CSDN 技术博客 1篇
3、PostgreSQL数据库内核深入学习


注:下面我们所有的学习环境是Centos8+PostgreSQL master +Oracle19C+MySQL8.0

postgres=# select version();
                                                  version                                                   
------------------------------------------------------------------------------------------------------------
 PostgreSQL 18devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-21), 64-bit
(1 row)

postgres=#

#-----------------------------------------------------------------------------#

SQL> select * from v$version;          

BANNER        Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
BANNER_FULL	  Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production Version 19.17.0.0.0	
BANNER_LEGACY Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
CON_ID 0


#-----------------------------------------------------------------------------#

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.27    |
+-----------+
1 row in set (0.06 sec)

mysql>

功能实现背景说明

大概描述一下此次的patch的内容,如下:

  1. 在优化的初步阶段,当我们仍在处理表达式树时,将 (expr op C1) OR (expr op C2) ... 替换为 expr op ANY(ARRAY[C1,C2, ...])
  2. 这里 Cn 是第 n 个常量表达式,“expr”是非常量表达式,“op”是一个返回布尔结果并具有通勤器的运算符commuter(用于表达式的常量和非常量部分的反向顺序的情况,如 Cn op expr
  3. 有时它会导致计划不理想。这就是为什么有 or_to_any_transform_limit GUC。它指定触发 OR-to-ANY 转换的 OR 表达式中参数长度的阈值。通常,更多可分组的 OR 参数意味着转换获胜的可能性大于失败的可能性
postgres=# create table t1 (id int primary key, name text);
CREATE TABLE
postgres=# 
postgres=# insert into t1 values (1, 'oracle');
INSERT 0 1
postgres=# insert into t1 values (2, 'mysql');
INSERT 0 1
postgres=# insert into t1 values (3, 'Sql Server');
INSERT 0 1
postgres=# insert into t1 values (4, 'postgresql');
INSERT 0 1
postgres=# select * from t1 where id = 2 or id = 4;
 id |    name    
----+------------
  2 | mysql
  4 | postgresql
(2 rows)

postgres=#
postgres=# explain(costs off, verbose) select * from t1 where id = 2 or id = 4;
                  QUERY PLAN                  
----------------------------------------------
 Bitmap Heap Scan on public.t1
   Output: id, name
   Recheck Cond: ((t1.id = 2) OR (t1.id = 4))
   ->  BitmapOr
         ->  Bitmap Index Scan on t1_pkey
               Index Cond: (t1.id = 2)
         ->  Bitmap Index Scan on t1_pkey
               Index Cond: (t1.id = 4)
(8 rows)

postgres=#
postgres=# explain(costs off, verbose) select * from t1 where id = ANY ('{2, 4}'::integer[]);
                       QUERY PLAN                       
--------------------------------------------------------
 Bitmap Heap Scan on public.t1
   Output: id, name
   Recheck Cond: (t1.id = ANY ('{2,4}'::integer[]))
   ->  Bitmap Index Scan on t1_pkey
         Index Cond: (t1.id = ANY ('{2,4}'::integer[]))
(5 rows)

postgres=#

然后看一下该patch的作用,如下:

postgres=# SET or_to_any_transform_limit = 1;
SET
postgres=# explain(costs off, verbose) select * from t1 where id = 2 or id = 4;
                       QUERY PLAN                       
--------------------------------------------------------
 Bitmap Heap Scan on public.t1
   Output: id, name
   Recheck Cond: (t1.id = ANY ('{2,4}'::integer[]))
   ->  Bitmap Index Scan on t1_pkey
         Index Cond: (t1.id = ANY ('{2,4}'::integer[]))
(5 rows)

postgres=#
postgres=# SET or_to_any_transform_limit = 2;
SET
postgres=# explain(costs off, verbose) select * from t1 where id = 2 or id = 4;
                       QUERY PLAN                       
--------------------------------------------------------
 Bitmap Heap Scan on public.t1
   Output: id, name
   Recheck Cond: (t1.id = ANY ('{2,4}'::integer[]))
   ->  Bitmap Index Scan on t1_pkey
         Index Cond: (t1.id = ANY ('{2,4}'::integer[]))
(5 rows)

postgres=# 
postgres=# SET or_to_any_transform_limit = 3;
SET
postgres=# explain(costs off, verbose) select * from t1 where id = 2 or id = 4;
                  QUERY PLAN                  
----------------------------------------------
 Bitmap Heap Scan on public.t1
   Output: id, name
   Recheck Cond: ((t1.id = 2) OR (t1.id = 4))
   ->  BitmapOr
         ->  Bitmap Index Scan on t1_pkey
               Index Cond: (t1.id = 2)
         ->  Bitmap Index Scan on t1_pkey
               Index Cond: (t1.id = 4)
(8 rows)

postgres=#

关于上面GUC参数的解释,如下:

设置 OR 表达式中参数的最小长度,超过该长度,规划器将尝试查找并将多个相似的 OR 表达式分组为 ANY 表达式。此转换的分组技术基于变量侧的等价性。这种表达式的一侧必须是常量子句,另一侧必须包含变量子句。默认值为 5。值 -1 完全禁用转换。

此 OR-to-ANY 转换的优点是查询规划和执行速度更快。在某些情况下,此转换还会导致更有效的计划,其中包含单个索引扫描而不是多个位图扫描。但是,当不同的 OR 参数更适合匹配不同的索引时,它也可能导致规划回归。当它们具有不同的匹配部分索引或查询中使用的其他列的分布不同时,可能会发生这种情况。通常,更多可分组的 OR 参数意味着转换获胜的可能性大于失败的可能性。


但是因为设计和实现上存在争议,目前该patch已回滚 如下:

在这里插入图片描述

不过本着内核开发人员学习的态度,我们还是看一下其内部的实现。(从社区的态度来看 后面说不定还会归来)


功能实现源码解析

or_to_any_transform_limit

首先看一下该GUC的定义,如下:

// src/backend/utils/misc/guc_tables.c

	{
		{"or_to_any_transform_limit", PGC_USERSET, QUERY_TUNING_OTHER,
			gettext_noop("Set the minimum length of the list of OR clauses to attempt the OR-to-ANY transformation."),
			gettext_noop("Once the limit is reached, the planner will try to replace expression like "
						 "'x=c1 OR x=c2 ..' to the expression 'x = ANY(ARRAY[c1,c2,..])'"),
			GUC_EXPLAIN
		},
		&or_to_any_transform_limit,
		5, -1, INT_MAX,
		NULL, NULL, NULL
	},

言简意赅:正如上面演示那样,设置 OR 子句列表的最小长度以尝试进行 OR 到 ANY 的转换。


process_duplicate_ors

接下来看一下今天学习的重点,如下:

// src/backend/optimizer/prep/prepqual.c

/*
 * process_duplicate_ors
 *	  Given a list of exprs which are ORed together, try to apply
 *	  the inverse OR distributive law.
 *	  给定一个进行 “或” 运算的表达式列表,尝试应用逆 “或” 分配律。
 *
 * Returns the resulting expression (could be an AND clause, an OR
 * clause, or maybe even a single subexpression).
 * 返回结果表达式(可以是 AND 子句、OR 子句、甚至可能是单个子表达式)。
 */
static Expr *
process_duplicate_ors(List *orlist);

前置条件:关于这个函数的内部,请看 张树杰 博客:

  • PostgreSQL代码分析,查询优化部分,process_duplicate_ors,点击前往

这里详细分析了每一行,我这里不再赘述。其中的示例,如下:

postgres=# create table ta(a int primary key);
CREATE TABLE
postgres=# create table tb(b int primary key);
CREATE TABLE
postgres=# 
postgres=# create table tc(c int primary key);
CREATE TABLE
postgres=# create table td(d int primary key);
CREATE TABLE
postgres=# 
postgres=# insert into ta values (1);
INSERT 0 1
postgres=# insert into tb values (1);
INSERT 0 1
postgres=# insert into tc values (1);
INSERT 0 1
postgres=# insert into td values (1);
INSERT 0 1
postgres=# insert into tb values (2);
INSERT 0 1
postgres=# insert into tc values (3);
INSERT 0 1
postgres=# insert into td values (4);
INSERT 0 1
postgres=# explain select * from ta, tb, tc, td WHERE (A=1 AND B=1) OR (A=1 AND C=1) OR (A=1 AND D=1);
                                       QUERY PLAN                                        
-----------------------------------------------------------------------------------------
 Nested Loop  (cost=0.15..331708934.17 rows=19499851 width=16)
   Join Filter: ((tb.b = 1) OR (tc.c = 1) OR (td.d = 1))
   ->  Nested Loop  (cost=0.15..81392.30 rows=6502500 width=12)
         ->  Nested Loop  (cost=0.15..69.17 rows=2550 width=8)
               ->  Index Only Scan using ta_pkey on ta  (cost=0.15..8.17 rows=1 width=4)
                     Index Cond: (a = 1)
               ->  Seq Scan on tb  (cost=0.00..35.50 rows=2550 width=4)
         ->  Materialize  (cost=0.00..48.25 rows=2550 width=4)
               ->  Seq Scan on tc  (cost=0.00..35.50 rows=2550 width=4)
   ->  Materialize  (cost=0.00..48.25 rows=2550 width=4)
         ->  Seq Scan on td  (cost=0.00..35.50 rows=2550 width=4)
(11 rows)

postgres=#

函数流程解释 简要如下:

  1. 处理空 OR 列表

    • 如果传入的 orlist 为空(没有子句),返回一个恒为 FALSE 的布尔常量,表示 OR 为空的情况。
  2. 处理单一子句

    • 如果 orlist 只有一个子句,直接返回该子句,因为一个单独的 OR 子句不需要进一步简化。
  3. 选择最短的 AND 子句作为参考

    • 遍历 orlist,找到最短的 AND 子句作为参考。如果我们发现一个不是 AND 的子句,我们可以将其视为一个单元素 AND 子句,该子句必然是最短的。
  4. 消除参考子句中的重复

    • 对参考子句进行去重,以确保参考列表中没有重复。
  5. 查找所有 OR 子句的共同条件

    • 遍历参考子句,检查每个参考子句是否存在于所有 OR 子句中。如果参考子句出现在所有 OR 子句中,则将其标记为“胜出者” (winners)。
  6. 处理没有胜出者的情况

    • 如果没有共同的胜出子句,检查 OR 子句列表的长度。如果 OR 子句足够长,则尝试将其转换为 SAOP(ScalarArrayOp: Scalar Array Op,即标量数组操作)。转换后返回优化后的 OR 子句列表。
  7. 有胜出者的情况,生成由剩余子句组成的新 OR 列表

    • 生成一个新的 OR 列表,去除已在所有子句中胜出的条件。如果任何子句退化为空,则会出现类似 (A AND B) OR (A) 的情况,该情况可以简化为 A — 也就是说,OR 的其他分支中的附加条件无关紧要。请注意,由于我们使用 list_difference,因此 AND 子句中多次出现的获胜子句将被自动删除。
  8. 再次尝试进行 SAOP 转换

    • 再次检查新生成的 OR 列表,如果其长度达到转换限制,则尝试将其转换为 SAOP。
  9. 处理特殊情况并构建最终的表达式

    • 如果新的 OR 列表不为空,则将其与胜出的子句合并。如果简化的 OR 不是退化的,则将其附加到获胜者列表中,正确处理一个元素的特殊情况(这真的会发生吗?)。此外,还要小心维护 AND/OR 平坦度,以防我们拉出子子 OR 子句。
  10. 最后 若winners列表长度为1返回该表达式;否则返回构造的 AND 子句,再次警惕单个元素和 AND/OR 平坦度。


接下来 不急着分析transform_or_to_any函数,我们对函数process_duplicate_ors调试一下:

第一个SQL,如下:

explain select * from ta, tb, tc, td WHERE (A=1 AND B=1) OR (A=1 AND C=1) OR (A=1 AND D=1);

在这里插入图片描述

如上,参考list这里选择的就是A=1 AND B=1,接下来就是winners list的获得:

在这里插入图片描述

该过程就是reference list中的每一个,都在orlist的元素中检查是否出现 是则进入winners list。否 则不再继续向下检查,直接跳过 如下:

在这里插入图片描述

因为这三个都是and子句,然后又因为winners list: A=1不为空。于是这里的list_difference将自动进行裁剪,如下:

在这里插入图片描述

裁剪拼接之后的neworlist,实际上就是:B=1、C=1和D=1

在这里插入图片描述

最后先是以递归方式将嵌套的 OR 子句展平为单个 or 子句列表 B=1 or C=1 or D=1,然后以递归方式将嵌套的 AND 子句展平为单个 and 子句列表 (A=1) AND (B=1 or C=1 or D=1)。此刻的函数堆栈,如下:

process_duplicate_ors(List * orlist)
find_duplicate_ors(Expr * qual, _Bool is_check)
canonicalize_qual(Expr * qual, _Bool is_check)
preprocess_expression(PlannerInfo * root, Node * expr, int kind)
preprocess_qual_conditions(PlannerInfo * root, Node * jtnode)
subquery_planner(PlannerGlobal * glob, Query * parse, PlannerInfo * parent_root, _Bool hasRecursion, double tuple_fraction, SetOperationStmt * setops)
standard_planner(Query * parse, const char * query_string, int cursorOptions, ParamListInfo boundParams)
planner(Query * parse, const char * query_string, int cursorOptions, ParamListInfo boundParams)
pg_plan_query(Query * querytree, const char * query_string, int cursorOptions, ParamListInfo boundParams)
standard_ExplainOneQuery(Query * query, int cursorOptions, IntoClause * into, ExplainState * es, const char * queryString, ParamListInfo params, QueryEnvironment * queryEnv)
ExplainOneQuery(Query * query, int cursorOptions, IntoClause * into, ExplainState * es, const char * queryString, ParamListInfo params, QueryEnvironment * queryEnv)
ExplainQuery(ParseState * pstate, ExplainStmt * stmt, ParamListInfo params, DestReceiver * dest)
standard_ProcessUtility(PlannedStmt * pstmt, const char * queryString, _Bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment * queryEnv, DestReceiver * dest, QueryCompletion * qc)
ProcessUtility(PlannedStmt * pstmt, const char * queryString, _Bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment * queryEnv, DestReceiver * dest, QueryCompletion * qc) 
PortalRunUtility(Portal portal, PlannedStmt * pstmt, _Bool isTopLevel, _Bool setHoldSnapshot, DestReceiver * dest, QueryCompletion * qc)
FillPortalStore(Portal portal, _Bool isTopLevel)
PortalRun(Portal portal, long count, _Bool isTopLevel, _Bool run_once, DestReceiver * dest, DestReceiver * altdest, QueryCompletion * qc)
exec_simple_query(const char * query_string)
...

在这里插入图片描述


第二个SQL,如下:

explain select * from ta, tb, tc WHERE (A=1 AND B=1) OR (A=1 AND C=1) OR (A=1);

在这里插入图片描述

如上,在选择reference list的时候,首先and子句A=1 AND B=1进入;第二段长度也是2 忽略;第三段更短 因此成为reference list: A=1

然后在随后的winners获取的二级循环中 成功匹配:

在这里插入图片描述

接下来在剪枝中,发生了退化degenerate case 如下:

在这里插入图片描述

这种情况下 实际上原条件(A=1 AND B=1) OR (A=1 AND C=1) OR (A=1)A=1效果一样,于是后期处理如下:

在这里插入图片描述

postgres=# explain select * from ta, tb, tc WHERE (A=1 AND B=1) OR (A=1 AND C=1) OR (A=1);
                                    QUERY PLAN                                     
-----------------------------------------------------------------------------------
 Nested Loop  (cost=0.15..81392.30 rows=6502500 width=12)
   ->  Nested Loop  (cost=0.15..69.17 rows=2550 width=8)
         ->  Index Only Scan using ta_pkey on ta  (cost=0.15..8.17 rows=1 width=4)
               Index Cond: (a = 1)
         ->  Seq Scan on tb  (cost=0.00..35.50 rows=2550 width=4)
   ->  Materialize  (cost=0.00..48.25 rows=2550 width=4)
         ->  Seq Scan on tc  (cost=0.00..35.50 rows=2550 width=4)
(7 rows)

postgres=#

上面两个都可以抽取出公共项,即:winners list不为空。接下来 看第三个SQL:

explain select * from ta, tb, tc, td WHERE (A=1 AND B=1) OR (A=1 AND C=1) OR (D=1);

在这里插入图片描述

如上,reference此时就只有一个,然后检查是否是winners list。因为第一个检查就未匹配,此时winners将是空,那么将进入如下逻辑:

// src/backend/optimizer/prep/prepqual.c

	/*
	 * If no winners, we can't do OR-to-ANY transformation.
	 */
	if (winners == NIL)
	{
		/*
		 * Make an attempt to group similar OR clauses into SAOP if the list
		 * is lengthy enough.
		 */
		if (or_to_any_transform_limit >= 0 &&
			list_length(orlist) >= or_to_any_transform_limit)
			orlist = transform_or_to_any(orlist);

		/* Transformation could group all OR clauses to a single SAOP */
		return (list_length(orlist) == 1) ?
			(Expr *) linitial(orlist) : make_orclause(orlist);
	}

因为我们这里没有开or_to_any的转换,那么最终返回的表达式还是make_orclause(orlist),保持了原样:

postgres=# explain select * from ta, tb, tc, td WHERE (A=1 AND B=1) OR (A=1 AND C=1) OR (D=1);
                                        QUERY PLAN                                         
-------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.00..1057270004879.88 rows=16594374899 width=16)
   Join Filter: (((ta.a = 1) AND (tb.b = 1)) OR ((ta.a = 1) AND (tc.c = 1)) OR (td.d = 1))
   ->  Nested Loop  (cost=0.00..207348588.00 rows=16581375000 width=12)
         ->  Nested Loop  (cost=0.00..81358.62 rows=6502500 width=8)
               ->  Seq Scan on ta  (cost=0.00..35.50 rows=2550 width=4)
               ->  Materialize  (cost=0.00..48.25 rows=2550 width=4)
                     ->  Seq Scan on tb  (cost=0.00..35.50 rows=2550 width=4)
         ->  Materialize  (cost=0.00..48.25 rows=2550 width=4)
               ->  Seq Scan on tc  (cost=0.00..35.50 rows=2550 width=4)
   ->  Materialize  (cost=0.00..48.25 rows=2550 width=4)
         ->  Seq Scan on td  (cost=0.00..35.50 rows=2550 width=4)
(11 rows)

postgres=#

有了上面的铺垫,接下来再看transform_or_to_any就会清晰很多,如下:

// src/backend/optimizer/prep/prepqual.c

/*
 * transform_or_to_any -
 *	  Discover the args of an OR expression and try to group similar OR
 *	  expressions to SAOP expressions.
 *	  发现 OR 表达式的参数,并尝试将相似的 OR 表达式分组为 SAOP 表达式。
 *
 * This transformation groups two-sided equality expression.  One side of
 * such an expression must be a plain constant or constant expression.  The
 * other side must be a variable expression without volatile functions.
 * To group quals, opno, inputcollid of variable expression, and type of
 * constant expression must be equal too.
 * 此转换将两边相等表达式分组。
 * 此类表达式的一侧必须是普通常量或常量表达式。
 * 另一侧必须是没有易失性函数的变量表达式。
 * 要分组,变量表达式的 quals、opno、inputcollid 和常量表达式的类型也必须相等。
 *
 * The grouping technique is based on the equivalence of variable sides of
 * the expression: using exprId and equal() routine, it groups constant sides
 * of similar clauses into an array.  After the grouping procedure, each
 * couple ('variable expression' and 'constant array') forms a new SAOP
 * operation, which is added to the args list of the returning expression.
 * 分组技术基于表达式变量侧的等价性:使用 exprId 和 equal() 例程,它将相似子句的常量侧分组为一个数组。
 * 分组过程之后,每对(“变量表达式”和“常量数组”)形成一个新的 SAOP 操作,该操作将添加到返回表达式的参数列表中。
 */
static List *
transform_or_to_any(List *orlist);

上面函数transform_or_to_any的目的:发现有合适的or表达式,就处理成ANY表达式。而函数process_duplicate_ors根据winners list是否为空 也选择了不同的道路。

  • 为空,最后make_orclause 例如上面的第三个SQL
  • 不为空,对于非degenerate的情况(neworlist != NIL),例如第一个SQL

函数transform_or_to_any的调用,如下:

在这里插入图片描述


transform_or_to_any

原理讲解

在开始之前 先看一下重要的数据结构 keyentry(因为该函数内部使用了一个哈希表 这个也是其他人不太喜欢的一个点),如下:

/*
 * The key for grouping similar operator expressions in transform_or_to_any().
 *  
 * 在transform_or_to_any()中对相似的运算符表达式进行分组的 key
 */
typedef struct OrClauseGroupKey
{
	/* We need this to put this structure into list together with other nodes */
	/* 我们需要将它与其他节点一起放入列表中。 当然这个也不太好 这是个什么 Node? */
	NodeTag		type;

	/* The expression of the variable side of operator */
	/* 运算符变量侧的表达式 */
	Expr	   *expr;
	/* The operator of the operator expression */
	/* 运算符表达式的运算符 */
	Oid			opno;
	/* The collation of the operator expression */
	/* 运算符表达式的排序规则 */
	Oid			inputcollid;
	/* The type of constant side of operator */
	/* 运算符常量端的类型 */
	Oid			consttype;
} OrClauseGroupKey;
/*
 * The group of similar operator expressions in transform_or_to_any().
 *  
 * transform_or_to_any() 中的一组类似的运算符表达式
 */
typedef struct OrClauseGroupEntry
{
	OrClauseGroupKey key;

	/* The list of constant sides of operators */
	/* 运算符常量侧的列表 */
	List	   *consts;

	/*
	 * List of source expressions.  We need this for convenience in case we
	 * will give up on transformation.
	 * 源表达式列表。如果我们放弃转换,我们需要这个列表以方便使用。
	 */
	List	   *exprs;
} OrClauseGroupEntry;

这里面还自定义了一些哈希回调,有兴趣的小伙伴可以了解一下:

	...
	info.hash = orclause_hash;
	info.keycopy = orclause_keycopy;
	info.match = orclause_match;
	...

static List *
transform_or_to_any(List *orlist)
{
	List	   *neworlist = NIL;
	List	   *entries = NIL;
	ListCell   *lc;
	HASHCTL		info;
	HTAB	   *or_group_htab = NULL;
	int			len_ors = list_length(orlist);
	OrClauseGroupEntry *entry = NULL;

	Assert(or_to_any_transform_limit >= 0 &&
		   len_ors >= or_to_any_transform_limit);

	MemSet(&info, 0, sizeof(info));
	info.keysize = sizeof(OrClauseGroupKey);
	info.entrysize = sizeof(OrClauseGroupEntry);
	info.hash = orclause_hash;
	info.keycopy = orclause_keycopy;
	info.match = orclause_match;
	or_group_htab = hash_create("OR Groups",
								len_ors,
								&info,
								HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_KEYCOPY);

	foreach(lc, orlist)
	{
		Node	   *orqual = lfirst(lc);
		Node	   *const_expr;
		Node	   *nconst_expr;
		OrClauseGroupKey hashkey;
		bool		found;
		Oid			opno;
		Oid			consttype;
		Node	   *leftop,
				   *rightop;

		if (!IsA(orqual, OpExpr))
		{
			entries = lappend(entries, orqual);
			continue;
		}

		opno = ((OpExpr *) orqual)->opno;
		if (get_op_rettype(opno) != BOOLOID)
		{
			/* Only operator returning boolean suits OR -> ANY transformation */
			/* 仅返回布尔值的运算符适合 OR -> ANY 转换 */
			entries = lappend(entries, orqual);
			continue;
		}

		/*
		 * Detect the constant side of the clause. Recall non-constant
		 * expression can be made not only with Vars, but also with Params,
		 * which is not bonded with any relation. Thus, we detect the const
		 * side - if another side is constant too, the orqual couldn't be an
		 * OpExpr.  Get pointers to constant and expression sides of the qual.
		 *  
		 * 检测子句的常量侧。
		 * 回想一下,非常量表达式不仅可以用 Var 来创建,还可以用 Params 来创建,后者与任何relation都没有关系。
		 * 因此,我们检测 const 侧 - 如果另一侧也是常量,则 orqual 不能是 OpExpr。
		 * 获取指向 qual 的常量侧和表达式侧的指针。
		 */
		leftop = get_leftop(orqual);
		if (IsA(leftop, RelabelType))
			leftop = (Node *) ((RelabelType *) leftop)->arg;
		rightop = get_rightop(orqual);
		if (IsA(rightop, RelabelType))
			rightop = (Node *) ((RelabelType *) rightop)->arg;

		if (IsA(leftop, Const))
		{
			opno = get_commutator(opno);

			if (!OidIsValid(opno))
			{
				/* commutator doesn't exist, we can't reverse the order */
				/* commutator不存在,我们无法反转顺序 */
				entries = lappend(entries, orqual);
				continue;
			}

			nconst_expr = get_rightop(orqual);
			const_expr = get_leftop(orqual);
		}
		else if (IsA(rightop, Const))
		{
			const_expr = get_rightop(orqual);
			nconst_expr = get_leftop(orqual);
		}
		else
		{
			entries = lappend(entries, orqual);
			continue;
		}

		/*
		 * Forbid transformation for composite types, records, and volatile
		 * expressions.
		 * 禁止对复合类型、记录和易失性表达式进行转换。
		 */
		consttype = exprType(const_expr);
		if (type_is_rowtype(exprType(const_expr)) ||
			type_is_rowtype(consttype) ||
			contain_volatile_functions((Node *) nconst_expr))
		{
			entries = lappend(entries, orqual);
			continue;
		}

		/*
		 * At this point we definitely have a transformable clause. Classify
		 * it and add into specific group of clauses, or create new group.
		 * 此时我们肯定有一个可转换子句。将其分类并添加到特定的子句组中,或创建新的组。
		 */
		hashkey.type = T_Invalid;
		hashkey.expr = (Expr *) nconst_expr;
		hashkey.opno = opno;
		hashkey.consttype = consttype;
		hashkey.inputcollid = exprCollation(const_expr);
		entry = hash_search(or_group_htab, &hashkey, HASH_ENTER, &found);

		if (unlikely(found))
		{
			entry->consts = lappend(entry->consts, const_expr);
			entry->exprs = lappend(entry->exprs, orqual);
		}
		else
		{
			entry->consts = list_make1(const_expr);
			entry->exprs = list_make1(orqual);

			/*
			 * Add the entry to the list.  It is needed exclusively to manage
			 * the problem with the order of transformed clauses in explain.
			 * Hash value can depend on the platform and version.  Hence,
			 * sequental scan of the hash table would prone to change the
			 * order of clauses in lists and, as a result, break regression
			 * tests accidentially.
			 * 将条目添加到列表中。
			 * 它专门用于管理 explain 中转换子句的顺序问题。
			 * 哈希值可能取决于平台和版本。
			 * 因此,哈希表的顺序扫描很容易改变列表中子句的顺序,从而意外破坏回归测试。
			 */
			entries = lappend(entries, entry);
		}
	}

	/* Let's convert each group of clauses to an ANY expression. */
	/* 让我们将每组子句转换为 ANY 表达式。 */

	/*
	 * Go through the list of groups and convert each, where number of consts
	 * more than 1. trivial groups move to OR-list again
	 * 遍历组列表并转换每个组,其中 const 的数量大于 1。trivial 组再次移至 OR 列表
	 */
	foreach(lc, entries)
	{
		Oid			scalar_type;
		Oid			array_type;

		if (!IsA(lfirst(lc), Invalid))
		{
			neworlist = lappend(neworlist, lfirst(lc));
			continue;
		}

		entry = (OrClauseGroupEntry *) lfirst(lc);

		Assert(list_length(entry->consts) > 0);
		Assert(list_length(entry->exprs) == list_length(entry->consts));

		if (list_length(entry->consts) == 1)
		{
			/*
			 * Only one element returns origin expression into the BoolExpr
			 * args list unchanged.
			 * 只有一个元素将原始表达式不加改变地返回到 BoolExpr 参数列表中。
			 */
			list_free(entry->consts);
			neworlist = list_concat(neworlist, entry->exprs);
			continue;
		}

		/*
		 * Do the transformation.
		 */
		scalar_type = entry->key.consttype;
		array_type = OidIsValid(scalar_type) ? get_array_type(scalar_type) :
			InvalidOid;

		if (OidIsValid(array_type))
		{
			/*
			 * OK: coerce all the right-hand non-Var inputs to the common type
			 * and build an ArrayExpr for them.
			 * 确定:将所有右侧非 Var 输入强制转换为通用类型并为它们构建一个 ArrayExpr。
			 */
			List	   *aexprs = NIL;
			ArrayExpr  *newa = NULL;
			ScalarArrayOpExpr *saopexpr = NULL;
			HeapTuple	opertup;
			Form_pg_operator operform;
			List	   *namelist = NIL;
			ListCell   *lc2;

			foreach(lc2, entry->consts)
			{
				Node	   *node = (Node *) lfirst(lc2);

				node = coerce_to_common_type(NULL, node, scalar_type,
											 "OR ANY Transformation");
				aexprs = lappend(aexprs, node);
			}

			newa = makeNode(ArrayExpr);
			/* array_collid will be set by parse_collate.c */
			newa->element_typeid = scalar_type;
			newa->array_typeid = array_type;
			newa->multidims = false;
			newa->elements = aexprs;
			newa->location = -1;

			/*
			 * Try to cast this expression to Const. Due to current strict
			 * transformation rules it should be done [almost] every time.
			 * 尝试将此表达式转换为 Const。由于当前严格的转换规则,[几乎] 每次都应该这样做。
			 */
			newa = (ArrayExpr *) eval_const_expressions(NULL, (Node *) newa);

			opertup = SearchSysCache1(OPEROID,
									  ObjectIdGetDatum(entry->key.opno));
			if (!HeapTupleIsValid(opertup))
				elog(ERROR, "cache lookup failed for operator %u",
					 entry->key.opno);

			operform = (Form_pg_operator) GETSTRUCT(opertup);
			if (!OperatorIsVisible(entry->key.opno))
				namelist = lappend(namelist, makeString(get_namespace_name(operform->oprnamespace)));

			namelist = lappend(namelist, makeString(pstrdup(NameStr(operform->oprname))));
			ReleaseSysCache(opertup);

			saopexpr =
				(ScalarArrayOpExpr *)
				make_scalar_array_op(NULL,
									 namelist,
									 true,
									 (Node *) entry->key.expr,
									 (Node *) newa,
									 -1);
			saopexpr->inputcollid = entry->key.inputcollid;

			neworlist = lappend(neworlist, (void *) saopexpr);
		}
		else
		{
			/*
			 * If the const node's (right side of operator expression) type
			 * don't have “true” array type, then we cannnot do the
			 * transformation. We simply concatenate the expression node.
			 * 
			 * 如果 const 节点(运算符表达式的右侧)的类型没有“真”数组类型,则无法进行转换。
			 * 我们只需连接表达式节点即可。
			 */
			list_free(entry->consts);
			neworlist = list_concat(neworlist, entry->exprs);
		}
	}
	hash_destroy(or_group_htab);
	list_free(entries);

	/* One more trick: assemble correct clause */
	/* 还有一个技巧:组装正确的子句 */
	return neworlist;
}

大概梳理一下该函数,如下:

第一步 初始化:创建一个局部使用的哈希表(HTAB)来 存储 OR条件的分组,按变量和常量来分类。每个OR条件要么是一个简单的布尔操作符表达式(OpExpr),要么被直接保留。

第二步 遍历OR列表:

  1. 检查每个OR条件是否是一个OpExpr
  2. 如果条件涉及布尔运算(通过检查返回类型是否为BOOL),则继续处理,否则将条件 保留 (维持原状)
  3. 识别等式的左侧和右侧操作数,判断其中一侧是否是常量(如果两侧都是常量则跳过该条件)
  4. 对常量的类型和表达式进行检查,确保它们是简单的变量和常量,不涉及复杂类型或易变函数
  5. 开始分组 条件:将每个符合条件的表达式按常量和变量分组,存储在哈希表中。如果已经存在相同变量的组,则将常量添加到该组;或者 新开一组

第三步 构建ANY表达式:

  1. 如果某个变量的分组包含多个常量,则将这些常量转换为数组表达式,并创建一个新的ScalarArrayOpExpr,即ANY表达式
  2. 如果无法进行转换(如常量没有数组类型),则将原始表达式保留在OR列表中

第四步 返回结果:返回新的OR列表,其中包含优化后的ANY表达式。


调试情况1

postgres=# SET or_to_any_transform_limit = 0;
SET
postgres=# table t1;
 id |    name    
----+------------
  1 | oracle
  2 | mysql
  3 | Sql Server
  4 | postgresql
(4 rows)

postgres=#

下面开始调试,SQL 如下:

postgres=# explain(costs off, verbose) select * from t1 where id = 2 or id = 3 or name like '%sql%';
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Seq Scan on public.t1
   Output: id, name
   Filter: ((t1.id = ANY ('{2,3}'::integer[])) OR (t1.name ~~ '%sql%'::text))
(3 rows)

postgres=#

首先对于第一个条件,符合要求(两边相等表达式:此类表达式的一侧必须是普通常量或常量表达式 = 另一侧必须是没有易失性函数的变量表达式),开启一个分组,如下(存入到哈希表中):

在这里插入图片描述

该条件的opno为96,自然返回类型就是bool,如下:

// src/include/catalog/pg_operator.dat

{ oid => '96', oid_symbol => 'Int4EqualOperator', descr => 'equal',
  oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'int4',
  oprright => 'int4', oprresult => 'bool', oprcom => '=(int4,int4)',
  oprnegate => '<>(int4,int4)', oprcode => 'int4eq', oprrest => 'eqsel',
  oprjoin => 'eqjoinsel' },

HASH_ENTER之后,返回的entry就是这样一个代表如下表达式的分组:

		hashkey.type = T_Invalid; // 0 忽略
		hashkey.expr = (Expr *) nconst_expr; // 就是 id
		hashkey.opno = opno; // 96 int = 
		hashkey.consttype = consttype; // 23
		hashkey.inputcollid = exprCollation(const_expr); // 0 忽略

然后该条件进入列表,如下:

entries = lappend(entries, entry);

对于第二个条件,自然也是OK的 对于哈希表来说,同样的key 自然是可以找到上面的分组 如下:

在这里插入图片描述

于是条件二 就附加到上一个的entry中(参考上面的数据结构),如下:

在这里插入图片描述

注意:总的entries还是1


接下来是条件三,它的opno = 1209,如下:

{ oid => '1209', oid_symbol => 'OID_TEXT_LIKE_OP',
  descr => 'matches LIKE expression',
  oprname => '~~', oprleft => 'text', oprright => 'text', oprresult => 'bool',
  oprnegate => '!~~(text,text)', oprcode => 'textlike', oprrest => 'likesel',
  oprjoin => 'likejoinsel' },

它的返回值类型依然是bool,于是继续 如下:

在这里插入图片描述

但是它对应的哈希key值,如下:

		hashkey.type = T_Invalid; // 0 忽略
		hashkey.expr = (Expr *) nconst_expr; // 就是 name
		hashkey.opno = opno; // 1209 text ~~ 
		hashkey.consttype = consttype; // 25
		hashkey.inputcollid = exprCollation(const_expr); // 100

于是它自己新建一个entry,并附加到entries中(这里面现在有2个)。


接下来看一下相关的转换(也就是遍历entries里面的,检查能否转换),以及构造新的neworlist,如下:

对于第一个entry里面就是有两个常量2、3 所以就可以顺利通过如下逻辑(没有被原样保留):

		if (list_length(entry->consts) == 1)
		{
			/*
			 * Only one element returns origin expression into the BoolExpr
			 * args list unchanged.
			 * 只有一个元素将原始表达式不加改变地返回到 BoolExpr 参数列表中
			 */
			list_free(entry->consts);
			neworlist = list_concat(neworlist, entry->exprs);
			continue;
		}

因为基本类型是int 23,自然其array类型为1007(可以转换):

// src/include/catalog/pg_type.dat

{ oid => '23', array_type_oid => '1007',
  descr => '-2 billion to 2 billion integer, 4-byte storage',
  typname => 'int4', typlen => '4', typbyval => 't', typcategory => 'N',
  typinput => 'int4in', typoutput => 'int4out', typreceive => 'int4recv',
  typsend => 'int4send', typalign => 'i' },

注:若是这里 const 节点(运算符表达式的右侧)的类型没有 “真” 数组类型,则无法进行转换。我们只需连接表达式节点即可。


转换的过程,如下:

在这里插入图片描述

在这里插入图片描述

然后先创建一个ArrayExpr结点,如下:

			newa = makeNode(ArrayExpr);
			/* array_collid will be set by parse_collate.c */
			newa->element_typeid = scalar_type; // 23
			newa->array_typeid = array_type; // 1007
			newa->multidims = false;
			newa->elements = aexprs; // 2、3
			newa->location = -1;

然后根据这个entry->key.opno = 96,可以得到oprname => '=';此外array的变量 就是entry->key.expr = id列,如下:

在这里插入图片描述

至此,条件id = 2 or id = 3,就转换成了t1.id = ANY ('{2,3}'::integer[])


至于第二个entry->consts = 1个,无法转换 自然就和上面说明那样直接拼接到neworlist,如下:

在这里插入图片描述


完成这一切之后哈希表就可以销毁了,hash_destroy(or_group_htab); 而这个也是其他开发人员所质疑的一点。


调试情况2

postgres=# explain(costs off, verbose) select * from t1 where id = 2 or id = 3 or name like '%sql%' or name like '%server%';
                                           QUERY PLAN                                            
-------------------------------------------------------------------------------------------------
 Seq Scan on public.t1
   Output: id, name
   Filter: ((t1.id = ANY ('{2,3}'::integer[])) OR (t1.name ~~ ANY ('{%sql%,%server%}'::text[])))
(3 rows)

postgres=#

如上SQL 补充了情况1下的 第二个entry->consts = 1个,无法转换。具体调试不再展开。


调试情况3

上面说的 两边相等表达式 分组,并非特指 = 表达式。如下SQL:

postgres=# explain(costs off, verbose) select * from t1 where id < 2 or id < 3 ;
                  QUERY PLAN                  
----------------------------------------------
 Seq Scan on public.t1
   Output: id, name
   Filter: (t1.id < ANY ('{2,3}'::integer[]))
(3 rows)

postgres=#

快速调试,如下:

// 相同操作符

{ oid => '97', oid_symbol => 'Int4LessOperator', descr => 'less than',
  oprname => '<', oprleft => 'int4', oprright => 'int4', oprresult => 'bool',// here
  oprcom => '>(int4,int4)', oprnegate => '>=(int4,int4)', oprcode => 'int4lt',
  oprrest => 'scalarltsel', oprjoin => 'scalarltjoinsel' },

两次对应的哈希key值,如下:

		hashkey.type = T_Invalid; // 0 忽略
		hashkey.expr = (Expr *) nconst_expr; // 就是 id
		hashkey.opno = opno; // 97 int <
		hashkey.consttype = consttype; // 23
		hashkey.inputcollid = exprCollation(const_expr); // 0

在这里插入图片描述

因为基本类型是int 23,自然其array类型为1007(可以转换)。这次有所不同的就是操作符的变化:

在这里插入图片描述


调试情况4

postgres=# explain(costs off, verbose) select * from t1 where id < 2 or id > 3 ;
               QUERY PLAN               
----------------------------------------
 Seq Scan on public.t1
   Output: id, name
   Filter: ((t1.id < 2) OR (t1.id > 3))
(3 rows)

postgres=#

如果是这种情况,两个操作符不同 那么就会在哈希表中插入两个不同的entry,但是每个entry->consts = 1个,无法转换,最后就维持了原样。

在这里插入图片描述


社区争议回退原因

SHA-1: ff9f72c68f678ded340b431c3e280fe56644a3e7

* revert: Transform OR clauses to ANY expression

This commit reverts 72bd38cc99 due to implementation and design issues.

Reported-by: Tom Lane

有兴趣的小伙伴可以自行查看邮件列表:

  • https://postgr.es/m/3604469.1712628736%40sss.pgh.pa.us

本文不再赘述,相信不久的将来 该规则还会回来。

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

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

相关文章

安装Unity3D并配置VisualStudio

安装Unity3D并配置VisualStudio 由于近期课程要求&#xff0c;需要在电脑上安装Unity3D并配置VisualStudio&#xff0c;所以顺便写了本篇博文 1.下载Unity Hub 首先我们找到Unity中文官网&#xff0c;下载Unity Hub&#xff0c;它可以帮助我们管理我们的Unity项目和版本&#…

vue3.x系列之封装响应式的hooks技巧

v3.x封装一个接口请求的hooks的&#xff0c;都应该很熟练了&#xff0c;那么要实现一个响应式的hooks该怎么做&#xff1f; 实现效果 代码实现 <template><div v-if"error">Oops! Error encountered: {{ error.message }}</div><div v-else-i…

【第2章 开始学习C++】C++语句

文章目录 导语声明语句和变量赋值语句cout的新花样使用cin类简介 导语 C 程序是一组函数&#xff0c; 而每个函数又是一组语句。 C 有好几种语句&#xff0c;例如&#xff1a;声明语句创建变量&#xff0c; 赋值语句给该变量提供一个值。 声明语句和变量 计算机是一种精确的…

selenium-Alert类用于操作提示框/确认弹框(4)

之前文章我们提到&#xff0c;在webdriver.WebDriver类有一个switch_to方法&#xff0c;通过switch_to.alert()可以返回Alert对象&#xff0c;而Alert对象主要用于网页中弹出的提示框/确认框/文本输入框的确认或者取消等动作。 Alert介绍 当在页面定位到提示框/确认框/文本录入…

集成学习和随机森林

集成学习 生活中的集成学习&#xff1a; 买东西找别推荐 import numpy as np import matplotlib.pyplot as pltfrom sklearn import datasetsX, y datasets.make_moons(n_samples500, noise0.3, random_state42)plt.scatter(X[y0,0], X[y0,1]) plt.scatter(X[y1,0], X[y1,1]…

OpenSource - License 开源项目 TrueLicense

文章目录 官网集成Demo 官网 https://truelicense.namespace.global/ https://github.com/christian-schlichtherle/truelicense 集成Demo https://github.com/christian-schlichtherle/truelicense-maven-archetype https://github.com/zifangsky/LicenseDemo https://git…

工厂车间|基于springBoot的工厂车间系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 社会发展日新月异&#xff0c;用计算机应用实现数据管理功能已经算是很完…

《Java基础》常用类

综述 深入理解包装类 和 String类 一、包装类 针对八种基本数据类型都有一个引用类型的包装类&#xff0c;这个类可以自动包装和解包 基本数据类型包装类booleanBooleancharCharacterbyteByteshortShortintIntegerlongLongfloatFloatdoubleDouble 这几个类的继承关系&…

零工市场转型数字化的挑战和机遇

我们的生活因为科技而变的更加方便、便利&#xff0c;例如手机支付、网上购物&#xff0c;那么就目前的就业市场来说&#xff0c;零工市场也面临着数字化转型的挑战和机遇。 零工市场数字化的机遇&#xff1a; 1.通过大数据的计算&#xff0c;极大的提高了零工市场中的匹配效率…

跨平台应用程序本地化过程的特点

跨平台应用程序本地化不仅仅是将单词从一种语言翻译成另一种语言。这是关于调整应用程序&#xff0c;使其无缝融入全球用户的不同文化和语言环境&#xff0c;无论他们使用的是哪种设备或平台。这个过程对于跨平台应用程序来说尤其复杂&#xff0c;它们需要在多个操作系统和设备…

汽车车轮平衡块行业前景:预计2030年全球市场规模将达到10亿美元

汽车车轮平衡块&#xff0c;也称为轮胎平衡块&#xff0c;是一种安装在车轮上的配重部件。它的主要作用是帮助车轮在高速旋转状态下保持动平衡。当车轮高速旋转时&#xff0c;由于车轮的动态不平衡状态&#xff0c;会导致车辆在行驶中出现车轮抖动和方向盘震动的现象。汽车车轮…

[Python] 文件操作详解

文章目录 文件是什么&#xff1f;常用的文件操作函数1. 打开文件2. 关闭文件3. 读取文件(1) read() 方法(2) readline() 方法(3) readlines() 方法 4. 写入文件(1) write() 方法(2) 追加写入 5. 文件读写模式总结 中文文件的处理指定文件编码 上下文管理器示例&#xff1a;使用…

posix接口与system V接口及其异同

POSIX接口和System V接口是用于多线程和进程间通信的两种主要编程接口。它们各自有不同的特点、功能和适用场景。以下是对这两种接口的详细介绍及其异同点。 POSIX接口 特点 标准化: POSIX&#xff08;可移植操作系统接口&#xff09;是由IEEE制定的标准&#xff0c;旨在提供统…

win10+vs2017+opencv4.5.3+yolov5-5.0

win10vs2017opencv4.5.3yolov5-5.0 1&#xff0c;安装OpenCV4.5.3配置环境2&#xff0c;进行模型加载3&#xff0c;如何导出自己的onnx模型&#xff08;1&#xff09;下载YOLOv5的5.0版本的代码&#xff08;2&#xff09;在谷歌实验室更改两部分代码&#xff08;1-1&#xff09…

1个maven命令快速查看jar包依赖路径

本文目标&#xff1a;开发人员&#xff0c;在了解maven工具的dependency命令的条件下&#xff0c;进行查看jar包依赖关系&#xff0c;达到确认jar包引入来源的程度。 文章目录 1 痛点2 解决方案3 总结/练习 1 痛点 maven项目依赖包多时&#xff0c;项目某个包有问题时&#xff…

小赢卡贷公益行:乡村振兴与多元公益并进

在金融科技的浪潮中&#xff0c;小赢卡贷不仅以其创新的金融产品和服务赢得了市场的广泛认可&#xff0c;更以其背后的公益之心&#xff0c;积极履行社会责任&#xff0c;传递着温暖与希望。小赢公益基金会&#xff0c;作为小赢卡贷社会责任的延伸&#xff0c;主要聚焦于乡村振…

IEEE754浮点数的表示

在学习浮点数的表示之前&#xff0c;先看一下二进制和十进制之间的转换。再来学习IEEE754标准的浮点数。 十进制转二进制 对于十进制整数转换位2进制小数。 例如125转换为二进制小数方法为除二取余。 125/262 余数1 62/231 余数0 31/215 余数1 15…

USB转多路RS485应用-组态软件调试

前言 在工业领域&#xff0c;经常使用组态软件来监控和控制生产设备。组态是指不需要编写计算机程序、通过类似“配置”的方式完成工业应用开发的系统。终端设备包括仪表&#xff0c;PLC&#xff0c;传感器等&#xff0c;大部分支持RS485接口和Modbus协议。 运维或部署人员在实…

并发事务演示及隔离级别

引言 在MySQL数据库中&#xff0c;事务是一组不可分割的操作单元&#xff0c;这些操作要么全部成功&#xff0c;要么全部失败。事务的四大特性&#xff0c;通常被称为ACID特性&#xff0c;包括原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#x…

yolov11人物背景扣除

有时候我们需要对图片进行背景扣除和替换,本文将基于yolov11对一张图片进行背景扣除,对视频的处理同理。 安装 pip install ultralytics 2 、获取测试图片 3、代码 from ultralytics import YOLO import cv2 import nu