当前位置: 首页 > news >正文

PostgreSQL的学习心得和知识总结(一百四十四)|深入理解PostgreSQL数据库之sendTuples的实现原理及功能修改


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

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


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


深入理解PostgreSQL数据库之sendTuples的实现原理及功能修改

  • 文章快速说明索引
  • 功能使用背景说明
    • 报文解析
    • 结果接收
  • 源码改造实现分析



文章快速说明索引

学习目标:

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


学习内容:(详见目录)

1、深入理解PostgreSQL数据库之sendTuples的实现原理及功能修改


学习时间:

2024年05月30日 21:22:11


学习产出:

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


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

postgres=# select version();version                                                   
------------------------------------------------------------------------------------------------------------PostgreSQL 17devel 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>

功能使用背景说明

我们先看一个例子,如下:

postgres=# create table t1 (id int);
2024-05-30 06:35:26.573 PDT [72446] LOG:  duration: 2.073 ms
CREATE TABLE
postgres=# insert into t1 select generate_series(1, 2);
2024-05-30 06:35:38.353 PDT [72446] LOG:  duration: 0.927 ms
INSERT 0 2
postgres=# create table t2 as select * from t1;
2024-05-30 06:35:42.241 PDT [72446] LOG:  duration: 1.356 ms
SELECT 2
postgres=# select * from t1, t2;
2024-05-30 06:35:48.143 PDT [72446] LOG:  duration: 0.380 msid | id 
----+----1 |  11 |  22 |  12 |  2
(4 rows)postgres=#

这是一个非常简单的select语句,接下来我们使用抓包工具捕获通信协议,如下:

有兴趣的小伙伴们可以看一下本人之前的博客,如下:

  • PostgreSQL的学习心得和知识总结(一百一十三)|Linux环境下Wireshark工具的安装及抓包PostgreSQL建立连接过程,点击前往

在这里插入图片描述


报文解析

Q报文,如下:

Frame 1: 113 bytes on wire (904 bits), 113 bytes captured (904 bits) on interface 0
Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)
Internet Protocol Version 6, Src: ::1, Dst: ::1
Transmission Control Protocol, Src Port: 47996, Dst Port: 5432, Seq: 1, Ack: 1, Len: 27
PostgreSQLType: Simple queryLength: 26Query: select * from t1, t2;

T D C Z报文,如下:

Frame 2: 223 bytes on wire (1784 bits), 223 bytes captured (1784 bits) on interface 0
Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)
Internet Protocol Version 6, Src: ::1, Dst: ::1
Transmission Control Protocol, Src Port: 5432, Dst Port: 47996, Seq: 1, Ack: 28, Len: 137
PostgreSQLType: Row descriptionLength: 48Field count: 2
PostgreSQLType: Data rowLength: 16Field count: 2
PostgreSQLType: Data rowLength: 16Field count: 2
PostgreSQLType: Data rowLength: 16Field count: 2
PostgreSQLType: Data rowLength: 16Field count: 2
PostgreSQLType: Command completionLength: 13Tag: SELECT 4
PostgreSQLType: Ready for queryLength: 5Status: Idle (73)

我们这里不再详解PostgreSQL所有报文的含义及源码实现,我们这里只看一下以上几种的即可,如下:


Q报文的发送,如下:

// src/interfaces/libpq/fe-exec.cstatic int
PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
{.../* Send the query message(s) *//* construct the outgoing Query message */if (pqPutMsgStart(PqMsg_Query, conn) < 0 ||pqPuts(query, conn) < 0 ||pqPutMsgEnd(conn) < 0){/* error message should be set up already */pqRecycleCmdQueueEntry(conn, entry);return 0;}...
}

T报文的发送,如下:

// src/backend/access/common/printtup.c/** SendRowDescriptionMessage --- send a RowDescription message to the frontend* SendRowDescriptionMessage --- 向前端发送 RowDescription 消息** Notes: the TupleDesc has typically been manufactured by ExecTypeFromTL()* or some similar function; it does not contain a full set of fields.* 注意:TupleDesc 通常由 ExecTypeFromTL() 或某些类似函数生成;它不包含完整的字段集* * The targetlist will be NIL when executing a utility function that does* not have a plan.  If the targetlist isn't NIL then it is a Query node's* targetlist; it is up to us to ignore resjunk columns in it.  The formats[]* array pointer might be NULL (if we are doing Describe on a prepared stmt);* send zeroes for the format codes in that case.* 执行没有计划的实用程序函数时,目标列表将为 NIL* 如果目标列表不是 NIL,则它就是查询节点的目标列表;我们可以忽略其中的 resjunk 列* formats[] 数组指针可能为 NULL(如果我们在准备好的 stmt 上执行 Describe)* 在这种情况下,为格式代码发送零*/
void
SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,List *targetlist, int16 *formats)
{int			natts = typeinfo->natts;int			i;ListCell   *tlist_item = list_head(targetlist);/* tuple descriptor message type */pq_beginmessage_reuse(buf, 'T');/* # of attrs in tuples */pq_sendint16(buf, natts);/** Preallocate memory for the entire message to be sent. That allows to* use the significantly faster inline pqformat.h functions and to avoid* reallocations.** Have to overestimate the size of the column-names, to account for* character set overhead.*/enlargeStringInfo(buf, (NAMEDATALEN * MAX_CONVERSION_GROWTH /* attname */+ sizeof(Oid)	/* resorigtbl */+ sizeof(AttrNumber)	/* resorigcol */+ sizeof(Oid)	/* atttypid */+ sizeof(int16) /* attlen */+ sizeof(int32) /* attypmod */+ sizeof(int16) /* format */) * natts);for (i = 0; i < natts; ++i){Form_pg_attribute att = TupleDescAttr(typeinfo, i);...pq_writestring(buf, NameStr(att->attname));pq_writeint32(buf, resorigtbl);pq_writeint16(buf, resorigcol);pq_writeint32(buf, atttypid);pq_writeint16(buf, att->attlen);pq_writeint32(buf, atttypmod);pq_writeint16(buf, format);}pq_endmessage_reuse(buf);
}

D报文的发送,如下:

// src/backend/access/common/printtup.c/* ----------------*		printtup --- send a tuple to the client** Note: if you change this function, see also serializeAnalyzeReceive* in explain.c, which is meant to replicate the computations done here.* * 注意:如果您更改此函数,另请参阅 explain.c 中的 serializeAnalyzeReceive,旨在复制此处完成的计算* ----------------*/
static bool
printtup(TupleTableSlot *slot, DestReceiver *self)
{TupleDesc	typeinfo = slot->tts_tupleDescriptor;DR_printtup *myState = (DR_printtup *) self;MemoryContext oldcontext;StringInfo	buf = &myState->buf;int			natts = typeinfo->natts;int			i;/* Set or update my derived attribute info, if needed */if (myState->attrinfo != typeinfo || myState->nattrs != natts)printtup_prepare_info(myState, typeinfo, natts);/* Make sure the tuple is fully deconstructed */slot_getallattrs(slot);/* Switch into per-row context so we can recover memory below */oldcontext = MemoryContextSwitchTo(myState->tmpcontext);/** Prepare a DataRow message (note buffer is in per-query context)*/pq_beginmessage_reuse(buf, 'D');pq_sendint16(buf, natts);/** send the attributes of this tuple*/for (i = 0; i < natts; ++i){PrinttupAttrInfo *thisState = myState->myinfo + i;Datum		attr = slot->tts_values[i];...}pq_endmessage_reuse(buf);/* Return to caller's context, and flush row's temporary memory */MemoryContextSwitchTo(oldcontext);MemoryContextReset(myState->tmpcontext);return true;
}

这里发送一个tuple,如下:

在这里插入图片描述


C报文的发送,如下:

// src/backend/tcop/dest.c/* ----------------*		EndCommand - clean up the destination at end of command* ----------------*/
void
EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_output)
{char		completionTag[COMPLETION_TAG_BUFSIZE];Size		len;switch (dest){case DestRemote:case DestRemoteExecute:case DestRemoteSimple:len = BuildQueryCompletionString(completionTag, qc,force_undecorated_output);pq_putmessage(PqMsg_CommandComplete, completionTag, len + 1);case DestNone:case DestDebug:case DestSPI:case DestTuplestore:case DestIntoRel:case DestCopyOut:case DestSQLFunction:case DestTransientRel:case DestTupleQueue:case DestExplainSerialize:break;}
}

在这里插入图片描述


Z报文的发送,如下:

// src/backend/tcop/dest.c/* ----------------*		ReadyForQuery - tell dest that we are ready for a new query**		The ReadyForQuery message is sent so that the FE can tell when*		we are done processing a query string.*		In versions 3.0 and up, it also carries a transaction state indicator.*		发送 ReadyForQuery 消息是为了让 FE 知道我们何时处理完查询字符串*		在 3.0 及更高版本中,它还带有事务状态指示器**		Note that by flushing the stdio buffer here, we can avoid doing it*		most other places and thus reduce the number of separate packets sent.*		请注意,通过在此处刷新 stdio 缓冲区,我们可以避免在大多数其他地方执行此操作,从而减少发送的单独数据包的数量* ----------------*/
void
ReadyForQuery(CommandDest dest)
{switch (dest){case DestRemote:case DestRemoteExecute:case DestRemoteSimple:{StringInfoData buf;pq_beginmessage(&buf, PqMsg_ReadyForQuery);pq_sendbyte(&buf, TransactionBlockStatusCode());pq_endmessage(&buf);}/* Flush output at end of cycle in any case. */pq_flush();break;case DestNone:case DestDebug:case DestSPI:case DestTuplestore:case DestIntoRel:case DestCopyOut:case DestSQLFunction:case DestTransientRel:case DestTupleQueue:case DestExplainSerialize:break;}
}

在这里插入图片描述


上面D报文的发送,也就是结果集的发送,这里不再深入介绍。接下来详细看一下client一侧接收这种结果(也即解析D报文)的逻辑,如下:

结果接收

/** parseInput: if appropriate, parse input data from backend* until input is exhausted or a stopping state is reached.* Note that this function will NOT attempt to read more data from the backend.* * parseInput:如果合适,解析来自后端的输入数据* 直到输入耗尽或达到停止状态* 请注意,此函数不会尝试从后端读取更多数据*/
void
pqParseInput3(PGconn *conn)
{char		id;int			msgLength;int			avail;/** Loop to parse successive complete messages available in the buffer.* 循环解析缓冲区中可用的连续完整消息*/for (;;){/** Try to read a message.  First get the type code and length. Return* if not enough data.* 尝试读取一条消息。首先获取类型代码和长度。如果数据不足则返回*/...if (id == PqMsg_NotificationResponse){if (getNotify(conn))return;}else if (id == PqMsg_NoticeResponse){if (pqGetErrorNotice3(conn, false))return;}else if (conn->asyncStatus != PGASYNC_BUSY){...}else{/** In BUSY state, we can process everything.*/switch (id){case PqMsg_CommandComplete:if (pqGets(&conn->workBuffer, conn))return;if (!pgHavePendingResult(conn)){conn->result = PQmakeEmptyPGresult(conn,PGRES_COMMAND_OK);if (!conn->result){libpq_append_conn_error(conn, "out of memory");pqSaveErrorResult(conn);}}if (conn->result)strlcpy(conn->result->cmdStatus, conn->workBuffer.data,CMDSTATUS_LEN);conn->asyncStatus = PGASYNC_READY;break;...case PqMsg_DataRow:if (conn->result != NULL &&(conn->result->resultStatus == PGRES_TUPLES_OK ||conn->result->resultStatus == PGRES_TUPLES_CHUNK)){/* Read another tuple of a normal query response *//* 读取正常查询响应的另一个元组 */if (getAnotherTuple(conn, msgLength))return;}else if (conn->error_result ||(conn->result != NULL &&conn->result->resultStatus == PGRES_FATAL_ERROR)){/** We've already choked for some reason.  Just discard* tuples till we get to the end of the query.*/conn->inCursor += msgLength;}else{/* Set up to report error at end of query */libpq_append_conn_error(conn, "server sent data (\"D\" message) without prior row description (\"T\" message)");pqSaveErrorResult(conn);/* Discard the unexpected message */conn->inCursor += msgLength;}break;...}					/* switch on protocol character */}/* Successfully consumed this message */if (conn->inCursor == conn->inStart + 5 + msgLength){/* trace server-to-client message */if (conn->Pfdebug)pqTraceOutputMessage(conn, conn->inBuffer + conn->inStart, false);/* Normal case: parsing agrees with specified length */conn->inStart = conn->inCursor;}else{/* Trouble --- report it */libpq_append_conn_error(conn, "message contents do not agree with length in message type \"%c\"", id);/* build an error result holding the error message */pqSaveErrorResult(conn);conn->asyncStatus = PGASYNC_READY;/* trust the specified message length as what to skip */conn->inStart += 5 + msgLength;}}
}

如上getAnotherTuple函数负责具体的tuple构造,如下:

// src/interfaces/libpq/fe-protocol3.c/** parseInput subroutine to read a 'D' (row data) message.* We fill rowbuf with column pointers and then call the row processor.* Returns: 0 if processed message successfully, EOF to suspend parsing* (the latter case is not actually used currently).* parseInput 子程序读取“D”(行数据)消息。* * 我们用列指针填充 rowbuf,然后调用行处理器* 返回:如果成功处理消息,则返回 0* 如果暂停解析,则返回 EOF(后一种情况目前实际上未使用)*/
static int
getAnotherTuple(PGconn *conn, int msgLength)
{PGresult   *result = conn->result;int			nfields = result->numAttributes;const char *errmsg;PGdataValue *rowbuf;int			tupnfields;		/* # fields from tuple */int			vlen;			/* length of the current field value */int			i;/* Get the field count and make sure it's what we expect */if (pqGetInt(&tupnfields, 2, conn)){/* We should not run out of data here, so complain */errmsg = libpq_gettext("insufficient data in \"D\" message");goto advance_and_error;}if (tupnfields != nfields){errmsg = libpq_gettext("unexpected field count in \"D\" message");goto advance_and_error;}/* Resize row buffer if needed */rowbuf = conn->rowBuf;if (nfields > conn->rowBufLen){rowbuf = (PGdataValue *) realloc(rowbuf,nfields * sizeof(PGdataValue));if (!rowbuf){errmsg = NULL;		/* means "out of memory", see below */goto advance_and_error;}conn->rowBuf = rowbuf;conn->rowBufLen = nfields;}/* Scan the fields */for (i = 0; i < nfields; i++){/* get the value length */if (pqGetInt(&vlen, 4, conn)){/* We should not run out of data here, so complain */errmsg = libpq_gettext("insufficient data in \"D\" message");goto advance_and_error;}rowbuf[i].len = vlen;/** rowbuf[i].value always points to the next address in the data* buffer even if the value is NULL.  This allows row processors to* estimate data sizes more easily.*/rowbuf[i].value = conn->inBuffer + conn->inCursor;/* Skip over the data value */if (vlen > 0){if (pqSkipnchar(vlen, conn)){/* We should not run out of data here, so complain */errmsg = libpq_gettext("insufficient data in \"D\" message");goto advance_and_error;}}}/* Process the collected row */errmsg = NULL;if (pqRowProcessor(conn, &errmsg))return 0;				/* normal, successful exit *//* pqRowProcessor failed, fall through to report it */advance_and_error:...return 0;
}

源码改造实现分析

在这里插入图片描述

这里tuple的发送 都是由这里的逻辑进行控制,如下:

	sendTuples = (operation == CMD_SELECT ||queryDesc->plannedstmt->hasReturning);

我今天介绍的功能(不能称之为功能)的目的在于:

  1. 对于大批量查询等返回tuple的语句,D报文的发送 和 解析过程将非常耗时
  2. 我这里既希望该大SQL执行下去,又期望不产生D报文
  3. 相关的统计工作 C报文 足矣

基于以上思考,这里修改代码 如下:(切不可当真)

diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4d7c92d63c..a1b824545b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -345,8 +345,7 @@ standard_ExecutorRun(QueryDesc *queryDesc,*/estate->es_processed = 0;-       sendTuples = (operation == CMD_SELECT ||
-                                 queryDesc->plannedstmt->hasReturning);
+       sendTuples = ((operation == CMD_SELECT || queryDesc->plannedstmt->hasReturning) && (not_send_tuple == false));if (sendTuples)dest->rStartup(dest, operation, queryDesc->tupDesc);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 46c258be28..c166768dfe 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -501,6 +501,7 @@ bool                Debug_print_plan = false;bool           Debug_print_parse = false;bool           Debug_print_rewritten = false;bool           Debug_pretty_print = true;
+bool           not_send_tuple = false;bool           log_parser_stats = false;bool           log_planner_stats = false;
@@ -1337,6 +1338,15 @@ struct config_bool ConfigureNamesBool[] =true,NULL, NULL, NULL},
+       {
+               {"not_send_tuple", PGC_USERSET, DEVELOPER_OPTIONS,
+                       gettext_noop("Not send tuples for testing."),
+                       NULL
+               },
+               &not_send_tuple,
+               false,
+               NULL, NULL, NULL
+       },{{"log_parser_stats", PGC_SUSET, STATS_MONITORING,gettext_noop("Writes parser performance statistics to the server log."),
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index e4a594b5e8..07123dd83b 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -244,6 +244,7 @@ extern PGDLLIMPORT bool Debug_print_plan;extern PGDLLIMPORT bool Debug_print_parse;extern PGDLLIMPORT bool Debug_print_rewritten;extern PGDLLIMPORT bool Debug_pretty_print;
+extern PGDLLIMPORT bool not_send_tuple;extern PGDLLIMPORT bool log_parser_stats;

使用如下:

[postgres@localhost:~/test/bin]$ ./psql
psql (17beta1)
Type "help" for help.postgres=# create table t1 (id int);
2024-05-30 05:10:42.486 PDT [63041] LOG:  duration: 2.423 ms
CREATE TABLE
postgres=# insert into t1 select generate_series(1, 10000);
2024-05-30 05:10:50.146 PDT [63041] LOG:  duration: 18.665 ms
INSERT 0 10000
postgres=# create table t2 as select * from t1;
2024-05-30 05:10:55.843 PDT [63041] LOG:  duration: 15.274 ms
SELECT 10000
postgres=# show not_send_tuple;
2024-05-30 05:10:59.054 PDT [63041] LOG:  duration: 0.132 msnot_send_tuple 
----------------off
(1 row)postgres=# \o /home/postgres/test/bin/1.txt
postgres=# select * from t1, t2;
2024-05-30 05:12:08.036 PDT [63041] LOG:  duration: 45920.595 ms
postgres=# \o
postgres=# 
postgres=# set not_send_tuple = on;
2024-05-30 05:15:18.267 PDT [63041] LOG:  duration: 0.652 ms
SET
postgres=# \o /home/postgres/test/bin/2.txt
postgres=# select * from t1, t2;
2024-05-30 05:15:42.149 PDT [63041] LOG:  duration: 11213.197 ms
postgres=# \o
postgres=#
[postgres@localhost:~/test/bin]$ ls -lht 1.txt 
-rw-rw-r--. 1 postgres postgres 1.4G May 30 05:14 1.txt
[postgres@localhost:~/test/bin]$ 
[postgres@localhost:~/test/bin]$ ls -lht 2.txt 
-rw-rw-r--. 1 postgres postgres 17 May 30 05:15 2.txt
[postgres@localhost:~/test/bin]$ 
[postgres@localhost:~/test/bin]$ cat 2.txt 
SELECT 100000000
[postgres@localhost:~/test/bin]$ tail -10 1.txt 9993 | 100009994 | 100009995 | 100009996 | 100009997 | 100009998 | 100009999 | 1000010000 | 10000
(100000000 rows)[postgres@localhost:~/test/bin]$

使用抓包工具,如下:

postgres=# create table t1 (id int);
CREATE TABLE
postgres=# insert into t1 select generate_series(1, 2);
INSERT 0 2
postgres=# create table t2 as select * from t1;
SELECT 2
postgres=# select * from t1, t2;id | id 
----+----1 |  11 |  22 |  12 |  2
(4 rows)postgres=# set not_send_tuple = on;
SET
postgres=# select * from t1, t2;
SELECT 4
postgres=#

在这里插入图片描述

相关文章:

PostgreSQL的学习心得和知识总结(一百四十四)|深入理解PostgreSQL数据库之sendTuples的实现原理及功能修改

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…...

C++数据结构之:链List

摘要&#xff1a; it人员无论是使用哪种高级语言开发东东&#xff0c;想要更高效有层次的开发程序的话都躲不开三件套&#xff1a;数据结构&#xff0c;算法和设计模式。数据结构是相互之间存在一种或多种特定关系的数据元素的集合&#xff0c;即带“结构”的数据元素的集合&am…...

10.Redis之set类型

谈到一个术语,这个术语很可能有多种含义~~ 1.Set 1) 集合. 2)设置 (和 get 相对应) 集合就是把一些有关联的数据放到一起~~ 1.集合中的元素是无序的! 【此处说的无序和 前面list这里的有序 是对应的, 有序: 顺序很重要. 变换一下顺序, 就是不同的 list 了 无序: 顺序不…...

SpringBoot + mongodb 删除集合中的数据

MongoTemplate是Spring Data MongoDB提供的一个工具类&#xff0c;用于与MongoDB进行交互。它提供了许多方法来执行数据库操作&#xff0c;包括删除数据。 本文将介绍如何使用Java MongoTemplate删除集合内的数据&#xff0c;并提供相应的代码示例。 1. 引入MongoTemplate 首…...

【日常记录】【JS】前端预览图片的两种方式,Base64预览和blob预览

文章目录 1、前言1、FileReader3、window.URL.createObjectURL4、参考链接 1、前言 一般来说&#xff0c;都是 后端返回给前端图片的url&#xff0c;前端直接把这个值插入到 img 的src 里面即可还有一种情况是前端需要预览一下图片&#xff0c;比如&#xff1a;上传头像按钮&a…...

每日刷题——杭电2156.分数矩阵和杭电2024.C语言合法标识符

杭电2156.分数矩阵 原题链接&#xff1a;Problem - 2156 题目描述 Problem Description&#xff1a;我们定义如下矩阵: 1/1 1/2 1/3 1/2 1/1 1/2 1/3 1/2 1/1 矩阵对角线上的元素始终是1/1&#xff0c;对角线两边分数的分母逐个递增。请求出这个矩阵的总和。 Input&#xf…...

爬虫学习--18.反爬斗争 selenium(3)

操作多窗口与页面切换 有时候窗口中有很多子tab页面。这时候肯定是需要进行切换的。selenium提供了一个叫做switch_to.window来进行切换&#xff0c;具体切换到哪个页面&#xff0c;可以从driver.window_handles中找到。 from selenium import webdriver from selenium.webdri…...

如何评价GPT-4o?

GPT-4o是OpenAI为聊天机器人ChatGPT发布的一款新语言模型&#xff0c;其名称中的“o”代表Omni&#xff0c;即全能的意思&#xff0c;凸显了其多功能的特性。这款模型在多个方面都有着显著的优势和进步。 首先&#xff0c;GPT-4o具有极强的多模态能力&#xff0c;它能够接受文本…...

算能BM1684+FPGA+AI+Camera推理边缘计算盒

搭载算丰智算芯片BM1684&#xff0c;是面向AI推理的边缘计算盒。高效适配市场上所有AI算法&#xff0c;实现视频结构化、人脸识别、行为分析、状态监测等应用&#xff0c;为智慧城市、智慧交通、智慧能源、智慧金融、智慧电信、智慧工业等领域进行AI赋能。 产品规格 处理器芯片…...

不同厂商SOC芯片在视频记录仪领域的应用

不同SoC公司芯片在不同产品上的应用信息&#xff1a; 大唐半导体 芯片型号: LC1860C (主控) LC1160 (PMU)产品应用: 红米2A (399元)大疆晓Spark技术规格: 28nm工艺&#xff0c;4个ARM Cortex-A7处理器&#xff0c;1.5GHz主频&#xff0c;2核MaliT628 GPU&#xff0c;1300万像…...

【Python入门学习笔记】Python3超详细的入门学习笔记,非常详细(适合小白入门学习)

Python3基础 想要获取pdf或markdown格式的笔记文件点击以下链接获取 Python入门学习笔记点击我获取 1&#xff0c;Python3 基础语法 1-1 编码 默认情况下&#xff0c;Python 3 源码文件以 UTF-8 编码&#xff0c;所有字符串都是 unicode 字符串。 当然你也可以为源码文件指…...

通用代码生成器应用场景三,遗留项目反向工程

通用代码生成器应用场景三&#xff0c;遗留项目反向工程 如果您有一个遗留项目&#xff0c;要重新开发&#xff0c;或者源代码遗失&#xff0c;或者需要重新开发&#xff0c;但是希望复用原来的数据&#xff0c;并加快开发。 如果您的项目是通用代码生成器生成的&#xff0c;…...

轻量级动态可监控线程池 - DynamicTp

一、背景介绍 使用线程池ThreadPoolExecutor的过程中你是否有以下痛点呢&#xff1f; 代码中创建了一个 ThreadPoolExecutor&#xff0c;但是不知道那几个核心参数设置多少比较合适凭经验设置参数值&#xff0c;上线后发现需要调整&#xff0c;改代码重新发布服务&#xff0c…...

对于vsc中的vue命令 vue.json

打开vsc 然后在左下角有一个设置 2.点击用户代码片段 3.输入 vue.json回车 将此代码粘贴 &#xff08;我的不一定都适合&#xff09; { "vue2 template": { "prefix": "v2", "body": [ "<template>", " <…...

Spring Boot 官方不再支持 Spring Boot 的 2.x 版本!新idea如何创建java8项目

idea现在只能创建最少jdk17 使用 IDEA 内置的 Spring Initializr 创建 Spring Boot 新项目时&#xff0c;没有 Java 8 的选项了&#xff0c;只剩下了 > 17 的版本 是因为 Spring Boot 官方不再支持 Spring Boot 的 2.x 版本了&#xff0c;之后全力维护 3.x&#xff1b;而 …...

分享一个 ASP.NET Web Api 上传和读取 Excel的方案

前言 许多业务场景下需要处理和分析大量的数据&#xff0c;而 Excel 是业务人员常用的数据表格工具&#xff0c;因此&#xff0c;将 Excel 表格中内容上传并读取到网站&#xff0c;是一个很常见的功能&#xff0c;目前有许多成熟的开源或者商业的第三方库&#xff0c;比如 NPO…...

【算法实战】每日一题:将某个序列中内的每个元素都设为相同的值的最短次数(差分数组解法,附概念理解以及实战操作)

题目 将某个序列中内的每个元素都设为相同的值的最短次数 1.差分数组&#xff08;后面的减去前面的值存储的位置可以理解为中间&#xff09; 差分数组用于处理序列中的区间更新和查询问题。它存储序列中相邻元素之间的差值&#xff0c;而不是直接存储每个元素的值 怎么对某…...

EXCEL数据透视图中的日期字段,怎样自动分出年、季度、月的功能?

在excel里&#xff0c;这个果然是有个设置的地方&#xff0c;修改后就好了。 点击文件选项卡&#xff0c;选项&#xff0c;在高级里&#xff0c;将图示选项的勾选给取消&#xff0c;然后再创建数据透视表或透视图&#xff0c;日期就不会自动组合了&#xff1a; 这个选项只对新…...

【设计模式深度剖析】【1】【行为型】【模板方法模式】| 以烹饪过程为例加深理解

&#x1f448;️上一篇:结构型设计模式对比 文章目录 模板方法模式定义英文原话直译如何理解呢&#xff1f; 2个角色类图代码示例 应用优点缺点使用场景 示例解析&#xff1a;以烹饪过程为例类图代码示例 模板方法模式 模板方法模式&#xff08;Template Method Pattern&…...

JAVA:异步任务处理类CompletableFuture让性能提升一倍

一、前言 CompletableFuture 是 Java 8 引入的一个功能强大的类&#xff0c;用于异步编程。它表示一个可能尚未完成的计算的结果&#xff0c;你可以对其添加回调函数来在计算完成时执行某些操作。在 Spring Boot 应用中&#xff0c;CompletableFuture 可以用于提高应用的响应性…...

10Linux 进程管理学习笔记

Linux 进程管理 目录 文章目录 Linux 进程管理一.进程1.显示当前进程状态(ps)进程树(pstree)1.1实时显示进程信息(top)顶部概览信息&#xff1a;CPU 状态&#xff1a;内存状态&#xff1a;进程信息表头&#xff1a;进程列表&#xff1a;1.2(htop) 2.终止进程(kill)2.1通过名称…...

一些关于深度聚类以及部分对比学习的论文阅读笔记

目录 资料SwAV问题方法方法的创新点为什么有效有什么可以借鉴的地方聚类Multi-crop 代码 PCL代码 Feature Alignment and Uniformity for Test Time Adaptation代码 SimSiam 资料 深度聚类算法研究综述(很赞&#xff0c;从聚类方法和深度学习方法两个方面进行了总结&#xff0…...

【ARM-Linux篇】u-boot编译

一、u-boot简介 uboot是一种通用的引导加载程序&#xff0c;它可以用于多种嵌入式系统&#xff0c;支持多种操作系统&#xff0c;如Linux, Android,NetBSD等。uboot的主要作用是将操作系统内核从存储设备&#xff08;如Flash, SD卡等&#xff09;加载到内存中&#xff0c;并执…...

Lombok一文通

1、Lombok简介 作为java的忠实粉丝&#xff0c;但也不得不承认&#xff0c;java是一门比较啰嗦的语言&#xff0c;很多代码的编写远不如其他静态语言方便&#xff0c;更别说跟脚本语言比较了。 因此&#xff0c;lombok应运而生。 Lombok是一种工具库&#xff0c;它提供了一组…...

Seq2Seq模型:详述其发展历程、深远影响与结构深度剖析

Seq2Seq&#xff08;Sequence-to-Sequence&#xff09;模型是一种深度学习架构&#xff0c;专为处理从一个输入序列到一个输出序列的映射任务设计。这种模型最初应用于机器翻译任务&#xff0c;但因其灵活性和有效性&#xff0c;现已被广泛应用于自然语言处理&#xff08;NLP&a…...

公网如何访问内网?

公网和内网已经成为我们生活中不可或缺的存在。由于内网的安全性考虑&#xff0c;公网无法直接访问内网资源。如何实现公网访问内网呢&#xff1f;本文将介绍一种名为【天联】的私有通道技术&#xff0c;通过安全加密&#xff0c;保障数据传输的安全性。 【天联】私有通道技术 …...

手机定制开发_基于天玑900的5G安卓手机定制方案

手机定制方案基于联发科天玑900强劲旗舰八核2.4GHz处理器。这款处理器采用了6nm先进制程工艺&#xff0c;为用户带来了痛快淋漓的性能体验。不论是进行游戏还是日常娱乐&#xff0c;用户都能轻松驾驭。手机搭载了最新的Android 13操作系统&#xff0c;提高了数据读取的准确性&a…...

免费,C++蓝桥杯等级考试真题--第2级

C蓝桥杯等级考试真题–第2级...

panic 、asset、crash 的含义和区别

在编程中&#xff0c;“panic” 和 “assert” 都是用于处理错误和异常情况的机制&#xff0c;但在不同的编程语言和框架中有一些区别。 panic&#xff1a; 含义&#xff1a;通常表示程序发生了无法恢复的错误或异常情况&#xff0c;需要立即终止程序的执行。 用法&#xff1…...

解决Windows 10通过SSH连接Ubuntu 20.04时的“Permission Denied”错误

在使用SSH连接远程服务器时&#xff0c;我们经常可能遇到各种连接错误&#xff0c;其中“Permission denied, please try again”是较为常见的一种。本文将分享一次实际案例的解决过程&#xff0c;帮助你理解如何排查并解决这类问题。 问题描述 在尝试从Windows 10系统通过SS…...

企业英语网站/怎么做网络营销推广

1 4 . 4 . 1 线程组所有线程都隶属于一个线程组。那可以是一个默认线程组&#xff0c;亦可是一个创建线程时明确指定的组。在创建之初&#xff0c;线程被限制到一个组里&#xff0c;而且不能改变到一个不同的组。每个应用都至少有一个线程从属于系统线程组。若创建多个线程而不…...

亿唐微方网站建设/怎样在百度上免费建网站

...

济南seo网站关键词优化排名/如何做百度关键词推广

求助各位大神&#xff0c;dell T630电脑进大白菜PE后识别不出来阵列&#xff0c;装驱动又不会装&#xff0c;现在进BIOS显示HARD raid PERC H330的阵列卡出感叹号了&#xff08;device path&#xff1a;pciroot&#xff08;0x0&#xff09;PCI(0X1A,0X0)/USB(0X0,0X0)/USB(0X4,…...

万网建站流程/郑州seo关键词自然排名工具

思想很简单,就是生成一个随机数,让用户输入数据和产生的随机数进行对比.如果相等即猜对,如果不等则让用户继续输入直到猜对为止. 随机数的产生可以使用rand()函数,如果想要产生一定范围内的随机数值需要rand()%? 再加上1 ,例如要产生一个1到100的随机数:rand()%1001.这样虽然…...

建设网站需要注意什么/网站优化关键词排名

上篇文章我们介绍了SpringBoot和MyBatis的整合&#xff0c;可以说非常简单快捷的就搭建了一个web项目&#xff0c;但是在一个真正的企业级项目中&#xff0c;可能我们还需要更多的更加完善的框架才能开始真正的开发&#xff0c;比如连接池、分页插件等。下面我们就来看看在Spri…...

网站的重要目录对百度进行了封禁/深圳搜狗seo

http://www.cnblogs.com/xiaoluo501395377/archive/2013/04/05/3001148.html...