Postgres 如何使事务原子化?
原子性(“ACID”意义上的)要求 对于对数据库执行的一系列操作,要么一起提交,要么全部回滚;不允许中间状态。对于现实世界的混乱的代码来说,这是天赐之物。
这些更改将被恢复,而不是导致生产环境中的错误更改数据,然后使其永久损坏。在处理数百万个请求时,由于间歇性问题和其他意外状态而中途断开的长尾连接可能会造成不便,但不会扰乱您的数据。
Postgres 的实现尤其以很少的开销提供强大的事务语义而闻名。虽然我已经使用它很多年了,但我从来没有理解过它。 Postgres 工作得足够可靠,以至于我能够将它视为一个黑匣子——非常有用,但其内部工作原理却是一个谜。
本文探讨了 Postgres 如何记录其事务、事务如何原子提交,以及理解这一切的关键概念1 。
管理并发访问
假设您构建了一个简单的数据库,可以读取和写入磁盘上的 CSV 文件。当单个客户端发出请求时,它会打开文件,读取一些信息,然后写回更改。大部分情况都运行良好,但有一天您决定使用一项复杂的新功能(多客户端支持)来增强您的数据库!
不幸的是,新的实现立即受到问题的困扰,当两个客户端几乎同时尝试访问数据时,这些问题似乎尤其明显。用户打开 CSV 文件,读取、修改和写入一些数据,但该更改会立即被尝试执行相同操作的另一个客户端破坏。
两个客户端之间的争用导致数据丢失。
这是并发访问的问题,可以通过引入并发控制来解决。有很多简单的解决方案。我们可以确保任何进程在读取或写入文件之前获取文件上的独占锁,或者我们可以通过单个流控制点推送所有操作,以便它们一次只运行一个。这些解决方法不仅速度慢,而且无法扩展以使我们的数据库完全符合 ACID 标准。现代数据库有一个更好的方式,MVCC(多版本并发控制)。
在 MVCC 下,语句在内部作为 transaction执行,并且不是直接覆盖数据,而是创建它的新版本。原始数据仍然可供其他可能需要它的客户端使用,并且任何新数据都将保持隐藏状态,直到事务提交。客户端不再直接争用,并且数据可以安全地保留,因为它们不会覆盖彼此的更改。
当事务启动时,它会拍摄一个快照来捕获数据库当时的状态。数据库中的每笔事务都是串行应用的 顺序,使用全局锁确保只有一个正在被执行 一次确认已提交或中止。快照是一个 完美表示两者之间的数据库状态 事务。
为了避免已删除和隐藏的行没完没了地累积,数据库 将通过真空进程(或者在某些情况下,与其他查询一起发生的可能的“微真空”)来删除过时的数据,但它们只会执行此操作以获取打开的快照不再需要的信息。
Postgres 使用 MVCC 管理并发访问。让我们看看它是如何工作的。
事务、元组和快照
这是 Postgres 用于表示事务的数据结构(来自proc.c ):
typedef struct PGXACT
{TransactionId xid; /* id of top-level transaction currently being* executed by this proc, if running and XID* is assigned; else InvalidTransactionId */TransactionId xmin; /* minimal running XID as it was when we were* starting our xact, excluding LAZY VACUUM:* vacuum must not remove tuples deleted by* xid >= xmin ! */...
} PGXACT;
事务通过xid
(事务或“xact”ID)进行标识。作为一项优化,Postgres 仅在事务开始修改数据时才为其分配xid
,因为只有在此时其他进程才需要开始跟踪其更改。只读事务不需要xid
。
当一个事务启动时,‘ xmin ’会立即被设置为所有正在运行的事务中最小的‘ xid ’ 。
真空进程通过获取所有活动事务的xmin 来计算它们需要保留的最小数据边界。
元组生命周期
Postgres 中的数据行通常称为 元组。虽然 Postgres 使用 B 树等常见查找结构来加快检索速度,但索引并不存储元组的完整数据集或其任何可见性信息。相反,它们存储一个tid
(元组 ID),可用于从物理存储(也称为“堆”)检索行。 tid
为 Postgres 提供了一个起点,它可以开始扫描堆,直到找到满足当前快照可见性的元组。
下面是堆元组的 Postgres 实现(与索引元组相反,索引元组是在索引中找到的结构),以及表示其头部信息的其他一些结构(来自htup.h
和htup_details.h
):
typedef struct HeapTupleData
{uint32 t_len; /* length of *t_data */ItemPointerData t_self; /* SelfItemPointer */Oid t_tableOid; /* table the tuple came from */HeapTupleHeader t_data; /* -> tuple header and data */
} HeapTupleData;/* referenced by HeapTupleData */
struct HeapTupleHeaderData
{HeapTupleFields t_heap;...
}/* referenced by HeapTupleHeaderData */
typedef struct HeapTupleFields
{TransactionId t_xmin; /* inserting xact ID */TransactionId t_xmax; /* deleting or locking xact ID */...
} HeapTupleFields;
与事务一样,元组跟踪其自己的xmin
,但在元组的情况下,它被记录为表示元组变得可见的第一个事务(即创建它的事务)。它还跟踪xmax
是最后一个 元组可见的事务(即 删除它的事务) 2 .
使用 xmin 和 xmax 跟踪堆元组的生命周期。
xmin
和xmax
是内部概念,但它们可以在任何 Postgres 表上显示为隐藏列。只需按名称显式选择它们即可:
# SELECT *, xmin, xmax FROM names;id | name | xmin | xmax
----+----------+-------+-------1 | Hyperion | 27926 | 279282 | Endymion | 27927 | 0
快照:xmin、xmax 和 xip
这是快照结构(来自 snapshot.h ):
typedef struct SnapshotData
{/** The remaining fields are used only for MVCC snapshots, and are normally* just zeroes in special snapshots. (But xmin and xmax are used* specially by HeapTupleSatisfiesDirty.)** An MVCC snapshot can never see the effects of XIDs >= xmax. It can see* the effects of all older XIDs except those listed in the snapshot. xmin* is stored as an optimization to avoid needing to search the XID arrays* for most tuples.*/TransactionId xmin; /* all XID < xmin are visible to me */TransactionId xmax; /* all XID >= xmax are invisible to me *//** For normal MVCC snapshot this contains the all xact IDs that are in* progress, unless the snapshot was taken during recovery in which case* it's empty. For historic MVCC snapshots, the meaning is inverted, i.e.* it contains *committed* transactions between xmin and xmax.** note: all ids in xip[] satisfy xmin <= xip[i] < xmax*/TransactionId *xip;uint32 xcnt; /* # of xact ids in xip[] */...
}
快照的xmin
的计算方式与事务的 xmin 相同(即创建快照时正在运行的事务中的最低xid
),但目的不同。快照xmin
是数据可见性的下限。由xid < xmin
的事务创建的元组对快照可见。
它还定义了一个xmax
,它被设置为最后提交的xid
加一。 xmax
跟踪可见性的上限; xid >= xmax
的事务对于快照不可见。
最后,快照定义了*xip
,一个包含所有 创建快照时正在活动的事务的xid
。 *xip
是需要的,因为即使已经存在xmin
的可见性边界,仍然可能有一些事务已经提交,并且xid
大于xmin
,但也大于正在进行的事务的xid
(因此它们不能包含在xmin
中)。
我们希望xid > xmin
的所有已提交事务的结果可见,但隐藏任何正在运行的事务的结果。 *xip
存储创建快照时处于活动状态的事务列表,以便我们可以区分哪个是哪个。
针对数据库执行的事务和捕获某个时刻的快照。
开始事务
当执行BEGIN语句时,Postgres会执行一些基本的簿记操作,但它会尽可能地推迟更昂贵的操作。例如,在新事务开始修改数据之前,系统将延迟分配“xid”,以减少其在其他位置跟踪所带来的开销。
新事务也不会立即获得快照。 当它运行第一个查询时,它将 exec_simple_query
(在postgres.c
中)会将一个压入堆栈。即使是简单的SELECT 1;
足以触发它:
static void
exec_simple_query(const char *query_string)
{.../** Set up a snapshot if parse analysis/planning will need one.*/if (analyze_requires_snapshot(parsetree)){PushActiveSnapshot(GetTransactionSnapshot());snapshot_set = true;}...
}
创建新快照是机器真正开始发挥作用的地方。这是GetSnapshotData
(在procarray.c
中):
Snapshot
GetSnapshotData(Snapshot snapshot)
{/* xmax is always latestCompletedXid + 1 */xmax = ShmemVariableCache->latestCompletedXid;Assert(TransactionIdIsNormal(xmax));TransactionIdAdvance(xmax);...snapshot->xmax = xmax;
}
该函数执行大量初始化操作,但正如我们所讨论的,它的一些最重要的工作是设置快照的xmin
、 xmax
和*xip
。其中最简单的是xmax
,它是从 postmaster 管理的共享内存中检索的。每个提交的事务都会通知 postmaster 它已提交,并且如果xid
高于其已持有的值, latestCompletedXid
将被更新。 (稍后会详细介绍)。
注意,该函数的责任是在最后一个xid
上加一。这并不像递增它那么简单,因为 Postgres 中的事务 ID 可能回绕。事务 ID 被定义为一个简单的无符号 32 位整数(来自ch ):
typedef uint32 TransactionId;
尽管xid
分配很节约(如上所述,读取不需要分配),但吞吐量很大的系统很容易达到 32 位的界限,因此系统需要能够回绕到“重置” xid
根据需要顺序。这是由一些预处理器处理的 魔法(在transam.h中):
#define InvalidTransactionId ((TransactionId) 0)
#define BootstrapTransactionId ((TransactionId) 1)
#define FrozenTransactionId ((TransactionId) 2)
#define FirstNormalTransactionId ((TransactionId) 3).../* advance a transaction ID variable, handling wraparound correctly */
#define TransactionIdAdvance(dest) \do { \(dest)++; \if ((dest) < FirstNormalTransactionId) \(dest) = FirstNormalTransactionId; \} while(0)
前几个 ID 被保留为特殊标识符,因此我们总是跳过这些 ID 并从3
开始。
回到GetSnapshotData
,我们得到xmin
和xip
迭代所有正在运行的事务(再次参见 上面的快照解释了它们的作用):
/** Spin over procArray checking xid, xmin, and subxids. The goal is* to gather all active xids, find the lowest xmin, and try to record* subxids.*/
for (index = 0; index < numProcs; index++)
{volatile PGXACT *pgxact = &allPgXact[pgprocno];TransactionId xid;xid = pgxact->xmin; /* fetch just once *//** If the transaction has no XID assigned, we can skip it; it* won't have sub-XIDs either. If the XID is >= xmax, we can also* skip it; such transactions will be treated as running anyway* (and any sub-XIDs will also be >= xmax).*/if (!TransactionIdIsNormal(xid)|| !NormalTransactionIdPrecedes(xid, xmax))continue;if (NormalTransactionIdPrecedes(xid, xmin))xmin = xid;/* Add XID to snapshot. */snapshot->xip[count++] = xid;...
}...snapshot->xmin = xmin;
提交事务
事务通过CommitTransaction
(在xact.c
中)提交。这个函数非常复杂,但以下是它的一些重要部分:
static void
CommitTransaction(void)
{.../** We need to mark our XIDs as committed in pg_xact. This is where we* durably commit.*/latestXid = RecordTransactionCommit();/** Let others know about no transaction in progress by me. Note that this* must be done _before_ releasing locks we hold and _after_* RecordTransactionCommit.*/ProcArrayEndTransaction(MyProc, latestXid);...
}
持久性和 WAL
Postgres 完全是围绕持久性的目标而设计的,这意味着即使在崩溃或断电等极端事件中,已提交的事务也应保持已提交状态。与许多优秀的系统一样,它使用预写日志( WAL ,或“xlog”)来实现这种持久性。所有更改都会写入并刷新到磁盘,即使突然终止,Postgres 也可以重播它在 WAL 中找到的内容,以恢复未写入其数据文件的任何更改。
上面代码片段中的RecordTransactionCommit
处理将事务状态更改发送到 WAL:
static TransactionId
RecordTransactionCommit(void)
{bool markXidCommitted = TransactionIdIsValid(xid);/** If we haven't been assigned an XID yet, we neither can, nor do we want* to write a COMMIT record.*/if (!markXidCommitted){...} else {XactLogCommitRecord(xactStopTimestamp,nchildren, children, nrels, rels,nmsgs, invalMessages,RelcacheInitFileInval, forceSyncCommit,MyXactFlags,InvalidTransactionId /* plain commit */ );....}if ((wrote_xlog && markXidCommitted &&synchronous_commit > SYNCHRONOUS_COMMIT_OFF) ||forceSyncCommit || nrels > 0){XLogFlush(XactLastRecEnd);/** Now we may update the CLOG, if we wrote a COMMIT record above*/if (markXidCommitted)TransactionIdCommitTree(xid, nchildren, children);}...
}
提交日志
除了 WAL 之外,Postgres 还有一个提交日志(或 “clog”或“pg_xact”)总结了每笔事务并 无论它是提交还是中止。这就是 TransactionIdCommitTree
正在执行上面的操作 – 大部分 信息先写到WAL,然后 TransactionIdCommitTree
遍历并将提交日志中的事务状态设置为“已提交”。
尽管提交日志被称为“日志”,但它实际上更像是分布在共享内存和磁盘上的多个页面上的提交状态位图。在现代编程中很少见的节俭示例中,事务的状态只能用两位来记录,因此我们可以在每个字节中存储四个事务,即在标准 8k 页中存储 32,768 个事务。
来自clog.h
和clog.c
:
#define TRANSACTION_STATUS_IN_PROGRESS 0x00
#define TRANSACTION_STATUS_COMMITTED 0x01
#define TRANSACTION_STATUS_ABORTED 0x02
#define TRANSACTION_STATUS_SUB_COMMITTED 0x03#define CLOG_BITS_PER_XACT 2
#define CLOG_XACTS_PER_BYTE 4
#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
各种优化
虽然持久性很重要,但性能也是 Postgres 理念的核心价值。如果事务从未分配过xid
,Postgres 会跳过将其写入 WAL 和提交日志。如果事务被中止,我们仍然将其中止状态写入 WAL 和提交日志,但不必立即刷新(fsync),因为即使发生崩溃,我们也不会丢失任何信息。在崩溃恢复期间,Postgres 会注意到未标记的事务,并假设它们已中止。
防御性编程
TransactionIdCommitTree
(在transam.c中,及其实现TransactionIdSetTreeStatus
在 clog.c )提交一棵“树”,因为提交 可能有 子提交。我不会进入任何子提交 细节,但值得注意的是,因为 TransactionIdCommitTree
不能保证是原子的,每个子提交都被记录为单独提交,而父提交则被记录为最后一步。当 Postgres 在崩溃后恢复时,在读取父记录并确认提交之前,子提交记录不会被视为已提交(即使它们被标记为已提交)。
这又是以原子性的语义;系统本来可以成功记录每个子提交,但在写入父提交之前就崩溃了。
clog.c
中的内容如下:
/** Record the final state of transaction entries in the commit log for* all entries on a single page. Atomic only on this page.** Otherwise API is same as TransactionIdSetTreeStatus()*/
static void
TransactionIdSetPageStatus(TransactionId xid, int nsubxids,TransactionId *subxids, XidStatus status,XLogRecPtr lsn, int pageno)
{...LWLockAcquire(CLogControlLock, LW_EXCLUSIVE);/** Set the main transaction id, if any.** If we update more than one xid on this page while it is being written* out, we might find that some of the bits go to disk and others don't.* If we are updating commits on the page with the top-level xid that* could break atomicity, so we subcommit the subxids first before we mark* the top-level commit.*/if (TransactionIdIsValid(xid)){/* Subtransactions first, if needed ... */if (status == TRANSACTION_STATUS_COMMITTED){for (i = 0; i < nsubxids; i++){Assert(ClogCtl->shared->page_number[slotno] == TransactionIdToPage(subxids[i]));TransactionIdSetStatusBit(subxids[i],TRANSACTION_STATUS_SUB_COMMITTED,lsn, slotno);}}/* ... then the main transaction */TransactionIdSetStatusBit(xid, status, lsn, slotno);}...LWLockRelease(CLogControlLock);
}
通过共享内存发出完成信号
将事务记录到提交日志后,就可以安全地向系统的其余部分发出其完成的信号。这发生在上面CommitTransaction
的第二次调用中(进入 procarray.c ):
void
ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
{/** We must lock ProcArrayLock while clearing our advertised XID, so* that we do not exit the set of "running" transactions while someone* else is taking a snapshot. See discussion in* src/backend/access/transam/README.*/if (LWLockConditionalAcquire(ProcArrayLock, LW_EXCLUSIVE)){ProcArrayEndTransactionInternal(proc, pgxact, latestXid);LWLockRelease(ProcArrayLock);}...
}static inline void
ProcArrayEndTransactionInternal(PGPROC *proc, PGXACT *pgxact,TransactionId latestXid)
{.../* Also advance global latestCompletedXid while holding the lock */if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,latestXid))ShmemVariableCache->latestCompletedXid = latestXid;
}
您可能想知道“ProcArray”是什么。与许多其他类似守护进程的服务不同,Postgres 使用进程fork模型来处理并发而不是线程。当它接受新连接时,Postmaster 会fork一个新后端(在postmaster.c
中)。后端由PGPROC
结构(在proc.h
中)表示,整个活动进程集在共享内存中跟踪,即“ProcArray”。
现在记住,当创建快照时,我们如何设置它的 xmax
到latestCompletedXid + 1
?通过设置 全局共享内存中的latestCompletedXid
改为xid
刚刚提交的事务,我们刚刚完成了 结果对从此开始的每个新快照可见 向前指向任何后端。
使用LWLockConditionalAcquire
和 LWLockConditionalAcquire 来查看锁获取和释放调用 LWLockRelease
。大多数时候,Postgres 是完美的 很高兴让进程并行工作,但是有一个 少数地方需要获取锁来避免 竞争,这就是其中之一。临近年初 这篇文章我们讨论了 Postgres 中的事务处理 按串行顺序提交或中止,一次一个。 ProcArrayEndTransaction
获取独占锁,以便它可以更新latestCompletedXid
而不会被另一个进程否定其工作。
响应客户
在整个过程中,客户端一直在同步等待其事务被确认。原子性保证的一部分是不可能出现误报,即数据库在事务尚未提交时将其标记为已提交。失败可能发生在很多地方,但如果有一个地方,客户会发现它并有机会重试或以其他方式解决问题。
可见性检查
我们之前介绍了可见性信息如何存储在堆元组上。 heapgettup
(在heapam.c中)是负责扫描堆中满足快照可见性标准的元组的方法:
static void
heapgettup(HeapScanDesc scan,ScanDirection dir,int nkeys,ScanKey key)
{.../** advance the scan until we find a qualifying tuple or run out of stuff* to scan*/lpp = PageGetItemId(dp, lineoff);for (;;){/** if current tuple qualifies, return it.*/valid = HeapTupleSatisfiesVisibility(tuple,snapshot,scan->rs_cbuf);if (valid){return;}++lpp; /* move forward in this page's ItemId array */++lineoff;}...
}
HeapTupleSatisfiesVisibility
是一个预处理器宏, 将调用“satisfies”函数,例如 HeapTupleSatisfiesMVCC
(在tqual.c
中):
bool
HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,Buffer buffer)
{...else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))return false;else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,HeapTupleHeaderGetRawXmin(tuple));.../* xmax transaction committed */return false;
}
XidInMVCCSnapshot
根据快照的 xid 进行初始检查以查看元组的xid
是否可见 xmin
、 xmax
和xip
。这是一个简化的实现,显示了对每个(来自tqual.c
)的检查:
static bool
XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
{/* Any xid < xmin is not in-progress */if (TransactionIdPrecedes(xid, snapshot->xmin))return false;/* Any xid >= xmax is in-progress */if (TransactionIdFollowsOrEquals(xid, snapshot->xmax))return true;...for (i = 0; i < snapshot->xcnt; i++){if (TransactionIdEquals(xid, snapshot->xip[i]))return true;}...
}
注意,与您直观的想法相比,该函数的返回值是相反的 , false
表示xid
对快照可见。尽管令人困惑,但您可以通过将返回值与调用它的位置进行比较来了解它正在做什么。
确认xid
可见后,Postgres 使用TransactionIdDidCommit
(来自transam.c
)检查其提交状态:
bool /* true if given transaction committed */
TransactionIdDidCommit(TransactionId transactionId)
{XidStatus xidstatus;xidstatus = TransactionLogFetch(transactionId);/** If it's marked committed, it's committed.*/if (xidstatus == TRANSACTION_STATUS_COMMITTED)return true;...
}
进一步探索落实 TransactionLogFetch
将显示它的工作原理与宣传的一样。它根据给定的事务 ID 计算提交日志中的位置,并访问该位置以获取该事务的提交状态。提交的事务是否用于帮助确定元组的可见性。
这里的关键是,为了保持一致性,提交日志被视为提交状态(以及扩展的可见性)的规范来源3 。无论 Postgres 是在几小时前成功提交事务,还是在服务器刚刚从中恢复的崩溃前几秒,都将返回相同的信息。
Hint bits 提示位
上面的HeapTupleSatisfiesMVCC
在从可见性检查返回之前还做了一件事:
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,HeapTupleHeaderGetRawXmin(tuple));
检查提交日志以查看元组的xmin
或 提交xmax
事务是一项昂贵的操作。为了避免每次都必须访问它,Postgres 将为扫描的堆元组设置称为“提示位”的特殊提交状态标志。后续操作可以检查元组的提示位,并保存到提交日志本身。
黑盒子
当我对数据库运行事务时:
BEGIN;SELECT * FROM users WHERE email = 'brandur@example.com';INSERT INTO users (email) VALUES ('brandur@example.com')RETURNING *;COMMIT;
我不会停下来思考发生了什么事。我获得了一个强大的高级抽象(以 SQL 的形式),我知道它会可靠地工作,并且正如我们所见,Postgres 在幕后完成了所有繁重的工作。好的软件是一个黑盒子,而 Postgres 是一个特别黑的盒子(尽管内部结构很容易访问)。
感谢Peter Geoghegan耐心地回答了我所有关于 Postgres 事务和快照的业余问题,并为我提供了一些查找相关代码的指导。
1警告几句:Postgres 源代码相当庞大,因此我掩盖了一些细节以使本文更容易理解。它也正在积极开发中,因此随着时间的推移,其中一些代码示例可能会变得相当过时。
2读者可能会注意到,虽然xmin
和xmax
可以很好地跟踪元组的创建和删除,但它们不足以处理更新。为了简洁起见,我现在掩盖了更新的工作原理。
3请注意,提交日志最终将被截断,但只会超出快照的xmin
范围,因此在必须在 WAL 中进行检查之前,可见性检查会短路。
原文地址:How Postgres Makes Transactions Atomic — brandur.org
相关文章:

Postgres 如何使事务原子化?
原子性(“ACID”意义上的)要求 对于对数据库执行的一系列操作,要么一起提交,要么全部回滚;不允许中间状态。对于现实世界的混乱的代码来说,这是天赐之物。 这些更改将被恢复,而不是导致生产环境…...

[Vue3]简易版Vue
简易版Vue 实现ref功能 ref功能主要是收集依赖和触发依赖的过程。 export class Dep { // 创建一个类,使用时使用new Depconstructor(value) { // 初始化可传入一个值this._val value;this.effects new Set(); //收集依赖的容器,使用set数据结构}…...

ElasticSearch学习记录
服务器操作系统版本:Ubuntu 24.04 Java版本:21 Spring Boot版本:3.3.5 如果打算用GUI,虚拟机安装Ubuntu 24.04,见 虚拟机安装Ubuntu 24.04及其常用软件(2024.7)_ubuntu24.04-CSDN博客文章浏览阅读6.6k次࿰…...

LabVIEW算法执行时间评估与Windows硬件支持
在设计和实现复杂系统时,准确估算算法的执行时间是关键步骤,尤其在实时性要求较高的应用中。这一评估有助于确定是否需要依赖硬件加速来满足性能需求。首先需要对算法进行时间复杂度分析并进行实验测试,了解其在Windows系统中的运行表现。根据…...

经验帖 | Matlab安装成功后打不开的解决方法
最近在安装Matlab2023时遇到了一个问题: 按照网上的安装教程成功安装 在打开软件时 界面闪一下就消失 无法打开 但是 任务管理器显示matlab在运行中 解决方法如下: matlab快捷方式–>右键打开属性–>目标 填写许可证文件路径 D:\MATLAB\MatlabR20…...

Ubuntu Linux 文件、目录权限问题
本文为Ubuntu Linux操作系统- 第五弹 此文是在上期文件目录的内容操作基础上接着讲权限问题 上期回顾:Ubuntu Linux 目录和文件的内容操作 文件访问者身份与文件访问权限 Linux文件结构 所有者(属主)所属组(属组)其他…...

LabVIEW密码保护与反编译的安全性分析
在LabVIEW中,密码保护是一种常见的源代码保护手段,但其安全性并不高,尤其是在面对专业反编译工具时。理论上,所有软件的反编译都是可能的,尽管反编译不一定恢复完全的源代码,但足以提取程序的核心功能和算法…...

yolo11经验教训----之一
一、格式转换 可以把python中的.pt文件,导出为libtorch识别的格式: model YOLO("yolo11n.pt") model.export(format"torchscript") 二、查看结构 在c中,我用qt,这样做的: #include "…...

异步处理优化:多线程线程池与消息队列的选择与应用
目录 一、异步处理方式引入 (一)异步业务识别 (二)明确异步处理方式 二、多线程线程池(Thread Pool) (一)工作原理 (二)直面优缺点和适用场景 1.需要快…...

Hadoop生态圈框架部署 伪集群版(一)- Linux操作系统安装及配置
文章目录 前言一、下载CentOS镜像1. 下载 二、创建虚拟机hadoop三、CentOS安装与配置1. 安装CentOS2. 配置虚拟网络及虚拟网卡2.1 配置虚拟网络2.2 配置虚拟网卡 3. 安装 SSH 远程连接工具 FinalShell3.1 简介3.2 下载和安装3.2.1 下载3.2.2 安装 3.3 查看动态ip地址3.4 使用Fi…...

Go的Gin比java的Springboot更加的开箱即用?
前言 隔壁组的云计算零零后女同事,后文简称 云女士 ,非说 Go 的 Gin 框架比 Springboot 更加的开箱即用,我心想在 Java 里面 Springboot 已经打遍天下无敌手,这份底蕴岂是 Gin 能比。 但是云女士突出一个执拗,非我要…...

pickle常见Error解决
1. pickle OverflowError: cannot serialize a bytes object larger than 4 GiB 进行pickle.dump时出现上述错误,可以加上“protocol4”参数。依据:https://docs.python.org/3/library/pickle.html#data-stream-format 2. pickle EOFError: Ran out of…...

认识Java数据类型和变量
数据类型分类 基本数据类型(8个): 整数型 byte 8位 short 16位 int 32位 long 64位 默认整数类型是int类型 小数型/浮点型 float【单精度32位】 double【双进度64位】 字符型 char 16位 只能表示单个字符 布尔型 boolean 1位 只能有两个值 true 【真】 false 【…...

Qt开发技巧(二十四)滚动部件的滑动问题,Qt设置时区问题,自定义窗体样式不生效问题,编码格式问题,给按钮左边加个图,最小化后的卡死假象
继续记录一些Qt开发中的技巧操作: 1.滚动部件的滑动问题 再Linux嵌入式设备上,有时候一个页面的子部件太多,一屏放不下是需要做页面滑动,可以使用“QScrollArea”控件,拖来一个“QScrollArea”控件,将子部件…...

SHELL----正则表达式
一、文本搜索工具——grep grep -参数 条件 文件名 其中参数有以下: -i 忽略大小写 -c 统计匹配的行数 -v 取反,不显示匹配的行 -w 匹配单词 -E 等价于 egrep ,即启用扩展正则表达式 -n 显示行号 -rl 将指定目录内的文件打…...

44.5.【C语言】辨析“数组指针”和“指针数组”
目录 1.数组指针 2.指针数组 执行结果 底层分析 1.数组指针 从语文的角度理解,"数组"修饰"指针".因此数组指针是指针 例如以下代码 #include <stdio.h> int main() {char a[5] { "ABCDE" };return 0;} 其中a就是数组指针,因为数…...

node.js基础学习-express框架-路由及中间件(十)
一、前言 Express 是一个简洁、灵活的 Node.js Web 应用框架。它基于 Node.js 的内置 HTTP 模块构建,提供了一系列用于构建 Web 应用程序和 API 的功能,使开发者能够更高效地处理 HTTP 请求和响应,专注于业务逻辑的实现。 其特点包括简单易用…...

使用MSYS搭建linux开发环境踩坑笔记
前言: 使用linux系统或虚拟机进行嵌入式linux开发是常规方法; 使用MSYS是用于尝鲜和研究。 由于windows和linux的差异,使用MSYS代替Linux虚拟机会遇到很多坑。 主要原因在于: 1. windows和linux文件系统的差异:win不…...

vue3+ts+vite+ElementPlus上传进度条实时更新(UPLoad和progress)。
需求: 上传文件时,展示进度条实时更新: 下面是代码片段: <!-- 添加媒体弹窗 -- 上传 --><el-dialog v-model"centerDialogVisible" title"媒体信息" width"700" :close-on-click-modal"false&qu…...

AspNet WebAPI 模型绑定问题
继承System.Web.Http.ApiController的Action的Model如果被[Serializable]定义,会导致Model的字段无法绑定。 Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll [Serializable] public class Model {public string id { get; set; } }public MyA…...

Android 图形系统之七:SurfaceFlinger
一. 引言 什么是 SurfaceFlinger?SurfaceFlinger 的核心作用和地位?为什么需要了解 SurfaceFlinger? 二. SurfaceFlinger 的基本概念 Surface 和 SurfaceFlinger 的关系SurfaceFlinger 与图形渲染(OpenGL ES 和 Vulkan…...

14、鸿蒙学习——管理通知角标
针对未读的通知,系统提供了角标设置接口,将未读通知个数显示在桌面图标的右上角角标上。 通知增加时,角标上显示的未读通知个数需要增加。 通知被查看后,角标上显示的未读通知个数需要减少,没有未读通知时࿰…...

TongRDS分布式内存数据缓存中间件
命令 优势 支持高达10亿级的数据缓冲,内存优化管理,避免GC性能劣化。 高并发系统设计,可充分利用多CPU资源实现并行处理。 数据采用key-value多索引方式存储,字段类型和长度可配置。 支持多台服务并行运行,服务之间可互…...

[在线实验]-RabbitMQ镜像的下载与部署
镜像下载 docker的rabbitmq镜像资源-CSDN文库 加载镜像 docker load --input rabbitmq.tar 给镜像打标签 这里发现镜像名为none,需要给镜像重命名下 docker tag [镜像id] [新镜像名称]:[新镜像标签] docker tag ebaf409ffbe2 rabbitmq:management 运行镜像…...

Linux 系统文件描述符(File Descriptor)小白级介绍
1. 概述 Linux 遵循"一切皆文件"的理念。在 Linux 系统中,文件描述符是一个索引值(非负整数),指向内核为每个进程所维护的该进程打开文件的记录表。 如上所述,每个进程都维护着一张文件描述符表。 文件描述…...

【Verilog】实验二 数据选择器的设计与vivado集成开发环境
目录 一、实验目的 二、实验环境 三、实验任务 四、实验原理 五、实验步骤 top.v mux2_1.v 一、实验目的 1. 掌握数据选择器的工作原理和逻辑功能。 2. 熟悉vivado集成开发环境。 3. 熟悉vivado中进行开发设计的流程。 二、实验环境 1. 装有vivado的计算机。 2. Sw…...

IDL学习笔记(三)OMI数据处理。hdf5文件读取,图像反转,GeoTiff区别,月季年均值计算提取输出,单位转换,运行时间计算
modis Level 2 grid 数据是全球格网化数据。一天的数据全在其中。 modis Level 1 和 2 数据是一景一景的影像。 IDL学习笔记(三)OMI数据处理 hdf5文件读取单位转换,输出hdf5数据集的图像,并检查图像经纬度是否正确,若错…...

深入浅出:PHP中的数据类型全解析
文章目录 引言理解数据类型标量类型整数 (integer)浮点数 (float)布尔值 (boolean)字符串 (string) 复合类型数组 (array)对象 (object)资源 (resource)NULL 特殊类型Callable强制类型转换 实战案例总结与展望参考资料 引言 在编程的世界里,数据类型是构建任何应用…...

要使用 OpenResty 创建一个接口,返回客户端的 IP 地址,并以 JSON 格式输出
要使用 OpenResty 创建一个接口,返回客户端的 IP 地址,并以 JSON 格式输出 要使用 OpenResty 创建一个接口,返回客户端的 IP 地址,并以 JSON 格式输出方案一解决方案(openresty使用cjson)说明:使…...

智慧油客:从初识、再识OceanBase,到全栈上线
今天,我们邀请了智慧油客的研发总监黄普友,为我们讲述智慧油客与 OceanBase 初识、熟悉和结缘的故事。 智慧油客自2016年诞生以来,秉持新零售的思维,成功从过去二十年间以“以销售产品为中心”的传统思维模式,转向“以…...