PostgreSQL源码分析——索引扫描

这里,我们分析一下索引扫描的过程,以最简单的select * from t1 where a = 100;语句为例,分析一下查询的过程。

postgres@postgres=# \d t1;Table "public.t1"Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------a      | integer |           | not null | b      | integer |           |          | 
Indexes:"t1_pkey" PRIMARY KEY, btree (a)-- 顺序扫描
postgres@postgres=# explain select * from t1;QUERY PLAN                      
------------------------------------------------------Seq Scan on t1  (cost=0.00..15.00 rows=1000 width=8)
(1 row)-- 索引扫描
postgres@postgres=# explain select * from t1 where a = 100;QUERY PLAN                            
------------------------------------------------------------------Index Scan using t1_pkey on t1  (cost=0.28..2.29 rows=1 width=8)Index Cond: (a = 100)
(2 rows)
语法解析以及语义分析

这部分前面已有分析过,这里简要描述:

解析层面流程如下:

exec_simple_query
--> pg_parse_query              // 生成抽象语法树--> raw_parser--> base_yyparse
--> pg_analyze_and_rewrite      // 语义分析,转为查询树Query

语法表示可查看gram.y中的定义。

simple_select:SELECT opt_all_clause opt_target_list   // select *into_clause from_clause where_clause    // from t1 where a = 100;group_clause having_clause window_clause{SelectStmt *n = makeNode(SelectStmt);// ......$$ = (Node *)n;}
from_clause:FROM from_list							{ $$ = $2; }| /*EMPTY*/								{ $$ = NIL; };from_list:table_ref								{ $$ = list_make1($1); }| from_list ',' table_ref				{ $$ = lappend($1, $3); };
// 表示t1
table_ref:	relation_expr opt_alias_clause{$1->alias = $2;$$ = (Node *) $1;}
where_clause:WHERE a_expr							{ $$ = $2; }	// 表示 where a = 100;| /*EMPTY*/								{ $$ = NULL; };

关键数据结构:

// select语句的抽象语法树表示
typedef struct SelectStmt
{NodeTag		type;// 对应select *List	   *targetList;		/* the target list (of ResTarget) */ // 对应         from t1List	   *fromClause;		/* the FROM clause */  // 保存RangeVar节点// ...... } SelectStmt;/** RangeVar - range variable, used in FROM clauses** Also used to represent table names in utility statements; there, the alias* field is not used, and inh tells whether to apply the operation* recursively to child tables.  In some contexts it is also useful to carry* a TEMP table indication here.*/
typedef struct RangeVar
{NodeTag		type;char	   *catalogname;	/* the catalog (database) name, or NULL */char	   *schemaname;		/* the schema name, or NULL */char	   *relname;		/* the relation/sequence name */bool		inh;			/* expand rel by inheritance? recursively act* on children? */char		relpersistence; /* see RELPERSISTENCE_* in pg_class.h */Alias	   *alias;			/* table alias & optional column aliases */int			location;		/* token location, or -1 if unknown */
} RangeVar;    // 存储表的所在库名,模式名,表名信息语义分析流程:
```c
pg_analyze_and_rewrite   // 语义分析,生成查询树Query
--> pg_analyze--> transformStmt--> transformSelectStmt--> transformFromClause  // 处理表, 将RangeVar转为RangeTblEntry--> transformFromClauseItem--> transformTableEntry     --> transformTargetList     // 处理 * ,展开为a, b--> transformWhereClause    // 处理 where a = 100
--> pg_rewrite_query
查询优化

主要是生成执行计划,前面已经通过EXPLAIN命令得知其执行计划为索引扫描,让我们具体看一下其是如何生成的。这里选择率以及代价估算先不进行分析,后续单独进行分析。

pg_plan_queries
--> pg_plan_query--> planner--> standard_planner--> subquery_planner--> grouping_planner--> query_planner 		// Generate a path (that is, a simplified plan) for a basic query--> setup_simple_rel_arrays--> add_base_rels_to_query--> build_simple_rel--> generate_base_implied_equalities(root);--> generate_base_implied_equalities_const--> distribute_restrictinfo_to_rels	// 选择下推, where a = 100 , 绑定到表t1中--> find_base_rel--> rel->baserestrictinfo = lappend(rel->baserestrictinfo, restrictinfo);--> extract_restriction_or_clauses--> make_one_rel	// Finds all possible access paths for executing a query--> set_base_rel_sizes--> set_rel_size--> set_plain_rel_size--> set_baserel_size_estimates	// 选择率计算,计算代价Cost要用--> clauselist_selectivity // 选择率计算, 会将表以及where a = 100限制条件传进去进行计算--> clauselist_selectivity_simple--> clause_selectivity--> restriction_selectivity /* Estimate selectivity for a restriction clause. */--> eqsel--> eqsel_internal--> var_eq_const--> get_variable_numdistinct--> set_base_rel_pathlists--> set_rel_pathlist // Build access paths for a base relation--> create_seqscan_path // 生成顺序扫描路径--> create_index_paths	 // 索引扫描路径--> get_index_paths--> build_index_paths--> check_index_only  // 检查是否可以IndexOnlyScan,仅索引扫描--> make_rel_from_joinlist--> apply_scanjoin_target_to_paths--> create_plan--> create_scan_plan--> create_indexscan_plan--> if indexonly--> make_indexonlyscanelse--> make_indexscan

这里有个函数要重点说明一下,在涉及多表路径规划时,单表路径是最基础的,单表路径可以是顺序扫描、索引扫描、TID扫描等,这个是最底层的。

/* Build access paths for a plain relation (no subquery, no inheritance) */
void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{Relids		required_outer;/* We don't support pushing join clauses into the quals of a seqscan, but* it could still have required parameterization due to LATERAL refs in its tlist. */required_outer = rel->lateral_relids;/* Consider sequential scan */add_path(rel, create_seqscan_path(root, rel, required_outer, 0));	// 顺序扫描/* If appropriate, consider parallel sequential scan */if (rel->consider_parallel && required_outer == NULL)		// 尝试并行顺序扫描create_plain_partial_paths(root, rel);/* Consider index scans */create_index_paths(root, rel);			// 索引扫描/* Consider TID scans */create_tidscan_paths(root, rel);      // TID扫描
}

最后由最佳路径Path生成顺序扫描执行计划SeqScan

/* ==========* Scan nodes* ========== */
typedef struct Scan
{Plan		plan;// 指明扫描那个表,Index		scanrelid;		/* relid is index into the range table */
} Scan;/* ----------------*		sequential scan node* ---------------- */
typedef Scan SeqScan;

其中有个非常重要的函数, 这个函数有个细节,就是IndexOnlyScan,在这里会进行一个判断,是否可以进行IndexOnly扫描,判断条件时target列是否仅索引列,如果满足的话,则可以用IndexOnlyScan。当然前提是enable_indexonlyscan = true。比如下面的语句:

postgres@postgres=# show enable_indexonlyscan ;enable_indexonlyscan 
----------------------on
(1 row)-- Index Only Scan
postgres@postgres=# explain select a from t1 where a = 100;QUERY PLAN                               
-----------------------------------------------------------------------Index Only Scan using t1_pkey on t1  (cost=0.28..2.29 rows=1 width=4)Index Cond: (a = 100)
(2 rows)
-- 关闭 enable_indexonlyscan后,不能进行 IndexOnlyScan
postgres@postgres=# set enable_indexonlyscan = false;
SET
postgres@postgres=# explain select a from t1 where a = 100;QUERY PLAN                            
------------------------------------------------------------------Index Scan using t1_pkey on t1  (cost=0.28..2.29 rows=1 width=4)Index Cond: (a = 100)
(2 rows)

我们继续看一下如何构造IndexPath

/** build_index_paths*	  Given an index and a set of index clauses for it, construct zero*	  or more IndexPaths. It also constructs zero or more partial IndexPaths.** We return a list of paths because (1) this routine checks some cases* that should cause us to not generate any IndexPath, and (2) in some* cases we want to consider both a forward and a backward scan, so as* to obtain both sort orders.  Note that the paths are just returned* to the caller and not immediately fed to add_path().** At top level, useful_predicate should be exactly the index's predOK flag* (ie, true if it has a predicate that was proven from the restriction* clauses).  When working on an arm of an OR clause, useful_predicate* should be true if the predicate required the current OR list to be proven.* Note that this routine should never be called at all if the index has an* unprovable predicate.** scantype indicates whether we want to create plain indexscans, bitmap* indexscans, or both.  When it's ST_BITMAPSCAN, we will not consider* index ordering while deciding if a Path is worth generating.** If skip_nonnative_saop is non-NULL, we ignore ScalarArrayOpExpr clauses* unless the index AM supports them directly, and we set *skip_nonnative_saop* to true if we found any such clauses (caller must initialize the variable* to false).  If it's NULL, we do not ignore ScalarArrayOpExpr clauses.** If skip_lower_saop is non-NULL, we ignore ScalarArrayOpExpr clauses for* non-first index columns, and we set *skip_lower_saop to true if we found* any such clauses (caller must initialize the variable to false).  If it's* NULL, we do not ignore non-first ScalarArrayOpExpr clauses, but they will* result in considering the scan's output to be unordered.** 'rel' is the index's heap relation* 'index' is the index for which we want to generate paths* 'clauses' is the collection of indexable clauses (IndexClause nodes)* 'useful_predicate' indicates whether the index has a useful predicate* 'scantype' indicates whether we need plain or bitmap scan support* 'skip_nonnative_saop' indicates whether to accept SAOP if index AM doesn't* 'skip_lower_saop' indicates whether to accept non-first-column SAOP*/
static List *
build_index_paths(PlannerInfo *root, RelOptInfo *rel,IndexOptInfo *index, IndexClauseSet *clauses,bool useful_predicate,ScanTypeControl scantype,bool *skip_nonnative_saop,bool *skip_lower_saop)
{List	   *result = NIL;IndexPath  *ipath;List	   *index_clauses;Relids		outer_relids;double		loop_count;List	   *orderbyclauses;List	   *orderbyclausecols;List	   *index_pathkeys;List	   *useful_pathkeys;bool		found_lower_saop_clause;bool		pathkeys_possibly_useful;bool		index_is_ordered;bool		index_only_scan;int			indexcol;/** Check that index supports the desired scan type(s)*/switch (scantype){case ST_INDEXSCAN:if (!index->amhasgettuple)return NIL;break;case ST_BITMAPSCAN:if (!index->amhasgetbitmap)return NIL;break;case ST_ANYSCAN:/* either or both are OK */break;}/** 1. Combine the per-column IndexClause lists into an overall list.** In the resulting list, clauses are ordered by index key, so that the* column numbers form a nondecreasing sequence.  (This order is depended* on by btree and possibly other places.)  The list can be empty, if the* index AM allows that.** found_lower_saop_clause is set true if we accept a ScalarArrayOpExpr* index clause for a non-first index column.  This prevents us from* assuming that the scan result is ordered.  (Actually, the result is* still ordered if there are equality constraints for all earlier* columns, but it seems too expensive and non-modular for this code to be* aware of that refinement.)** We also build a Relids set showing which outer rels are required by the* selected clauses.  Any lateral_relids are included in that, but not* otherwise accounted for.*/index_clauses = NIL;found_lower_saop_clause = false;outer_relids = bms_copy(rel->lateral_relids);for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++){ListCell   *lc;foreach(lc, clauses->indexclauses[indexcol]){IndexClause *iclause = (IndexClause *) lfirst(lc);RestrictInfo *rinfo = iclause->rinfo;/* We might need to omit ScalarArrayOpExpr clauses */if (IsA(rinfo->clause, ScalarArrayOpExpr)){if (!index->amsearcharray){if (skip_nonnative_saop){/* Ignore because not supported by index */*skip_nonnative_saop = true;continue;}/* Caller had better intend this only for bitmap scan */Assert(scantype == ST_BITMAPSCAN);}if (indexcol > 0){if (skip_lower_saop){/* Caller doesn't want to lose index ordering */*skip_lower_saop = true;continue;}found_lower_saop_clause = true;}}/* OK to include this clause */index_clauses = lappend(index_clauses, iclause);outer_relids = bms_add_members(outer_relids,rinfo->clause_relids);}/** If no clauses match the first index column, check for amoptionalkey* restriction.  We can't generate a scan over an index with* amoptionalkey = false unless there's at least one index clause.* (When working on columns after the first, this test cannot fail. It* is always okay for columns after the first to not have any* clauses.)*/if (index_clauses == NIL && !index->amoptionalkey)return NIL;}/* We do not want the index's rel itself listed in outer_relids */outer_relids = bms_del_member(outer_relids, rel->relid);/* Enforce convention that outer_relids is exactly NULL if empty */if (bms_is_empty(outer_relids))outer_relids = NULL;/* Compute loop_count for cost estimation purposes */loop_count = get_loop_count(root, rel->relid, outer_relids);/** 2. Compute pathkeys describing index's ordering, if any, then see how* many of them are actually useful for this query.  This is not relevant* if we are only trying to build bitmap indexscans, nor if we have to* assume the scan is unordered.*/pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&!found_lower_saop_clause &&has_useful_pathkeys(root, rel));index_is_ordered = (index->sortopfamily != NULL);if (index_is_ordered && pathkeys_possibly_useful){index_pathkeys = build_index_pathkeys(root, index,ForwardScanDirection);useful_pathkeys = truncate_useless_pathkeys(root, rel,index_pathkeys);orderbyclauses = NIL;orderbyclausecols = NIL;}else if (index->amcanorderbyop && pathkeys_possibly_useful){/* see if we can generate ordering operators for query_pathkeys */match_pathkeys_to_index(index, root->query_pathkeys,&orderbyclauses,&orderbyclausecols);if (orderbyclauses)useful_pathkeys = root->query_pathkeys;elseuseful_pathkeys = NIL;}else{useful_pathkeys = NIL;orderbyclauses = NIL;orderbyclausecols = NIL;}// 这里非常重要,检查是否可以进行index-only scan/** 3. Check if an index-only scan is possible.  If we're not building* plain indexscans, this isn't relevant since bitmap scans don't support* index data retrieval anyway.*/index_only_scan = (scantype != ST_BITMAPSCAN &&check_index_only(rel, index));/** 4. Generate an indexscan path if there are relevant restriction clauses* in the current clauses, OR the index ordering is potentially useful for* later merging or final output ordering, OR the index has a useful* predicate, OR an index-only scan is possible.*/if (index_clauses != NIL || useful_pathkeys != NIL || useful_predicate ||index_only_scan){ipath = create_index_path(root, index,index_clauses,orderbyclauses,orderbyclausecols,useful_pathkeys,index_is_ordered ?ForwardScanDirection :NoMovementScanDirection,index_only_scan,outer_relids,loop_count,false);result = lappend(result, ipath);/** If appropriate, consider parallel index scan.  We don't allow* parallel index scan for bitmap index scans.*/if (index->amcanparallel &&rel->consider_parallel && outer_relids == NULL &&scantype != ST_BITMAPSCAN){ipath = create_index_path(root, index,index_clauses,orderbyclauses,orderbyclausecols,useful_pathkeys,index_is_ordered ?ForwardScanDirection :NoMovementScanDirection,index_only_scan,outer_relids,loop_count,true);/** if, after costing the path, we find that it's not worth using* parallel workers, just free it.*/if (ipath->path.parallel_workers > 0)add_partial_path(rel, (Path *) ipath);elsepfree(ipath);}}/** 5. If the index is ordered, a backwards scan might be interesting.*/if (index_is_ordered && pathkeys_possibly_useful){index_pathkeys = build_index_pathkeys(root, index,BackwardScanDirection);useful_pathkeys = truncate_useless_pathkeys(root, rel,index_pathkeys);if (useful_pathkeys != NIL){ipath = create_index_path(root, index,index_clauses,NIL,NIL,useful_pathkeys,BackwardScanDirection,index_only_scan,outer_relids,loop_count,false);result = lappend(result, ipath);/* If appropriate, consider parallel index scan */if (index->amcanparallel &&rel->consider_parallel && outer_relids == NULL &&scantype != ST_BITMAPSCAN){ipath = create_index_path(root, index,index_clauses,NIL,NIL,useful_pathkeys,BackwardScanDirection,index_only_scan,outer_relids,loop_count,true);/** if, after costing the path, we find that it's not worth* using parallel workers, just free it.*/if (ipath->path.parallel_workers > 0)add_partial_path(rel, (Path *) ipath);elsepfree(ipath);}}}return result;
}
执行器

输入执行计划,输出最终结果。执行索引扫描。

主流程
PortalStart
--> ExecutorStart--> InitPlan--> ExecInitIndexScan
PortalRun
--> ExecutorRun--> ExecutePlan--> ExecIndexScan--> ExecScan--> ExecScanFetch--> IndexNext--> index_beginscan  --> index_beginscan_internal  --> btbeginscan	//start a scan on a btree index--> table_index_fetch_begin--> heapam_index_fetch_begin--> index_getnext_slot--> index_getnext_tid--> btgettuple
PortalDrop

具体实现代码如下:

/* ----------------------------------------------------------------*		ExecIndexScan(node)* ----------------------------------------------------------------*/
static TupleTableSlot *
ExecIndexScan(PlanState *pstate)
{IndexScanState *node = castNode(IndexScanState, pstate);/** If we have runtime keys and they've not already been set up, do it now.*/if (node->iss_NumRuntimeKeys != 0 && !node->iss_RuntimeKeysReady)ExecReScan((PlanState *) node);if (node->iss_NumOrderByKeys > 0)return ExecScan(&node->ss,(ExecScanAccessMtd) IndexNextWithReorder,(ExecScanRecheckMtd) IndexRecheck);elsereturn ExecScan(&node->ss,(ExecScanAccessMtd) IndexNext,(ExecScanRecheckMtd) IndexRecheck);
}/** ExecScanFetch -- check interrupts & fetch next potential tuple** This routine is concerned with substituting a test tuple if we are* inside an EvalPlanQual recheck.  If we aren't, just execute* the access method's next-tuple routine.*/
static inline TupleTableSlot *ExecScanFetch(ScanState *node, ExecScanAccessMtd accessMtd, ExecScanRecheckMtd recheckMtd)
{EState	   *estate = node->ps.state;if (estate->es_epq_active != NULL){// ...}/* Run the node-type-specific access method function to get the next tuple */return (*accessMtd) (node);
}/* ----------------------------------------------------------------*		IndexNext**		Retrieve a tuple from the IndexScan node's currentRelation*		using the index specified in the IndexScanState information.* ---------------------------------------------------------------- */
static TupleTableSlot *IndexNext(IndexScanState *node)
{EState	   *estate;ExprContext *econtext;ScanDirection direction;IndexScanDesc scandesc;TupleTableSlot *slot;/* extract necessary information from index scan node */estate = node->ss.ps.state;direction = estate->es_direction;/* flip direction if this is an overall backward scan */if (ScanDirectionIsBackward(((IndexScan *) node->ss.ps.plan)->indexorderdir)){if (ScanDirectionIsForward(direction))direction = BackwardScanDirection;else if (ScanDirectionIsBackward(direction))direction = ForwardScanDirection;}scandesc = node->iss_ScanDesc;econtext = node->ss.ps.ps_ExprContext;slot = node->ss.ss_ScanTupleSlot;if (scandesc == NULL){/** We reach here if the index scan is not parallel, or if we're* serially executing an index scan that was planned to be parallel.*/scandesc = index_beginscan(node->ss.ss_currentRelation,node->iss_RelationDesc,estate->es_snapshot,node->iss_NumScanKeys,node->iss_NumOrderByKeys);node->iss_ScanDesc = scandesc;/** If no run-time keys to calculate or they are ready, go ahead and* pass the scankeys to the index AM.*/if (node->iss_NumRuntimeKeys == 0 || node->iss_RuntimeKeysReady)index_rescan(scandesc,node->iss_ScanKeys, node->iss_NumScanKeys,node->iss_OrderByKeys, node->iss_NumOrderByKeys);}/* ok, now that we have what we need, fetch the next tuple. */while (index_getnext_slot(scandesc, direction, slot)){CHECK_FOR_INTERRUPTS();/* If the index was lossy, we have to recheck the index quals using the fetched tuple. */if (scandesc->xs_recheck){econtext->ecxt_scantuple = slot;if (!ExecQualAndReset(node->indexqualorig, econtext)){/* Fails recheck, so drop it and loop back for another */InstrCountFiltered2(node, 1);continue;}}return slot;}/* if we get here it means the index scan failed so we are at the end of the scan.. */node->iss_ReachedEnd = true;return ExecClearTuple(slot);
}
索引扫描

索引与表一样,单独存储,同样是被切分成很多页,索引扫描,先访问索引(访问索引所在页,在索引页中继续找,找到匹配的键,获取被查找的表的元组的TID,再根据该TID去访问表的某页,根据页偏移找到指定元组返回)

/* Descriptor for heap table scans.*/
typedef struct HeapScanDescData
{TableScanDescData rs_base;	/* AM independent part of the descriptor *//* state set up at initscan time */BlockNumber rs_nblocks;		/* total number of blocks in rel */BlockNumber rs_startblock;	/* block # to start at */BlockNumber rs_numblocks;	/* max number of blocks to scan *//* rs_numblocks is usually InvalidBlockNumber, meaning "scan whole rel" *//* scan current state */bool		rs_inited;		/* false = scan not init'd yet */BlockNumber rs_cblock;		/* current block # in scan, if any */Buffer		rs_cbuf;		/* current buffer in scan, if any *//* NB: if rs_cbuf is not InvalidBuffer, we hold a pin on that buffer *//* rs_numblocks is usually InvalidBlockNumber, meaning "scan whole rel" */BufferAccessStrategy rs_strategy;	/* access strategy for reads */HeapTupleData rs_ctup;		/* current tuple in scan, if any *//* these fields only used in page-at-a-time mode and for bitmap scans */int			rs_cindex;		/* current tuple's index in vistuples */int			rs_ntuples;		/* number of visible tuples on page */OffsetNumber rs_vistuples[MaxHeapTuplesPerPage];	/* their offsets */
}			HeapScanDescData;
typedef struct HeapScanDescData *HeapScanDesc;

更详细的代码要阅读nbtree.c中的实现。

/* btgettuple() -- Get the next tuple in the scan. */
bool btgettuple(IndexScanDesc scan, ScanDirection dir)
{BTScanOpaque so = (BTScanOpaque) scan->opaque;bool		res;/* btree indexes are never lossy */scan->xs_recheck = false;/** If we have any array keys, initialize them during first call for a* scan.  We can't do this in btrescan because we don't know the scan* direction at that time.*/if (so->numArrayKeys && !BTScanPosIsValid(so->currPos)){/* punt if we have any unsatisfiable array keys */if (so->numArrayKeys < 0)return false;_bt_start_array_keys(scan, dir);}/* This loop handles advancing to the next array elements, if any */do{/** If we've already initialized this scan, we can just advance it in* the appropriate direction.  If we haven't done so yet, we call* _bt_first() to get the first item in the scan.*/if (!BTScanPosIsValid(so->currPos))res = _bt_first(scan, dir);else{/** Check to see if we should kill the previously-fetched tuple.*/if (scan->kill_prior_tuple){/** Yes, remember it for later. (We'll deal with all such* tuples at once right before leaving the index page.)  The* test for numKilled overrun is not just paranoia: if the* caller reverses direction in the indexscan then the same* item might get entered multiple times. It's not worth* trying to optimize that, so we don't detect it, but instead* just forget any excess entries.*/if (so->killedItems == NULL)so->killedItems = (int *)palloc(MaxTIDsPerBTreePage * sizeof(int));if (so->numKilled < MaxTIDsPerBTreePage)so->killedItems[so->numKilled++] = so->currPos.itemIndex;}/** Now continue the scan.*/res = _bt_next(scan, dir);}/* If we have a tuple, return it ... */if (res)break;/* ... otherwise see if we have more array keys to deal with */} while (so->numArrayKeys && _bt_advance_array_keys(scan, dir));return res;
}

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

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

相关文章

a-select vModel时placeholder不显示问题

目录 a-select vModel时placeholder不显示问题 a-select vModel时placeholder不显示问题 问题描述&#xff1a;select 组件绑定了v-model和placeholder&#xff0c;v-model的value默认值为空字符串或者null时&#xff0c;select组件的placeholder属性不显示 <a-select v-m…

用小频谱仪观察收音机的本振

我找了一颗中周&#xff0c;想搭建一个几百khz的振荡电路&#xff0c;类似中波的本振&#xff0c;但是搭建了却看不到任何起振的波形。 遂想先看看已经装调好的中波收音机的本质波形是怎么样的&#xff1f; 用杜邦线加上一个夹子。把线夹到小频谱的天线杆上面。 取信号位置可…

windows实现python串口编程

一、windows安装python Welcome to Python.org 根据windows是64位找到对应的版本下载 下载完后直接安装即可&#xff01; 打开cmd查看python版本 $ python --version #查看版本 二、串口编程 1、安装pyserial库 pyserial是Python中的一个库,用于处理串口通信。 cmd…

ARM32开发——中断

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 中断概念中断分类中断触发条件 中断概念 中断是计算机系统中的一种机制&#xff0c;用于响应外部事件或内部事件,它可以使单片机暂…

former系列在时间序列预测任务上的研究综述

总&#xff1a;基于Transformer的LSTF解决方案 现有基于Transformer的LSTM解决方案设计要素总结如下&#xff1a; 从图中可以看出&#xff0c;Transformer在时序中应用具体包含以下几个步骤&#xff1a; 1&#xff09;时序分解&#xff1a;对于数据处理&#xff0c;TSF中0均…

5、分支对比 - 课件

一、基础知识 对比项双分支多分支多个if语句语法if-else语句if-else if-else语句多个独立的if语句分支数量只有两个分支可以有多个分支每个if语句都是一个独立的分支判断条件单一条件判断,结果为真或假按顺序判断多个条件,只执行第一个满足的分支每个if语句有独立的判断条件执…

OpenGL3.3_C++_Windows(6)

添加一个Editor camera /\/\/\/\/\//\/\/\/\/\/\/\/\//\/\///\/\/\/\//\/\/\/\//\//\/\/\/\/\\/原理&#xff1a;接受用户输入&#xff08;鼠标&#xff0c;按键……&#xff09;&#xff0c;通过&#xff08;callback / 内置API&#xff09;获取差值 / 是否触发用户输入&…

极速查询:StarRocks 存算分离 Compaction 原理 调优指南

作者&#xff1a;丁凯&#xff0c;StarRocks TSC member/镜舟科技云原生技术负责人 StarRocks 在数据摄入过程中&#xff0c;每次操作都会创建一个新的数据版本。在查询时&#xff0c;为了得到准确的结果&#xff0c;必须将所有版本合并。然而&#xff0c;随着历史数据版本的累…

关于 MySQL 5.7 升级 8.0 时 INT 显示问题分析

本案例中的客户是从 MySQL 5.7.36 升级到 MySQL 8.0.35&#xff0c;升级完成后业务反馈表结构有变化&#xff0c;升级前某个表的字段数据类型为 INT(10)&#xff0c;升级后变成了 INT&#xff0c;客户咨询有什么影响没有&#xff1f; 作者&#xff1a;张昊&#xff0c;DBA&…

JavaScript日期对象、DOM节点操作(查找、增加、克隆、删除)

目录 1. 日期对象2. DOM节点操作2.1 查找节点2.2 增加节点2.3 克隆节点2.4 删除节点 1. 日期对象 实例化日期对象&#xff1a; 获取当前时间: new Date()获取指定时间: new Date(2023-12-1 17:12:08) 日期对象方法: 方法作用说明getFullYear()获得年份获取四位年份getMonth…

logback-spring.xml 小记

为什么不用logback.xml 名字 加载顺序:logback.xml>application.yml>logback-spring.xml 使用xml中使用到配置文件属性时,就会报错 为什么logback中记录不到运行时报错 logback获取不到堆栈错误 解决办法:在全局错误出使用log.error()指定输出 为什么打印不出来myba…

五大API接口:提升你的应用性能与用户体验

引言&#xff1a; 简述API接口的重要性引入API接口对于提升应用性能和用户体验的贡献 API接口简介&#xff1a; 定义&#xff1a;解释什么是API接口作用&#xff1a;概述API接口在软件开发中的作用 1. 数据访问API 功能描述&#xff1a;提供快速、安全的数据存取功能提升性…

“首秀”欧洲杯,海信冰箱欧洲市占率居国产品牌首位

随着欧洲杯的火热开赛&#xff0c;挑灯夜战、观看球赛的时刻已经来临。此时&#xff0c;你需要何物相伴&#xff1f;是打开冰箱&#xff0c;取出真空腌制的食材&#xff0c;亲手烹饪一场观赛盛宴&#xff1f;还是取出极致保鲜的荔枝、樱桃&#xff0c;一边观赛一边品味&#xf…

部署yum仓库

目录 安装软件包 yum 配置文件 缓存功能操作步骤 创建并配置本地仓库文件 yum相关命令 yum install __ yum repolist yum list __ yum info __ yum search __ yum whatprovides __ yum remove __ yum -y update __ yum history yum grouplist yum groupinstall…

如何使用Vue3和ApexCharts轻松创建交互式图表

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 基于Vue3的ApexCharts动态图表展示 应用场景 本代码段适用于在Vue3应用中使用ApexCharts库创建交互式、可视化的图表。这些图表可以用于展示各种数据&#xff0c;例如销售额、支出、利润等&#xff0c;从而帮…

Microsoft Store打不开解决办法

//卸载Microsoft Store Get-AppxPackage -alluser WindowsStore | Remove-AppxPackage //重装 Get-AppxPackage -AllUsers Microsoft.WindowsStore* | Foreach {Add-AppxPackage -DisableDevelopmentMode -Register “ ( ( (_.InstallLocation)\AppXManifest.xml”}

ARP攻击和DNS攻击有什么区别

ARP攻击和DNS攻击的明显区别 一、定义与原理 ARP攻击&#xff1a; 定义&#xff1a;ARP攻击&#xff08;Address Resolution Protocol Attack&#xff09;是局域网最常见的一种攻击方式&#xff0c;其基本原理是通过伪造ARP数据包来窃取合法用户的通信数据&#xff0c;造成影…

006 spring事务支持

文章目录 事务回顾事务介绍事务并发问题(隔离性导致)事务隔离级别 Spring框架事务管理相关接口Spring框架事务管理的分类编程式事务管理(了解)声明式事务管理(重点) 事务管理之XML方式业务层持久层单元测试代码配置事务管理的AOP 事务管理之混合方式事务管理之基于AspectJ的纯注…

不重新安装Anaconda找回不见的Anaconda Prompt

找回Anaconda Prompt只需三步 系统&#xff1a;win11 x641.cd Anaconda的安装目录2. Anaconda Prompt又回来了 系统&#xff1a;win11 x64 1.cd Anaconda的安装目录 winR 输入cmd 进入命令行&#xff0c;进入到Anaconda的安装目录 eg&#xff1a;我的Anaconda安装在&#xff…

一个简单的信号发射电路的构建

在基本的信号发射电路中&#xff0c;线圈&#xff08;电感器&#xff09;和电阻的组合可以产生振荡信号&#xff0c;而天线&#xff08;通常通过线圈&#xff09;用于发射信号。 LC振荡电路&#xff1a; **线圈&#xff08;L1&#xff09;和电容器&#xff08;C&#xff09;**串…