【PGCCC】Postgres MVCC 内部:更新与插入的隐性成本
为什么 Postgres 中的更新操作有时感觉比插入操作慢?答案在于 Postgres 如何在后台管理数据版本。
Postgres 高效处理并发事务能力的核心是多版本并发控制(MVCC)。
在本文中,我将探讨 MVCC 在 Postgres 中的工作原理以及它如何影响写入和读取,以及插入和更新之间的性能差异。
-- pageinspect 扩展用于查看底层数据存储,我们很快就会用到它!
CREATE EXTENSION IF NOT EXISTS pageinspect;
-- 创建表存储
CREATE TABLE store ( id SERIAL PRIMARY KEY, name TEXT NOT NULL ,value INT NOT NULL );
-- 禁用表的自动清理
ALTER TABLE store SET (autovacuum_enabled = false );
设置过程包括创建一个名为“store”的示例表并禁用自动清理。这将使我们能够观察 Postgres 如何在没有自动清理的情况下处理版本控制(MVCC 的实际操作),从而帮助我们更好地理解 MVCC 的工作原理。
MVCC概述
为了理解为什么更新行为与插入不同,我们首先看看 MVCC 在 Postgres 中的工作方式。
多版本并发控制 (MVCC) 是 Postgres 用于处理同一行的并发事务同时保持数据一致性和隔离性的机制。
Postgres 不会在读取和写入期间锁定行,而是创建一行的多个版本,以允许事务在不相互阻塞的情况下运行。
一行的多个版本被称为元组 (Tuples)。如果插入一行,则有 1 个元组与之关联。如果更新同一行,则存在 2 个元组,其中一个是活的,另一个是死的。
工作原理如下:
写入操作
每个插入或更新查询都在一个事务中执行,每个事务都会被分配一个唯一的事务 ID。虽然该过程还涉及写入预写日志 (WAL) 和设置检查点,但本文不会介绍这些细节。
您可以通过以下方式检查postgres中的当前事务ID:
postgres = # SELECT txid_current();
txid_current
--------------
753
( 1 行)postgres = # SELECT txid_current();
txid_current
--------------
754 ( 1 行)
Postgres 中的每一行都包含与事务 ID 绑定的版本信息,用于跟踪该行随时间的变化状态。此版本控制通过 xmin 和 xmax 值进行管理,这些值可以直接查询。
postgres = #插入 store (name, value )值( 'score' , 10 );插入 0 1 postgres = #从store中选择xmin, xmax, * 其中id = 1 ; xmin | xmax | id | name | value ------+------+----+-------+------- 755 | 0 | 1 | score | 10 ( 1行)
- min表示插入该行(即创建初始元组)的事务的 ID。
- xmax表示删除或使该行失效的事务的 ID。由于这里的 xmax 为 0,因此表示该行处于活动状态,未被删除或失效。
Read Operation这些值也会影响行可见性,我们将在下面的文章中更详细地探讨。
让我们看一下数据库中存储的实际元组,看看 MVCC 内部是如何管理行版本的。我们可以使用 pageinspect 扩展直接检查元组,这使我们能够查看表底层页面的原始内容。
Postgres 以页的形式将数据存储在磁盘上,每个页包含多个元组。理想情况下,我们感兴趣的元组应该位于第一页(即第 0 页)。让我们使用 pageinspect 来查询它,看看存储了什么:
postgres = #从heap_page_items(get_raw_page( 'store' , 0 ))中选择lp、t_ctid、t_xmin、t_xmax ; lp | t_ctid | t_xmin | t_xmax ----+--------+--------+-------- 1 | ( 0 , 1 ) | 755 | 0 ( 1行)
我们可以看到 xmin 和 xmax 值与表上的常规 SELECT 查询的值匹配。
- lp– 这是行指针,充当页面内的索引,指向元组的实际位置。由于lp = 1,这意味着这是页面中第一个且唯一的元组。
- t_ctid– 这是元组的 ID,以 (page_number, tuple_number)格式指向元组的物理位置。如果该行被更新,则会创建一个新版本,并且 ctid 将指向新的位置——稍后会详细介绍。
- xmin–表示插入该行的事务 ID(在本例中为事务 755)。
- xmax– 表示删除或使该行失效的事务 ID。由于 xmax = 0,因此该行仍然有效。 Postgres 中的元组由系统列(例如 xmin、xmax、ctid 等)和实际行数据组成。系统列帮助 Postgres 管理行版本和可见性,稍后我们将更详细地探讨这些内容。
当发生写入操作(例如更新)时,现有行不会被直接修改。相反,Postgres 会创建一个包含更新值的新行版本,并保持旧行不变。然后,旧元组会被标记为“已死”,但仍可用于 MVCC 用途(例如支持并发读取)。
让我们更新行并检查更改:
postgres = # UPDATE store set value = 20 where id = 1 ;
UPDATE 1 postgres = # SELECT xmin, xmax, * FROM store WHERE id = 1 ;
xmin | xmax | id | name | value
------+------+----+-------+-------
756 | 0 | 1 | score | 20
( 1 行)
该行现在有了一个新的 xmin 值 (756),它代表创建此新元组的事务 ID。由于 MVCC 机制,我们预期原始行(现在是一个死元组)仍然与新行并存。
我们可以通过使用视图检查活元组和死元组的数量来确认这一点pg_stat_all_tables:
postgres = #从pg_stat_all_tables中选择n_live_tup、n_dead_tup、relname ,其中relname = 'store';n_live_tup | n_dead_tup | relname ------------+------------+--------- 1 | 1 | store (1行)
正如预期的那样,有 1 个活动元组(更新后的行)和 1 个死亡元组(原始行)。现在让我们检查底层页面以查看这两个元组:
让我们查询页面来再次查看元组。
postgres = #从heap_page_items(get_raw_page( 'store' , 0 ))中选择lp、t_ctid、t_xmin、t_xmax ; lp | t_ctid | t_xmin | t_xmax ----+--------+--------+-------- 1 | ( 0 , 2 ) | 755 | 756 2 | ( 0 , 2 ) | 756 | 0 ( 2行)
我们现在看到了预期的两个元组:
- 第一个元组有 xmin = 755(来自原始插入)和 xmax = 756,表明它被事务 756(更新)无效。
- 第二个元组是更新创建的新版本,其中 xmin = 756 和 xmax = 0(表示它仍然有效)。
- 请注意,t_ctid原始元组的 已从更改为(0,1)。(0,2)这是因为t_ctid指向该行的最新版本。该格式(page_number,line_pointer)表示该行的最新版本位于第 0 页,行指针为 2。
Heap Only Tuples (HOT)
此行为是 Postgres 一项名为“仅堆元组 (HOT)”的优化的一部分。在执行 UPDATE 操作时,Postgres 不会立即更新索引以指向新元组(这样做成本较高),而是会更新旧元组的 t_ctid 以指向新版本。这会创建一个元组链——索引指向旧元组,旧元组指向新元组,依此类推。
Postgres 依靠 Vacuum 进程来清理死元组,并在稍后更新索引。
您可以在 Postgres 官方文档中了解更多关于 HOT 的信息:
👉 https://www.postgresql.org/docs/current/storage-hot.html
Vacuum Process
你可能已经注意到,在之前的例子中,vacuum 操作被禁用了。这是故意为之,因为它允许我们观察死元组和 HOT 链,否则它们会被vacuum 进程清理掉。
真空在 Postgres 中扮演着至关重要的角色,它可以永久删除死元组、回收存储空间,并更新索引以消除热链——所有这些都有助于提升读取性能。它还可以通过释放死元组占用的存储空间来防止表膨胀。
清理通常配置为自动运行,但也可以手动运行。让我们尝试清理包含 1 个死元组的表。
postgres = # SELECT n_live_tup, n_dead_tup, relname FROM pg_stat_all_tables WHERE relname = 'store' ;
n_live_tup | n_dead_tup | relname
------------+------------+---------
1 | 1 | store ( 1 row )
--- 我们有 1 个死元组
--- 手动清理“store”表
postgres = # VACUUM store;
VACUUM
--- 我们最终得到 0 个死元组
postgres = # SELECT n_live_tup, n_dead_tup, relname FROM pg_stat_all_tables WHERE relname = 'store' ;
n_live_tup | n_dead_tup | relname
------------+------------+---------
1 | 0 |存储
(1 行)
postgres = #从heap_page_items(get_raw_page( 'store',0 ))中选择lp、t_ctid、t_xmin、t_xmax ; lp | t_ctid | t_xmin | t_xmax ----+--------+--------+ -------- 1 | | | 2 |(0,2)| 756 | 0 ( 2 行)
- 检查页面时我们只能看到一个元组,位于 lp 2,因为lp1 处的旧元组已被删除。
然而,清理表是有代价的。它会消耗 CPU 和内存,占用原本可以用于读写的资源,从而造成性能瓶颈。此外,清理所需的时间可能从几秒到几小时不等,具体取决于所需的清理量。
真空对于 至关重要Transaction Wraparound。您可以在这里相关信息
https://www.postgresql.org/docs/current/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND
对读取性能的影响
MVCC 不仅影响写入性能,还会影响读取性能。执行查询时,Postgres 需要确定哪个行版本有效,这会增加一些开销:
1. 行可见性检查
每次读取一行时,Postgres 都会检查其xmin和xmax值以确定它是否是最新版本。这会增加一些处理时间,尤其是在由于频繁更新而存在多个行版本的情况下。
2. 表膨胀
当更新操作创建新的行版本时,旧版本将保留在表中,直到VACUUM操作将其删除。随着时间的推移,这会增加表的大小并降低读取速度,因为 Postgres 可能需要扫描多个行版本才能找到有效的版本。
频繁VACUUM并AUTOVACUUM有助于减少膨胀并提高读取性能。
3. 写入密集型工作负载中的更新与插入
读取延迟可能会有所不同,具体取决于您是更新现有行还是插入新行:
- 插入——直接添加新行而不影响现有行。
- 更新——创建一个新的行版本并将旧版本标记为死版本,这会增加表的大小,并且需要在读取期间做更多的工作才能找到最新版本。
对于更新,Postgres 可能需要调整索引指针,因为每次更新都会创建一个新的元组。但是,如果更新符合HOT(仅堆元组)更新的条件,则意味着索引不需要立即更新,从而提高效率。
然而,热更新会在堆中创建元组链,这会增加的工作量VACUUM。Postgres 需要在读取期间跟踪链以查找最新的有效版本,并且一旦VACUUM删除旧版本,索引可能仍需要调整以反映最新状态。
频繁执行自动清理有助于清理死行并保持一致的性能。如果您在写入密集型工作负载中发现读取速度变慢,降低更新频率或调整数据模型可能会有所帮助。
这并不是建议你完全避免更新。但是,如果你在写入密集型工作负载下遇到更高的读取延迟,则值得考虑高更新频率是否是导致此问题的原因。优化数据模型以减少或避免过度更新,可以帮助缓解此问题并提高整体性能。
对于写入极其繁重的情况,针对高写入吞吐量进行优化的宽列存储(如 Cassandra 或 ScyllaDB)可以提供更好的性能。
对存储空间的影响
由于 MVCC,我们进行的更新越多,Postgres 消耗的磁盘空间就越多,而vacuum 负责回收该空间。
让我们用一个例子来演示一下,我将插入 100 万条记录并执行一些更新。我们还将查看每一步表占用的存储空间。
postgres = #截断store;
TRUNCATE TABLE ---
插入一百万条记录postgres = # DO $$
BEGINFOR i IN 1. .1000000 LOOPINSERT INTO store (name, value )VALUES ( 'Name_' || i, i * 10 ); END LOOP;
END $$;
DO
postgres = # SELECT COUNT ( * ) FROM store;count ---------1000000 ( 1 row )
让我们看看这个表占用的空间。
postgres = # SELECTrelname AS table_name, pg_size_pretty(pg_total_relation_size(relid)) AS total_size
FROMpg_catalog.pg_statio_user_tables
WHERErelname = 'store' ; table_name | total_size------------+------------ store | 71 MB ( 1 行)
这 100 万条记录占用了大约 71 MB 的磁盘空间。让我们更新所有行并检查存储空间(我们仍然禁用了自动清理功能)。
postgres = #更新存储设置 值= 2000 ;
更新 1000000 postgres = # SELECT relname AS table_name, pg_size_pretty(pg_total_relation_size(relid)) AS total_size FROMpg_catalog.pg_statio_user_tables
WHERErelname = 'store' ;
table_name | total_size
------------+------------
store | 142 MB ( 1 行)
正如预期的那样,由于更新语句之后每行都有 2 个元组(1 个死元组和 1 个活元组),因此表的大小增加了一倍。
让我们来看看这张表占用的空间。
postgres = # VACUUM 存储;
VACUUM postgres = # SELECTrelname AS table_name, pg_size_pretty(pg_total_relation_size(relid)) AS total_size
FROMpg_catalog.pg_statio_user_tables
WHERE relname = 'store' ; table_name | total_size
------------+------------
存储 | 142 MB
(1 行)
postgres = # SELECT n_live_tup,n_dead_tup,relname FROM pg_stat_all_tables WHERE relname = 'store' ;
n_live_tup | n_dead_tup | relname
------------+------------+---------1000000 | 0 |存储
(1 行)
有趣的是,在清理表之后,存储空间仍然为 142 MB,但死元组的数量已降至零,证实清理操作已成功清理元组。
这是 Vacuum 的预期行为——它不会减少总存储空间,而是将释放的空间标记为可重用。这意味着任何新数据都将写入现有存储空间,而不是增加整体磁盘使用率。
但是,在某些情况下,这可能并不理想。如果您真的想回收磁盘空间,可以运行 VACUUM FULL 操作:
postgres = # VACUUM FULL存储;
VACUUM postgres = # SELECTrelname AS table_name, pg_size_pretty(pg_total_relation_size(relid)) AS total_size
FROMpg_catalog.pg_statio_user_tables
WHERErelname = 'store';
table_name | total_size
------------+------------
store | 71 MB
(1 行)
在这个例子中,VACUUM FULL 回收了之前标记为可重复使用或未使用的物理磁盘空间,从而减少了整体存储大小。
结论
我们详细介绍了 MVCC 的工作原理以及真空过程对性能的重要性。
这篇文章中还有很多内容我没有涉及,我鼓励您阅读其中的一些主题(或者除非我决定在将来的某个时候撰写它们):
- 真空分析操作
- 重建索引操作
- 交易回溯调节
- 自动真空
要点:MVCC 允许 Postgres 高效地处理并发事务,但也带来了一些弊端。插入操作通常比更新操作读取/性能更快,因为更新操作会创建新的行版本,这会导致数据库膨胀,除非通过清理操作进行妥善管理。了解 MVCC 和 HOT 有助于您微调数据库以获得更佳性能。
#PG证书#PG考试#PostgreSQL培训#PostgreSQL考试#PostgreSQL认证
相关文章:
【PGCCC】Postgres MVCC 内部:更新与插入的隐性成本
为什么 Postgres 中的更新操作有时感觉比插入操作慢?答案在于 Postgres 如何在后台管理数据版本。 Postgres 高效处理并发事务能力的核心是多版本并发控制(MVCC)。 在本文中,我将探讨 MVCC 在 Postgres 中的工作原理以及它如何影响…...
ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之触摸屏 Touch)
目录 ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之触摸屏 Touch)简介模块概述功能定义架构位置核心特性 触摸(Touch)外设触摸外设概述触摸外设API和数据结构外设层API(periph_touch.h/periph_touch…...
Python高级爬虫之JS逆向+安卓逆向1.5节: 控制结构
目录 引言: 1.5.1 Python中的控制结构 1.5.2 条件控制 1.5.3 循环控制 1.5.4 跳转控制 1.5.5 爬虫不要接学生单 引言: 大神薯条老师的高级爬虫安卓逆向教程: 这套爬虫教程会系统讲解爬虫的初级,中级,高级知识&a…...
Spine-Leaf 与 传统三层架构:全面对比与解析
本文将详细介绍Spine-Leaf架构,深入对比传统三层架构(Core、Aggre、Access),并探讨其与Full-mesh网络和软件定义网络(SDN)的关联。通过通俗易懂的示例和数据中心网络分析,我将帮助您理解Spine-L…...
微信小程序文字混合、填充动画有效果图
效果图 .wxml <view class"text" style"--deg:{{deg}}deg;"><view>混合父级颜色</view> </view> <view class"fill {{status?action:}}">文字颜色填充</view> <button bind:tap"setStatus"…...
山东大学软件学院创新项目实训开发日志(15)之中医知识问答历史对话查看bug处理后端信息响应成功但前端未获取到
在开发中医知识问答历史对话查看功能的时候,出现了前后端信息获取异同的问题,在经过非常非常非常艰难的查询之后终于解决了这一问题,而这一问题的罪魁祸首就是后端没有setter和getter方法!!!!&a…...
HttpSessionBindingListener 的用法笔记250417
HttpSessionBindingListener 的用法笔记250417 HttpSessionBindingListener 是 Java Servlet 规范中 唯一 由 被存储对象自身实现 的会话监听接口, 1. 核心功能 HttpSessionBindingListener 是一个由 会话属性对象自身实现 的接口,用于监听该对象被绑定…...
EuroCropsML:首个面向少样本时间序列作物分类的多国基准数据集
2025-04-15,由慕尼黑工业大学等机构创建的 EuroCropsML 数据集,这是一个结合了农民报告的作物数据与 Sentinel-2 卫星观测的时间序列数据集,覆盖了爱沙尼亚、拉脱维亚和葡萄牙。该数据集为解决遥感应用中作物类型数据空间不平衡问题提供了新的…...
《如何用 Function 实现动态配置驱动的处理器注册机制?》
大家好呀!👋 今天我们来聊聊一个超实用的技术话题 - 如何用Java的Function接口实现动态配置驱动的处理器注册机制。听起来很高大上?别担心,我会用最简单的方式讲清楚!😊 一、为什么要用Function实现处理器…...
PyTorch:学习 CIFAR-10 分类
🔍 开始你的图像分类之旅:一步一步学习 CIFAR-10 分类 图像分类是计算机视觉中最基础的任务之一,如果你是初学者,那么以 CIFAR-10 为训练场是一个不错的选择。本文一步一步带你从零开始,学习如何用深度学习模型实现图…...
SpringBoot整合Thymeleaf模板:构建现代化Web视图层的完整指南
在Java Web开发领域,Thymeleaf作为一款自然模板引擎,凭借其优雅的语法和与Spring生态的无缝集成,已成为替代传统JSP的首选方案。本文将从技术整合、核心原理到生产实践,深度解析SpringBoot与Thymeleaf的协同工作方式。 一、Thymel…...
学习笔记十五——rust柯里化,看不懂 `fn add(x) -> impl Fn(y)` 的同学点进来!
🧠 Rust 柯里化从零讲透:看不懂 fn add(x) -> impl Fn(y) 的同学点进来! 🍔 一、什么是柯里化?先用一个超好懂的生活比喻 假设你在点一个汉堡: 你说:我要点一个鸡腿汉堡! 店员…...
软件安装包-yum
yum:软件管理的得力助手 yum是一个软件下载安装管理的一个客户端,例如:小米应用商城、华为应用商城... Linux中软件包可能有依赖关系——yum会帮我们解决依赖关系的问题! 1、软件包是什么? 在Linux下安装软件, 一个通…...
C++面试
C面试 c面试100题 1、封装多态继承 2、数据集合 3、 4、便于外部文件访问 5、只能通过对象访问 6、通过类名 7、构造函数、析构函数、拷贝构造函数、拷贝复制函数 8、将一个对象复制给新建的对象 9、没有返回值 10、类的对象中有指针,防止多个指针指向同…...
Java SpringBoot设置自定义web的图片本地路径
一,设置配置文件:application.properties my.config.image-pathD:\\Download\\images二,新增配置类:MyImagesConfig import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springfr…...
Python HTTP库——requests
文章目录 简介安装基本概念RESTfulAPIOAuth2.0Cookie和Session 初试GET请求POST请求PUT请求DELETE请求HEAD请求OPTIONS请求传递查询参数响应内容自定义响应头传递表单参数传递文件响应状态码响应头Cookies重定向和历史记录超时错误和异常Session对象请求和响应对象预处理请求SS…...
用idea配置springboot+mybatis连接postersql数据库
从socket开始,我们就要开始部署前后端的交互了,所以今天带来一份热度比较高的框架springboot,并教大家如何连接数据库。 框架 先给大家看一下目录结构,因为有些需要调用文件路径: 创建项目: 新版本可以…...
【补充篇】Davinci工具要求的dbc格式
1 简介 DBC文件是一种用于描述CAN(Controller Area Network,控制器局域网络)通信协议中报文和信号的格式化文件,其全称为“Database CAN”。DBC文件的核心作用是定义和解析CAN网络中的通信数据,包括节点、报文、信号及其属性等信息。 对于不同角色的工程师,DBC文件有着…...
IT资产管理(一)之GLPI安装及部署
一、GLPI 介绍 GLPI:Gestionnaire Libre de Parc Informatique 是一个免费的资产和 IT 管理软件包,提供 ITIL 服务台功能、许可证跟踪和软件审计。 GLPI 的主要功能: 服务资产和配置管理 (SACM):管理您的 IT 资产和配置,跟踪计算机、外围设备、网络打印机及其相关组件…...
RPCRT4!OSF_CCALL::ActivateCall函数分析之RPCRT4!OSF_CCALL结构中的Bindings--RPC源代码分析
第一部分: 1: kd> t RPCRT4!OSF_CCALL::ActivateCall: 001b:77bf5789 55 push ebp 1: kd> kc # 00 RPCRT4!OSF_CCALL::ActivateCall 01 RPCRT4!OSF_CASSOCIATION::AllocateCCall 02 RPCRT4!OSF_BINDING_HANDLE::AllocateCCall 03 RPCRT4!OS…...
docker登录AWS ECR拉取镜像
1、配置AWS 登录key [rootip-172-31-13-6 ~]# aws configure AWS Access Key ID [None]: XXXXXXXXXXX AWS Secret Access Key [None]: %%YYYDSRGTHFGFSGRTHTHE$RHTSG Default region name [None]: ap-southeast-1 Default output format [None]: json2、登录AWS ECR镜像仓库 …...
IntelliJ IDEA download JDK
IntelliJ IDEA download JDK 自动下载各个版本JDK,步骤 File - Project Structure (快捷键 Ctrl Shift Alt S) 如果下载失败,换个下载站点吧。一般选择Oracle版本,因为java被Oracle收购了 好了。 花里胡哨&#…...
MQTT协议与HTTP协议的对比分析
以下是MQTT协议与HTTP协议的对比分析,从协议特性到应用场景的系统性对比: 一、协议层级与设计目标对比 维度MQTTHTTP协议层级应用层协议(基于TCP/IP)应用层协议(基于TCP/IP)核心设计目标机器间轻量级消息通…...
jenkins凭据管理(配置github密钥)
1.凭据管理 添加两种类型的凭据,Username with password和Secret text(填的token) Username with password是github登录的用户名和密码,Secret text填的github生成的token,权限的限制更细,安全性更高一些 Dashboard -> Manag…...
B端小程序如何突破常规,成为企业获客新利器?
数据驱动的用户旅程优化 在当今竞争激烈的市场环境中,了解并优化用户的交互路径对于吸引和保留客户至关重要。B端小程序可以通过收集用户行为数据来分析用户偏好和使用习惯。例如,应用热图分析工具可以直观展示用户点击最频繁的区域,帮助企业…...
25.4.17学习总结
关于bcrypt算法 BCrypt 的主要特点和优点: 加盐 (Salting): BCrypt 会自动为每个密码生成一个随机的盐值 (salt) 并将其与密码组合在一起,然后再进行哈希。 盐值是随机数据,用于防止彩虹表攻击。 这意味着即使两个用户使用相同的密码&#x…...
java 设计模式 策略模式
简介 策略模式(Strategy Pattern)是一种行为设计模式,旨在定义一系列算法,并将每一个算法封装起来,使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户端。换句话说,策略模式通过将不同的算法…...
游戏盾和高防ip有什么区别
游戏盾和高防IP都是针对网络攻击的防护方案,但核心目标、技术侧重点和应用场景存在显著差异。以下是两者的详细对比分析: 一、核心定位与目标 维度高防IP游戏盾核心目标抵御大流量网络攻击(…...
关于 雷达(Radar) 的详细解析,涵盖其定义、工作原理、分类、关键技术、应用场景、挑战及未来趋势,结合实例帮助理解其核心概念
以下是关于 雷达(Radar) 的详细解析,涵盖其定义、工作原理、分类、关键技术、应用场景、挑战及未来趋势,结合实例帮助理解其核心概念: 一、雷达的定义与核心功能 1. 定义 雷达(Radar,Radio D…...
机器学习 Day11 决策树
1.决策树简介 原理:思想源于程序设计的 if - else 条件分支结构 ,是一种树形结构。内部节点表示属性判断,分支是判断结果输出,叶节点是分类结果 。案例:以母亲给女儿介绍男朋友为例。女儿依次询问年龄(≤3…...
【HFP】深入解析蓝牙 HFP 协议中呼叫转移、呼叫建立及保持呼叫状态的机制
目录 一、核心指令概述 1.1 ATCMER:呼叫状态更新的 “总开关” 1.2 ATBIA:指示器的 “精准控制器” 1.3 指令对比 1.4 指令关系图示 二、CIEV 结果码:状态传递的 “信使” 2.1 工作机制 2.2 三类核心指示器 三、状态转移流程详解 3…...
音频识别优化(FFT)
整合多频段检测、动态阈值调整和持续时长验证的完整代码实现,包含详细注释: #include "esp_dsp.h" #include "driver/i2s.h" #include "esp_log.h" #include "math.h" static const char* TAG "ADV_FRE…...
【Redis】Redis基本命令(1)
KEYS 返回所有满足样式(pattern)的key。 KEY * 返回所有key,不简易使用 性能问题:当 Redis 存储百万级键时,会消耗大量 CPU 和内存资源,Redis 是单线程模型,KEYS * 执行期间会阻塞其他所有命令…...
IDEA2024 pom.xml依赖文件包报红解决
异常: 原因: 本地的Maven Repository库中不存在对应版本的dependency依赖,所以导致报红。 解决: 方法1:找到对应项目,右键Sync Project 就可以了 方法2:修改setting中maven的自动更新…...
Qt 信号与槽复习
Qt 信号与槽复习 Qt 信号与槽(Signals and Slots)机制是 Qt 框架的核心特性之一,用于实现对象之间的通信。它提供了一种松耦合的方式,使得组件可以独立开发和复用,广泛应用于 GUI 编程、事件处理和跨模块交互。本文将…...
RestControllerAdvice 和 ControllerAdvice 两个注解的区别与联系
它们都用于实现全局的通用处理逻辑,主要应用在以下三个方面: 全局异常处理: 使用 ExceptionHandler 注解的方法。全局数据绑定: 使用 InitBinder 注解的方法。全局数据预处理: 使用 ModelAttribute 注解的方法。 联系: 核心功能相同: 两者都提供了上述…...
最快打包WPF 应用程序
在 Visual Studio 中右键项目选择“发布”,目标选“文件夹”,模式选“自包含”,生成含 .exe 的文件夹,压缩后可直接发给别人或解压运行,无需安装任何东西。 最简单直接的新手做法: 用 Visual Studio 的“…...
Java NIO Java 虚拟线程(微线程)与 Go 协程的运行原理不同 为何Go 能在低配机器上承接10万 Websocket 协议连接
什么是Java NIO? Java NIO(New Input/Output) 是Java 1.4(2002年)引入的一种非阻塞、面向缓冲区的输入输出框架,旨在提升Java在高性能和高并发场景下的I/O处理能力。它相比传统的 Java IO(java…...
C# 对列表中的元素的多个属性进行排序
目录 前言一、OrderBy、OrderByDescending、ThenBy、ThenByDescending二、Sort 前言 在开发过程中,我们经常需要 根据列表中的元素的某个属性进行排序,下面我们将简单介绍常用的排序函数。 例如此处有一个类,拥有的元素为编号和值 public …...
OpenCV颜色变换cvtColor
OpenCV计算机视觉开发实践:基于Qt C - 商品搜索 - 京东 颜色变换是imgproc模块中一个常用的功能。我们生活中看到的大多数彩色图片都是RGB类型的,但是在进行图像处理时需要用到灰度图、二值图、HSV(六角锥体模型,这个模型中颜色的…...
java IO/NIO/AIO
(✪▽✪)曼波~~~~!让曼波用最可爱的赛马娘方式给你讲解吧!(⁄ ⁄•⁄ω⁄•⁄ ⁄) 🎠曼波思维导图大冲刺(先看框架再看细节哦): 📚 解释 Java 中 IO、NIO、AIO 的区别和适用场景: …...
如何深入理解引用监视器,安全标识以及访问控制模型与资产安全之间的关系
一、核心概念总结 安全标识(策略决策的 “信息载体) 是主体(如用户、进程)和客体(如文件、数据库、设备)的安全属性,用于标记其安全等级、权限、访问能力或受保护级别,即用于标识其安全等级、权限范围或约束…...
宜搭与金蝶互通——连接器建立
一、 进入连接器工厂 图1 连接器入口 二、 新建连接器 图2 新建连接器第一步 1、 连接器显示名,如图2中①所示; 2、 图2中②域名,是金蝶系统API接口里面的“完整服务地址”com之前的信息,不含“https”,如图3中①所示; 3、 Base Url通常为“/”,如图2…...
中间件--ClickHouse-7--冷热数据分离,解决Mysql海量数据瓶颈
在web应用中,当数据量非常大时,即使MySQL的存储能够满足,但性能一般也会比较差。此时,可以考虑使用ClickHouse存储历史数据,在Mysql存储最近热点数据的方式,来优化和提升查询性能。ClickHouse的设计初衷就是…...
1.1 设置电脑开机自动用户登录exe开机自动启动
本文介绍两个事情: 1.Windows如何开机自动登录系统(不用输密码) 2. 应用程序(.exe)如何开机自动启动 详细解释如下: 一、Windows如何开机自动登录系统(不用输密码) 设备上的工控机,如果开机后都需要操作人员输入密码&…...
vscode stm32 variable uint32_t is not a type name 问题修复
问题 在使用vscodekeil开发stm32程序时,发现有时候vscode的自动补全功能失效,且problem窗口一直在报错。variable “uint32_t” is not a type name uint32_t 定义位置 uint32_t 实际是在D:/Keil_v5/ARM/ARMCC/include/stdint.h中定义的。将D:/Keil_v5…...
动态规划与记忆化搜索的区别与联系
记忆化搜索(Memoization)和动态规划(Dynamic Programming, DP)都是解决重叠子问题的高效算法技术,但它们有着不同的实现方式和特点。 1. 基本概念 记忆化搜索(自顶向下) 本质:带有…...
html+js+clickhouse环境搭建
实验背景: 我目前有一台服务器A,和一台主机B,两台设备属于同一局域网,相互之间可以通讯。服务器A中部署着clickhouse,我在主机B中想直接通过javascript代码访问服务器中的clickhouse数据库并获取数据。 ClickHouse 服务…...
生命护航行动再启航!
温州好人陈飞携防溺水课堂,为乡村少年宫筑起安全防线 图文作者:华夏之音/李望 随着夏日热浪的滚滚而来,楠溪江畔的安全警钟再次响起。在这片如诗如画的土地上,一场旨在保护青少年生命安全的防溺水课堂活动拉开了…...
Android Compose Activity 页面跳转动画详解
下面我将全面详细地介绍在 Compose 中实现 Activity 跳转动画的各种方法,包括基础实现、高级技巧和最佳实践。 一、基础 Activity 过渡动画 1. overridePendingTransition 传统方式 这是最基础且兼容性最好的方法,适用于所有 Android 版本。 实现步骤…...