• PostgreSQL的学习心得和知识总结(一百三十九)|深入理解PostgreSQL数据库GUC参数 allow_alter_system 的使用和原理



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

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


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


    深入理解PostgreSQL数据库GUC参数 allow_alter_system 的使用和原理



    文章快速说明索引

    学习目标:

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


    学习内容:(详见目录)

    1、深入理解PostgreSQL数据库GUC参数 allow_alter_system 的使用和原理


    学习时间:

    2024年04月21日 14:44:16


    学习产出:

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    参数使用背景说明

    背景

    2024/03/29日,PostgreSQL新增一个GUC参数,如下:

    添加allow_alter_system GUC


    这个被标记为PGC_SIGHUP,所以只能在配置文件中设置,不能在其他地方设置;并且它还被标记为GUC_DISALLOW_IN_AUTO_FILE,因此不能使用ALTER SYSTEM进行设置。当设置为 false 时,不允许使用 ALTER SYSTEM 命令


    人们非常担心这会被误解为安全功能,但事实并非如此,因为坚定的超级用户可以通过多种方式绕过它。因此,我们在文档的措辞上做了很多工作,希望避免任何此类混乱


    官方文档的解释,如下:

    当allow_alter_system设置为off时,执行ALTER SYSTEM命令会返回错误。该参数只能在 postgresql.conf 文件或服务器命令行中设置。默认值是打开的

    请注意,此设置不得视为安全功能。它仅禁用 ALTER SYSTEM 命令。它不会阻止超级用户使用其他 SQL 命令更改配置。超级用户有多种在操作系统级别执行 shell 命令的方法,因此可以修改 postgresql.auto.conf,无论此设置的值如何

    关闭此设置适用于 PostgreSQL 配置由某些外部工具管理的环境。在这种环境中,善意的超级用户可能会错误地使用 ALTER SYSTEM 来更改配置,而不是使用外部工具。这可能会导致意外行为,例如外部工具在稍后更新配置时覆盖更改。将此参数设置为关闭可以帮助避免此类错误

    该参数仅控制 ALTER SYSTEM 的使用。即使allow_alter_system设置为off,存储在postgresql.auto.conf中的设置也会生效


    使用

    接下来看一个简单的使用,示例1如下:

    [postgres@localhost:~/test/bin]$ ./psql 
    psql (17devel)
    Type "help" for help.
    
    postgres=# show timezone;
          TimeZone       
    ---------------------
     America/Los_Angeles
    (1 row)
    
    postgres=# set timezone = 'PRC';
    SET
    postgres=# show timezone;
     TimeZone 
    ----------
     PRC
    (1 row)
    
    postgres=# select now();
                  now              
    -------------------------------
     2024-04-18 15:35:49.411729+08
    (1 row)
    
    postgres=# reset timezone;
    RESET
    postgres=# show timezone;
          TimeZone       
    ---------------------
     America/Los_Angeles
    (1 row)
    
    postgres=# select now();
                  now              
    -------------------------------
     2024-04-18 00:36:06.505543-07
    (1 row)
    
    postgres=# alter system set timezone = 'PRC';
    2024-04-18 00:36:23.026 PDT [30697] ERROR:  ALTER SYSTEM is not allowed in this environment
    2024-04-18 00:36:23.026 PDT [30697] STATEMENT:  alter system set timezone = 'PRC';
    ERROR:  ALTER SYSTEM is not allowed in this environment
    postgres=# 
    postgres=# show timezone;
          TimeZone       
    ---------------------
     America/Los_Angeles
    (1 row)
    
    postgres=# show allow_alter_system;
     allow_alter_system 
    --------------------
     off
    (1 row)
    
    postgres=# alter system set allow_alter_system = on;
    2024-04-18 00:40:03.694 PDT [30697] ERROR:  ALTER SYSTEM is not allowed in this environment
    2024-04-18 00:40:03.694 PDT [30697] STATEMENT:  alter system set allow_alter_system = on;
    ERROR:  ALTER SYSTEM is not allowed in this environment
    postgres=#
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    [postgres@localhost:~/test/bin]$ cat test/postgresql.conf | grep allow_alter_system
    allow_alter_system = off
    [postgres@localhost:~/test/bin]$
    
    • 1
    • 2
    • 3

    示例2,如下:

    [postgres@localhost:~/test/bin]$ cat test/postgresql.conf | grep allow_alter_system
    allow_alter_system = off
    [postgres@localhost:~/test/bin]$ 
    [postgres@localhost:~/test/bin]$ vim test/postgresql.auto.conf 
    [postgres@localhost:~/test/bin]$ 
    [postgres@localhost:~/test/bin]$ cat test/postgresql.auto.conf 
    # Do not edit this file manually!
    # It will be overwritten by the ALTER SYSTEM command.
    allow_alter_system = on
    [postgres@localhost:~/test/bin]$ 
    [postgres@localhost:~/test/bin]$ ./pg_ctl restart -D test/
    waiting for server to shut down....2024-04-24 23:05:34.217 PDT [30683] LOG:  received fast shutdown request
    2024-04-24 23:05:34.219 PDT [30683] LOG:  aborting any active transactions
    2024-04-24 23:05:34.220 PDT [30683] LOG:  background worker "logical replication launcher" (PID 30689) exited with exit code 1
    2024-04-24 23:05:34.221 PDT [30684] LOG:  shutting down
    2024-04-24 23:05:34.240 PDT [30683] LOG:  database system is shut down
     done
    server stopped
    waiting for server to start....2024-04-24 23:05:34.361 PDT [50234] LOG:  starting PostgreSQL 17devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-21), 64-bit
    2024-04-24 23:05:34.361 PDT [50234] LOG:  listening on IPv6 address "::1", port 5432
    2024-04-24 23:05:34.361 PDT [50234] LOG:  listening on IPv4 address "127.0.0.1", port 5432
    2024-04-24 23:05:34.363 PDT [50234] LOG:  listening on Unix socket "/tmp/.s.PGSQL.5432"
    2024-04-24 23:05:34.366 PDT [50237] LOG:  database system was shut down at 2024-04-24 23:05:34 PDT
    2024-04-24 23:05:34.370 PDT [50234] LOG:  database system is ready to accept connections
     done
    server started
    [postgres@localhost:~/test/bin]$ ./psql 
    psql (17devel)
    Type "help" for help.
    
    postgres=# show allow_alter_system;
     allow_alter_system 
    --------------------
     on
    (1 row)
    
    postgres=# show timezone;
          TimeZone       
    ---------------------
     America/Los_Angeles
    (1 row)
    
    postgres=# alter system set timezone = 'PRC';
    ALTER SYSTEM
    postgres=# alter system set allow_alter_system = off;
    2024-04-24 23:06:26.063 PDT [50248] ERROR:  parameter "allow_alter_system" cannot be changed
    2024-04-24 23:06:26.063 PDT [50248] STATEMENT:  alter system set allow_alter_system = off;
    ERROR:  parameter "allow_alter_system" cannot be changed
    postgres=#
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    参数实现源码解析

    guc定义,如下:

    // src\backend\utils\misc\guc_tables.c
    
    	{
    		/*
    		 * This setting itself cannot be set by ALTER SYSTEM to avoid an
    		 * operator turning this setting off by using ALTER SYSTEM, without a
    		 * way to turn it back on.
    		 *  
    		 * 此设置本身无法通过 ALTER SYSTEM 设置,以避免操作员使用 ALTER SYSTEM 关闭此设置
    		 * 而无法将其重新打开
    		 */
    		{"allow_alter_system", PGC_SIGHUP, COMPAT_OPTIONS_OTHER,
    			gettext_noop("Allows running the ALTER SYSTEM command."),
    			gettext_noop("Can be set to off for environments where global configuration "
    						 "changes should be made using a different method."),
    			GUC_DISALLOW_IN_AUTO_FILE
    		},
    		&AllowAlterSystem,
    		true,
    		NULL, NULL, NULL
    	},
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    该参数真实使用的逻辑,如下:

    // src\backend\utils\misc\guc.c
    
    /*
     * Execute ALTER SYSTEM statement.
     *
     * Read the old PG_AUTOCONF_FILENAME file, merge in the new variable value,
     * and write out an updated file.  If the command is ALTER SYSTEM RESET ALL,
     * we can skip reading the old file and just write an empty file.
     * 读取旧的 PG_AUTOCONF_FILENAME 文件,合并新的变量值,并写出更新的文件
     * 如果命令是 ALTER SYSTEM RESET ALL,我们可以跳过读取旧文件,只写入一个空文件
     *
     * An LWLock is used to serialize updates of the configuration file.
     * LWLock 用于序列化配置文件的更新
     *
     * In case of an error, we leave the original automatic
     * configuration file (PG_AUTOCONF_FILENAME) intact.
     * 如果出现错误,我们会保留原始自动配置文件(PG_AUTOCONF_FILENAME)不变
     */
    void
    AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
    {
    	char	   *name;
    	char	   *value;
    	bool		resetall = false;
    	ConfigVariable *head = NULL;
    	ConfigVariable *tail = NULL;
    	volatile int Tmpfd;
    	char		AutoConfFileName[MAXPGPATH];
    	char		AutoConfTmpFileName[MAXPGPATH];
    
    	/*
    	 * Extract statement arguments
    	 */
    	name = altersysstmt->setstmt->name;
    
    	if (!AllowAlterSystem)
    		ereport(ERROR,
    				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    				 errmsg("ALTER SYSTEM is not allowed in this environment")));
    
    	switch (altersysstmt->setstmt->kind)
    	{
    		case VAR_SET_VALUE:
    			value = ExtractSetVariableArgs(altersysstmt->setstmt);
    			break;
    
    		case VAR_SET_DEFAULT:
    		case VAR_RESET:
    			value = NULL;
    			break;
    
    		case VAR_RESET_ALL:
    			value = NULL;
    			resetall = true;
    			break;
    
    		default:
    			elog(ERROR, "unrecognized alter system stmt type: %d",
    				 altersysstmt->setstmt->kind);
    			break;
    	}
    
    	/*
    	 * Check permission to run ALTER SYSTEM on the target variable
    	 */
    	if (!superuser())
    	{
    		if (resetall)
    			ereport(ERROR,
    					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
    					 errmsg("permission denied to perform ALTER SYSTEM RESET ALL")));
    		else
    		{
    			AclResult	aclresult;
    
    			aclresult = pg_parameter_aclcheck(name, GetUserId(),
    											  ACL_ALTER_SYSTEM);
    			if (aclresult != ACLCHECK_OK)
    				ereport(ERROR,
    						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
    						 errmsg("permission denied to set parameter \"%s\"",
    								name)));
    		}
    	}
    
    	/*
    	 * Unless it's RESET_ALL, validate the target variable and value
    	 */
    	if (!resetall)
    	{
    		struct config_generic *record;
    
    		/* We don't want to create a placeholder if there's not one already */
    		record = find_option(name, false, true, DEBUG5);
    		if (record != NULL)
    		{
    			/*
    			 * Don't allow parameters that can't be set in configuration files
    			 * to be set in PG_AUTOCONF_FILENAME file.
    			 */
    			if ((record->context == PGC_INTERNAL) ||
    				(record->flags & GUC_DISALLOW_IN_FILE) ||
    				(record->flags & GUC_DISALLOW_IN_AUTO_FILE))
    				ereport(ERROR,
    						(errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
    						 errmsg("parameter \"%s\" cannot be changed",
    								name)));
    
    			/*
    			 * If a value is specified, verify that it's sane.
    			 */
    			if (value)
    			{
    				union config_var_val newval;
    				void	   *newextra = NULL;
    
    				if (!parse_and_validate_value(record, name, value,
    											  PGC_S_FILE, ERROR,
    											  &newval, &newextra))
    					ereport(ERROR,
    							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
    							 errmsg("invalid value for parameter \"%s\": \"%s\"",
    									name, value)));
    
    				if (record->vartype == PGC_STRING && newval.stringval != NULL)
    					guc_free(newval.stringval);
    				guc_free(newextra);
    			}
    		}
    		else
    		{
    			/*
    			 * Variable not known; check we'd be allowed to create it.  (We
    			 * cannot validate the value, but that's fine.  A non-core GUC in
    			 * the config file cannot cause postmaster start to fail, so we
    			 * don't have to be too tense about possibly installing a bad
    			 * value.)
    			 */
    			(void) assignable_custom_variable_name(name, false, ERROR);
    		}
    
    		/*
    		 * We must also reject values containing newlines, because the grammar
    		 * for config files doesn't support embedded newlines in string
    		 * literals.
    		 */
    		if (value && strchr(value, '\n'))
    			ereport(ERROR,
    					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
    					 errmsg("parameter value for ALTER SYSTEM must not contain a newline")));
    	}
    
    	/*
    	 * PG_AUTOCONF_FILENAME and its corresponding temporary file are always in
    	 * the data directory, so we can reference them by simple relative paths.
    	 */
    	snprintf(AutoConfFileName, sizeof(AutoConfFileName), "%s",
    			 PG_AUTOCONF_FILENAME);
    	snprintf(AutoConfTmpFileName, sizeof(AutoConfTmpFileName), "%s.%s",
    			 AutoConfFileName,
    			 "tmp");
    
    	/*
    	 * Only one backend is allowed to operate on PG_AUTOCONF_FILENAME at a
    	 * time.  Use AutoFileLock to ensure that.  We must hold the lock while
    	 * reading the old file contents.
    	 */
    	LWLockAcquire(AutoFileLock, LW_EXCLUSIVE);
    
    	/*
    	 * If we're going to reset everything, then no need to open or parse the
    	 * old file.  We'll just write out an empty list.
    	 */
    	if (!resetall)
    	{
    		struct stat st;
    
    		if (stat(AutoConfFileName, &st) == 0)
    		{
    			/* open old file PG_AUTOCONF_FILENAME */
    			FILE	   *infile;
    
    			infile = AllocateFile(AutoConfFileName, "r");
    			if (infile == NULL)
    				ereport(ERROR,
    						(errcode_for_file_access(),
    						 errmsg("could not open file \"%s\": %m",
    								AutoConfFileName)));
    
    			/* parse it */
    			if (!ParseConfigFp(infile, AutoConfFileName, CONF_FILE_START_DEPTH,
    							   LOG, &head, &tail))
    				ereport(ERROR,
    						(errcode(ERRCODE_CONFIG_FILE_ERROR),
    						 errmsg("could not parse contents of file \"%s\"",
    								AutoConfFileName)));
    
    			FreeFile(infile);
    		}
    
    		/*
    		 * Now, replace any existing entry with the new value, or add it if
    		 * not present.
    		 */
    		replace_auto_config_value(&head, &tail, name, value);
    	}
    
    	/*
    	 * Invoke the post-alter hook for setting this GUC variable.  GUCs
    	 * typically do not have corresponding entries in pg_parameter_acl, so we
    	 * call the hook using the name rather than a potentially-non-existent
    	 * OID.  Nonetheless, we pass ParameterAclRelationId so that this call
    	 * context can be distinguished from others.  (Note that "name" will be
    	 * NULL in the RESET ALL case.)
    	 *
    	 * We do this here rather than at the end, because ALTER SYSTEM is not
    	 * transactional.  If the hook aborts our transaction, it will be cleaner
    	 * to do so before we touch any files.
    	 */
    	InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, name,
    									ACL_ALTER_SYSTEM,
    									altersysstmt->setstmt->kind,
    									false);
    
    	/*
    	 * To ensure crash safety, first write the new file data to a temp file,
    	 * then atomically rename it into place.
    	 *
    	 * If there is a temp file left over due to a previous crash, it's okay to
    	 * truncate and reuse it.
    	 */
    	Tmpfd = BasicOpenFile(AutoConfTmpFileName,
    						  O_CREAT | O_RDWR | O_TRUNC);
    	if (Tmpfd < 0)
    		ereport(ERROR,
    				(errcode_for_file_access(),
    				 errmsg("could not open file \"%s\": %m",
    						AutoConfTmpFileName)));
    
    	/*
    	 * Use a TRY block to clean up the file if we fail.  Since we need a TRY
    	 * block anyway, OK to use BasicOpenFile rather than OpenTransientFile.
    	 */
    	PG_TRY();
    	{
    		/* Write and sync the new contents to the temporary file */
    		write_auto_conf_file(Tmpfd, AutoConfTmpFileName, head);
    
    		/* Close before renaming; may be required on some platforms */
    		close(Tmpfd);
    		Tmpfd = -1;
    
    		/*
    		 * As the rename is atomic operation, if any problem occurs after this
    		 * at worst it can lose the parameters set by last ALTER SYSTEM
    		 * command.
    		 */
    		durable_rename(AutoConfTmpFileName, AutoConfFileName, ERROR);
    	}
    	PG_CATCH();
    	{
    		/* Close file first, else unlink might fail on some platforms */
    		if (Tmpfd >= 0)
    			close(Tmpfd);
    
    		/* Unlink, but ignore any error */
    		(void) unlink(AutoConfTmpFileName);
    
    		PG_RE_THROW();
    	}
    	PG_END_TRY();
    
    	FreeConfigVariables(head);
    
    	LWLockRelease(AutoFileLock);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
  • 相关阅读:
    DN-DETR(CVPR 2022)
    安卓开发之okHttp请求封装
    微信小程序日历插件用法-举例为(爸妈搜日历)
    奥运奖牌查询易语言代码
    设计原则学习
    【LLM】金融大模型场景和大模型Lora微调实战
    Vue常见问题
    酷雷曼第二期无人机技能培训圆满举办
    计算机、互联网基础系列-1-计算机基础、计算机网络基础知识汇总
    VFP发送XML与MSSQL的互操作, 解决一个传大表查询的大大大问题
  • 原文地址:https://blog.csdn.net/weixin_43949535/article/details/138125430