PostgreSQL的学习心得和知识总结(一百四十六)|深入理解PostgreSQL数据库之客户端侧auto savepoint的使用和实现


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

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


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


深入理解PostgreSQL数据库之客户端侧auto savepoint的使用和实现

  • 文章快速说明索引
  • 功能使用背景说明
  • 功能实现源码解析
    • pgJDBC autosave属性
    • psql ON_ERROR_ROLLBACK



文章快速说明索引

学习目标:

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


学习内容:(详见目录)

1、深入理解PostgreSQL数据库之客户端侧auto savepoint的使用和实现


学习时间:

2024年06月20日 22:41:20


学习产出:

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=# select version();
                                                  version                                                   
------------------------------------------------------------------------------------------------------------
 PostgreSQL 17beta1 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=#
postgres=# create table t1 (id int primary key);
CREATE TABLE
postgres=# begin;
BEGIN
postgres=*# insert into t1 values(1);
INSERT 0 1
postgres=*# savepoint somename;
SAVEPOINT
postgres=*# insert into t1 values(1);
2024-06-20 07:49:46.746 PDT [180224] ERROR:  duplicate key value violates unique constraint "t1_pkey"
2024-06-20 07:49:46.746 PDT [180224] DETAIL:  Key (id)=(1) already exists.
2024-06-20 07:49:46.746 PDT [180224] STATEMENT:  insert into t1 values(1);
ERROR:  duplicate key value violates unique constraint "t1_pkey"
DETAIL:  Key (id)=(1) already exists.
postgres=!# rollback to somename;
ROLLBACK
postgres=*# commit;
COMMIT
postgres=# table t1;
 id 
----
  1
(1 row)

postgres=#

如上,非常便于理解,因为savepoint的存在 而不会导致整个事务的回滚!


继续看一个例子,示例二 如下:

postgres=# \d+ t1
                                           Table "public.t1"
 Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
 id     | integer |           | not null |         | plain   |             |              | 
Indexes:
    "t1_pkey" PRIMARY KEY, btree (id)
Access method: heap

postgres=# table t1;
 id 
----
  1
(1 row)

postgres=# \echo :ON_ERROR_ROLLBACK
off
postgres=# \set ON_ERROR_ROLLBACK on
postgres=# 
postgres=# begin ;
BEGIN
postgres=*# insert into t1 values(2);
INSERT 0 1
postgres=*# insert into t1 values(2);
2024-06-20 07:54:10.813 PDT [181022] ERROR:  duplicate key value violates unique constraint "t1_pkey"
2024-06-20 07:54:10.813 PDT [181022] DETAIL:  Key (id)=(2) already exists.
2024-06-20 07:54:10.813 PDT [181022] STATEMENT:  insert into t1 values(2);
ERROR:  duplicate key value violates unique constraint "t1_pkey"
DETAIL:  Key (id)=(2) already exists.
postgres=*# 
postgres=*# commit;
COMMIT
postgres=# table t1;
 id 
----
  1
  2
(2 rows)

postgres=#

这是psql变量ON_ERROR_ROLLBACK的使用!PostgreSQL官方文档解释,如下:

ON_ERROR_ROLLBACK:

  • 当被设置为on时,如果事务块中的一个语句产生一个错误,该错误会被忽略并且该事务会继续。
  • 当被设置为interactive时,只在交互式会话中忽略这类错误,而读取脚本文件时则不会忽略错误。
  • 当被重置或者设置为off(默认值)时,事务块中产生错误的一个语句会中止整个事务。
  • 错误回滚模式的工作原理是:在事务块的每个命令之前都为你发出一个隐式的SAVEPOINT,然后在该命令失败时回滚到该保存点。

类似的 pgJDBC 中也有一个类似的属性,如下:

  • pgJDBC官方文档,点击前往

在这里插入图片描述

  • autosave(字符串)默认 never
    指定查询失败时驱动程序应执行的操作。在 autosave=always 模式下,JDBC 驱动程序在每次查询前设置一个保存点,并在失败时回滚到该保存点。在 autosave=never 模式(默认)下,永远不会进行保存点切换。在 autosave=conservative 模式下,为每个查询设置保存点,但仅在极少数情况下才会回滚,例如cached statement cannot change return typestatement XXX is not valid,因此 JDBC 驱动程序会回滚并重试

  • cleanupSavepoints(布尔值)默认 false
    确定在自动保存模式下创建的 SAVEPOINT 是否在语句之前释放。这样做是为了避免在执行 1000 次查询的情况下耗尽服务器上的共享缓冲区。


功能实现源码解析

pgJDBC autosave属性

// pgjdbc\src\main\java\org\postgresql\core\v3\QueryExecutorImpl.java

  @Override
  public void execute(Query query, @Nullable ParameterList parameters,
      ResultHandler handler,
      int maxRows, int fetchSize, int flags, boolean adaptiveFetch) throws SQLException {
    try (ResourceLock ignore = lock.obtain()) {
      waitOnLock();
      if (LOGGER.isLoggable(Level.FINEST)) {
        LOGGER.log(Level.FINEST, "  simple execute, handler={0}, maxRows={1}, fetchSize={2}, flags={3}",
            new Object[]{handler, maxRows, fetchSize, flags});
      }

      if (parameters == null) {
        parameters = SimpleQuery.NO_PARAMETERS;
      }

      flags = updateQueryMode(flags);

      boolean describeOnly = (QUERY_DESCRIBE_ONLY & flags) != 0;

      ((V3ParameterList) parameters).convertFunctionOutParameters();

      // Check parameters are all set..
      if (!describeOnly) {
        ((V3ParameterList) parameters).checkAllParametersSet();
      }

      boolean autosave = false;
      try {
        try {
          handler = sendQueryPreamble(handler, flags);
          autosave = sendAutomaticSavepoint(query, flags);
          sendQuery(query, (V3ParameterList) parameters, maxRows, fetchSize, flags,
              handler, null, adaptiveFetch);
          if ((flags & QueryExecutor.QUERY_EXECUTE_AS_SIMPLE) != 0) {
            // Sync message is not required for 'Q' execution as 'Q' ends with ReadyForQuery message
            // on its own
          } else {
            sendSync();
          }
          processResults(handler, flags, adaptiveFetch);
          estimatedReceiveBufferBytes = 0;
        } catch (PGBindException se) {
          // There are three causes of this error, an
          // invalid total Bind message length, a
          // BinaryStream that cannot provide the amount
          // of data claimed by the length argument, and
          // a BinaryStream that throws an Exception
          // when reading.
          //
          // We simply do not send the Execute message
          // so we can just continue on as if nothing
          // has happened. Perhaps we need to
          // introduce an error here to force the
          // caller to rollback if there is a
          // transaction in progress?
          //
          sendSync();
          processResults(handler, flags, adaptiveFetch);
          estimatedReceiveBufferBytes = 0;
          handler
              .handleError(new PSQLException(GT.tr("Unable to bind parameter values for statement."),
                  PSQLState.INVALID_PARAMETER_VALUE, se.getIOException()));
        }
      } catch (IOException e) {
        abort();
        handler.handleError(
            new PSQLException(GT.tr("An I/O error occurred while sending to the backend."),
                PSQLState.CONNECTION_FAILURE, e));
      }

      try {
        handler.handleCompletion();
        if (cleanupSavePoints) {
          releaseSavePoint(autosave, flags);
        }
      } catch (SQLException e) {
        rollbackIfRequired(autosave, e);
      }
    }
  }
  @Override
  public void execute(Query[] queries, @Nullable ParameterList[] parameterLists,
      BatchResultHandler batchHandler, int maxRows, int fetchSize, int flags, boolean adaptiveFetch)
      throws SQLException {
    try (ResourceLock ignore = lock.obtain()) {
      waitOnLock();
      if (LOGGER.isLoggable(Level.FINEST)) {
        LOGGER.log(Level.FINEST, "  batch execute {0} queries, handler={1}, maxRows={2}, fetchSize={3}, flags={4}",
            new Object[]{queries.length, batchHandler, maxRows, fetchSize, flags});
      }

      flags = updateQueryMode(flags);

      boolean describeOnly = (QUERY_DESCRIBE_ONLY & flags) != 0;
      // Check parameters and resolve OIDs.
      if (!describeOnly) {
        for (ParameterList parameterList : parameterLists) {
          if (parameterList != null) {
            ((V3ParameterList) parameterList).checkAllParametersSet();
          }
        }
      }

      boolean autosave = false;
      ResultHandler handler = batchHandler;
      try {
        handler = sendQueryPreamble(batchHandler, flags);
        autosave = sendAutomaticSavepoint(queries[0], flags);
        estimatedReceiveBufferBytes = 0;

        for (int i = 0; i < queries.length; i++) {
          Query query = queries[i];
          V3ParameterList parameters = (V3ParameterList) parameterLists[i];
          if (parameters == null) {
            parameters = SimpleQuery.NO_PARAMETERS;
          }

          sendQuery(query, parameters, maxRows, fetchSize, flags, handler, batchHandler, adaptiveFetch);

          if (handler.getException() != null) {
            break;
          }
        }

        if (handler.getException() == null) {
          if ((flags & QueryExecutor.QUERY_EXECUTE_AS_SIMPLE) != 0) {
            // Sync message is not required for 'Q' execution as 'Q' ends with ReadyForQuery message
            // on its own
          } else {
            sendSync();
          }
          processResults(handler, flags, adaptiveFetch);
          estimatedReceiveBufferBytes = 0;
        }
      } catch (IOException e) {
        abort();
        handler.handleError(
            new PSQLException(GT.tr("An I/O error occurred while sending to the backend."),
                PSQLState.CONNECTION_FAILURE, e));
      }

      try {
        handler.handleCompletion();
        if (cleanupSavePoints) {
          releaseSavePoint(autosave, flags);
        }
      } catch (SQLException e) {
        rollbackIfRequired(autosave, e);
      }
    }
  }

我们这里对上面两个函数简化一下,如下:

  1. sendAutomaticSavepoint
  2. sendQuery(query)
  3. if(cleanupSavePoints) releaseSavePoint
  4. 异常 rollbackIfRequired

假设这里启用autosave,第一步,如下:

  private boolean sendAutomaticSavepoint(Query query, int flags) throws IOException {
    if (((flags & QueryExecutor.QUERY_SUPPRESS_BEGIN) == 0
        || getTransactionState() == TransactionState.OPEN)
        && query != restoreToAutoSave
        && !"COMMIT".equalsIgnoreCase(query.getNativeSql())
        && getAutoSave() != AutoSave.NEVER
        // If query has no resulting fields, it cannot fail with 'cached plan must not change result type'
        // thus no need to set a savepoint before such query
        && (getAutoSave() == AutoSave.ALWAYS
        // If CompositeQuery is observed, just assume it might fail and set the savepoint
        || !(query instanceof SimpleQuery)
        || ((SimpleQuery) query).getFields() != null)) {

      /*
      create a different SAVEPOINT the first time so that all subsequent SAVEPOINTS can be released
      easily. There have been reports of server resources running out if there are too many
      SAVEPOINTS.
       */
      sendOneQuery(autoSaveQuery, SimpleQuery.NO_PARAMETERS, 1, 0,
          QUERY_NO_RESULTS | QUERY_NO_METADATA
              // PostgreSQL does not support bind, exec, simple, sync message flow,
              // so we force autosavepoint to use simple if the main query is using simple
              | QUERY_EXECUTE_AS_SIMPLE);
      return true;
    }
    return false;
  }

如上autoSaveQuery是:

  private final SimpleQuery autoSaveQuery =
      new SimpleQuery(
          new NativeQuery("SAVEPOINT PGJDBC_AUTOSAVE", null, false, SqlCommand.BLANK),
          null, false);

第二步:就是执行query


第三步:如果这里cleanupSavepoints = true,那么如下:

  private void releaseSavePoint(boolean autosave, int flags) throws SQLException {
    if ( autosave
        && getAutoSave() == AutoSave.ALWAYS
        && getTransactionState() == TransactionState.OPEN) {
      try {
        sendOneQuery(releaseAutoSave, SimpleQuery.NO_PARAMETERS, 1, 0,
            QUERY_NO_RESULTS | QUERY_NO_METADATA
                | QUERY_EXECUTE_AS_SIMPLE);

      } catch (IOException ex) {
        throw  new PSQLException(GT.tr("Error releasing savepoint"), PSQLState.IO_ERROR);
      }
    }
  }

如上releaseAutoSave是:

  private final SimpleQuery releaseAutoSave =
      new SimpleQuery(
          new NativeQuery("RELEASE SAVEPOINT PGJDBC_AUTOSAVE", null, false, SqlCommand.BLANK),
          null, false);

第四步:这里通常就是上面query执行失败的情况,如下:

  private void rollbackIfRequired(boolean autosave, SQLException e) throws SQLException {
    if (autosave
        && getTransactionState() == TransactionState.FAILED
        && (getAutoSave() == AutoSave.ALWAYS || willHealOnRetry(e))) {
      try {
        // ROLLBACK and AUTOSAVE are executed as simple always to overcome "statement no longer exists S_xx"
        execute(restoreToAutoSave, SimpleQuery.NO_PARAMETERS, new ResultHandlerDelegate(null),
            1, 0, QUERY_NO_RESULTS | QUERY_NO_METADATA | QUERY_EXECUTE_AS_SIMPLE);
      } catch (SQLException e2) {
        // That's O(N), sorry
        e.setNextException(e2);
      }
    }
    throw e;
  }

如上restoreToAutoSave是:

  /*
  In autosave mode we use this query to roll back errored transactions
   */
  private final SimpleQuery restoreToAutoSave =
      new SimpleQuery(
          new NativeQuery("ROLLBACK TO SAVEPOINT PGJDBC_AUTOSAVE", null, false, SqlCommand.BLANK),
          null, false);
}

看到这里,其整个流程上 内部实现和上面的示例一 一模一样!


psql ON_ERROR_ROLLBACK

我们这里直接调试一下 示例二,如下:

在这里插入图片描述

然后开始执行这个query,如下:

在这里插入图片描述


然后看一下接下来的逻辑处理,如下:

// src/bin/psql/common.c

...
	/* If we made a temporary savepoint, possibly release/rollback */
	if (on_error_rollback_savepoint)
	{
		const char *svptcmd = NULL;

		transaction_status = PQtransactionStatus(pset.db);

		switch (transaction_status)
		{
			case PQTRANS_INERROR:
				/* We always rollback on an error */
				svptcmd = "ROLLBACK TO pg_psql_temporary_savepoint";
				break;

			case PQTRANS_IDLE:
				/* If they are no longer in a transaction, then do nothing */
				break;

			case PQTRANS_INTRANS:

				/*
				 * Release our savepoint, but do nothing if they are messing
				 * with savepoints themselves
				 */
				if (!svpt_gone)
					svptcmd = "RELEASE pg_psql_temporary_savepoint";
				break;

			case PQTRANS_ACTIVE:
			case PQTRANS_UNKNOWN:
			default:
				OK = false;
				/* PQTRANS_UNKNOWN is expected given a broken connection. */
				if (transaction_status != PQTRANS_UNKNOWN || ConnectionUp())
					pg_log_error("unexpected transaction status (%d)",
								 transaction_status);
				break;
		}

		if (svptcmd)
		{
			PGresult   *svptres;

			svptres = PQexec(pset.db, svptcmd);
			if (PQresultStatus(svptres) != PGRES_COMMAND_OK)
			{
				pg_log_info("%s", PQerrorMessage(pset.db));
				ClearOrSaveResult(svptres);
				OK = false;

				goto sendquery_cleanup;
			}
			PQclear(svptres);
		}
	}

...

在这里插入图片描述

上面是失败的情形,下面来看一个成功的例子!


在这里插入图片描述

自然这里最终就是成功的,如下:

postgres=# table t1;
 id 
----
  1
  2
(2 rows)

postgres=# begin ;
BEGIN
postgres=*# insert into t1 values(3);
INSERT 0 1
postgres=*# commit;
COMMIT
postgres=# table t1;
 id 
----
  1
  2
  3
(3 rows)

postgres=#

注:无论是驱动pgJDBC还是连接工具psql,如上的 auto savepoint的行为都可以理解为 client 一侧的行为。那么我们是否可以在 server 一端实现类似的功能呢?

这个问题 本人后面另开一篇新的博客进行探讨!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络:应用层 - 文件传输协议 FTP 电子邮件

计算机网络&#xff1a;应用层 - 文件传输协议 FTP & 电子邮件 文件传输协议 FTP电子邮件 文件传输协议 FTP 文件传送协议 FTP(File Transfer Protocol)&#xff0c;曾是互联网祝频讲解上使用得最广泛的文件传送协议。 其特点是&#xff1a;若要存取一个文件&#xff0c;…

前端基础操作1——利用nvm任意切换(管理)node版本

在实际前端项目开发过程中&#xff0c;同时开发多个项目或者切换新项目时&#xff0c;因为node版本问题造成项目无法运行的问题比比皆是&#xff0c;这时候通过nvm管理切换不同版本的node&#xff0c;就能很快进入开发模式&#xff0c;避免因为环境问题浪费大量精力&#xff0c…

hive on spark 的架构和常见问题 - hive on spark 使用的是 yarn client 模式还是 yarn cluster 模式?

hive on spark 的架构和常见问题 - hive on spark 使用的是 yarn client 模式还是 yarn cluster 模式&#xff1f; 1. 回顾下 spark 的架构图和部署模式 来自官方的经典的 spark 架构图如下&#xff1a; 上述架构图&#xff0c;从进程的角度来讲&#xff0c;有四个角色/组件&…

[C++][数据结构][B-树][上]详细讲解

目录 0.常见的搜索结构1.B树概念2.B-树的插入分析1.流程分析2.插入过程总结 0.常见的搜索结构 种类数据格式时间复杂度顺序查找无要求 O ( N ) O(N) O(N)二分查找有序 O ( l o g 2 N ) O(log_2 N) O(log2​N)二叉搜索树无要求 O ( N ) O(N) O(N)二叉平衡树无要求 O ( l o g 2 …

20212416 2023-2024-2 《移动平台开发与实践》综合实践

移动平台开放综合实践 1.实验内容2.实验过程2.1 确定基础功能2.2 设计UI界面2.3 编写程序运行代码2.4 在基本功能的基础上丰富功能 3. 代码分析3.1设置按钮的点击事件监听器3.2 比分更新模块3.3 比分存储模块 4. 运行结果5.实践中遇到的问题及解决6.学习感悟与思考参考资料 1.实…

k8s中 docker和containerd 镜像相互导入导出

containerd镜像导出并导入docker 1 查看containerd 本地镜像列表 crictl images 2 containerd 导出本地镜像到当前目录下&#xff08;注意&#xff1a; 导出导入需要指定镜像平台类型 --platform&#xff09; ctr -n k8s.io images export nacos-server-24-06-30-13-02-…

【windows|007】DHCP服务详解

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 ​ &#x1f3c5;阿里云ACE认证高级工程师 ​ &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社…

在阿里云服务器Linux系统上从头到尾实现Webapp的部署(安装卸载JDK、安装Tomcat、安装配置MySQL)

输入yum list | grep jdk 选择 devel是软件包中的典型命名格式 devel表示这个包是开发工具相关的 里面包含内容是最完整的 x86表示cpu架构是x86_64 还有openjdk表示开源版本 输入yum install java-1.8.0-openjdk-devel.x86_64 开始下载 遇到问你 is this ok? 输入y表示ok 输…

计算机网络期末复习——简明扼要介绍考点及相关知识

期末复习的方法论&#xff1a;一般来说&#xff0c;期末复习都是“理论”结合“实践”。 理论&#xff0c;在于要对期末考点有基本的把握。可以询问老师或者师兄&#xff0c;总之要知道考试的重点在哪里。对照教材&#xff0c;勾画考试重点&#xff0c;删去不重要的琐碎知识点。…

【机器学习】深度学习赋能:基于 LSTM 的智能日志异常检测

目录 1. LSTM 简介 2. 日志序列异常检测概述 3. 数据预处理 3.1 日志解析 3.2 数据清洗 3.3 序列化 3.4 特征提取 示例代码 4. 构建 LSTM 模型 4.1 模型结构 4.2 模型构建示例 5. 训练 LSTM 模型 5.1 数据准备 5.2 模型训练 示例代码 6. 异常检测 6.1 异常分数…

QT截图程序三-截取自定义多边形

上一篇文章QT截图程序&#xff0c;可多屏幕截图二&#xff0c;增加调整截图区域功能-CSDN博客描述了如何截取&#xff0c;具备调整边缘功能后已经方便使用了&#xff0c;但是与系统自带的程序相比&#xff0c;似乎没有什么特别&#xff0c;只能截取矩形区域。 如果可以按照自己…

中欧科学家论坛暨第六届人工智能与先进制造国际会议(AIAM2024)

会议日期&#xff1a;2024年10月20-21日 会议地点&#xff1a;德国-法兰克福 会议官网&#xff1a;https://www.iaast.cn/meet/home/Bx130JiM 出版检索&#xff1a;EI、Scopus等数据库收录 【会议简介】 “中欧科学家论坛”由德国、法国、荷兰、瑞士、丹麦、意大利、西班牙…

python爬虫之selenium自动化操作

python爬虫之selenium自动化操作 需求&#xff1a;操作淘宝去掉弹窗广告搜索物品后进入百度回退又前进 selenium模块的基本使用 问题&#xff1a;selenium模块和爬虫之间具有怎样的关联? 1、便捷的获取网站中动态加载的数据 2、便捷实现模拟登录 什么是selenium模块&#x…

java连接kerberos用户认证

文章目录 一、背景二、代码2.1目录2.2配置文件application.properties2.3pom依赖2.4代码AuthProviderConfig配置类CustomConfigurationByKeytab配置类CustomConfigurationByPassword配置类TestControllerMyCallbackHandlerDummyUserDetailsService实现类LdapTest2Application启…

数据结构经典面试之数组——C#和C++篇

文章目录 1. 数组的基本概念与功能2. C#数组创建数组访问数组元素修改数组元素数组排序 3. C数组创建数组访问数组元素修改数组元素数组排序 4. 数组的实际应用与性能优化5. C#数组示例6. C数组示例总结 数组是编程中常用的数据结构之一&#xff0c;它用于存储一系列相同类型的…

算法训练营day15--110.平衡二叉树+ 257. 二叉树的所有路径+ 404.左叶子之和+222.完全二叉树的节点个数

一、110.平衡二叉树 题目链接&#xff1a;https://leetcode.cn/problems/balanced-binary-tree/ 文章讲解&#xff1a;https://programmercarl.com/0110.%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.html 视频讲解&#xff1a;https://www.bilibili.com/video/BV1Ug411S7m…

React中的JSX应该怎么用

什么是JSX JSX Javascript XML&#xff0c;JSX是一个 JavaScript 的语法扩展。 JSX可以很好地描述 UI 应该呈现出它应有交互的本质形式并且其完全可以和JavaScript融合在一起使用。而且具有 JavaScript 的全部功能。JSX 可以生成 React “元素”。 JSX代码示例&#xff1a; …

编译原理:语法分析(语法制导翻译)、语义分析(类型检查、中间代码生成)

编译器在做语法分析的过程中&#xff0c;除了回答程序代码的语法是否合法&#xff08;LL,LR能否接收&#xff09;外&#xff0c;还需要完成后续的工作&#xff08;包括构建语法树、类型检查、中间代码生成、目标代码生成&#xff09;&#xff0c;这些后续工作一般都可以通过语法…

板凳--------第60章 SOCKET:服务器设计

60.1 迭代型和并发型服务器 1016 1.迭代型&#xff1a; 服务器每次只处理一个客户端&#xff0c;只有当完全处理完一个客户端的请求后才会去处理下一个客户端。只适用于快速处理客户端请求的场景&#xff0c;因为每个客户端都必须等待&#xff0c;直到前面所有的客户端都处理完…

10年265倍!动态展示全球第一股英伟达10年股价走势

英伟达在过去十年的股价走势展示了其在市场上的强劲表现和显著增长。自1999年上市以来&#xff0c;英伟达的股价经历了多次显著的涨幅&#xff0c;并在2024年达到了历史新高。 从2023年6月的数据来看&#xff0c;英伟达的股价为386.54美元/股&#xff0c;市值为9548亿美元。然…