【PostgreSQL创建索引的锁分析和使用注意】

1.1 创建普通B-tree索引的整体流程

如下是梳理的创建普通B-tree索引的大概流程,可供参考。

1.校验新索引的Catalog元数据

|语法解析  ---将创建索引的sql解析成IndexStmt结构
	|校验B-Tree的handler   -----校验内核是否支持该类型的索引,在pg_am中查找"btree"对应的handler
		|校验索引列及比较函数   ----查找pg_attribute,校验create index中指定的索引列是否存在,如果存在记录attno,并且根据atttypid(表示表中列的字段类型)在pg_opclass里查找对应的比较函数
		
2.在文件系统中创建索引文件

|生成oid  ---为新的索引文件生成唯一oid,过程是:生成一个新的oid,然后查找pg_class的索引,如果不存在就返回这个oid
	|更新本地的relcache   ---正在创建的relation添加到relcache
		|创建索引文件以及写xlog   ----文件系统中生成新的文件 base/xxx/xxx。xlog记录类型为XLogInsert(RM_SMGR_ID, XLOG_SMGR_CREATE | XLR_SPECIAL_REL_UPDATE)
			
3.创建新索引的元数据

|索引作为对象的元数据写入到pg_class 
	|索引文件引用的列插入pg_attribute
		|索引本身相关信息插入pg_index
			|relcache失效,invalid所有heap相关的元数据  ---为了使catalog元数据的变更对所有进程生效
				|记录该索引对heap的依赖,对opclass的依赖等等插入pg_depend
					|使得新索引文件相关的relcache生效
	
4.用函数btbuild构建B-tree索引

|通过index的索引列构建排序时需要用到的sortkey,扫描tuple生成索引元组数组
	| 构建B+树叶节点
		|对索引元组执行排序
			|将排序成功的结点依次插入到B-Tree中,自下向上构建B-Tree索引page --依次读取排好序的tuple,填充到B-Tree的叶子节点上,自下向上插入B-Tree
		

1.2 锁相关介绍

PostgreSQL里有很多可以加锁的对象:表、单个页、单个元组、事务ID(包括虚拟和永久ID)和普通数据库对象等等,常规锁的locktype主要有以下几种。有时候通过pg_locks查询的时候,根据pid会查到许多的锁,但是这些锁并不一定都是加在表上的,根据locktype以及relation过滤出不同对象上的锁。

/*
 * LOCKTAG is the key information needed to look up a LOCK item in the
 * lock hashtable.  A LOCKTAG value uniquely identifies a lockable object.
 *
 * The LockTagType enum defines the different kinds of objects we can lock.
 * We can handle up to 256 different LockTagTypes.
 */
typedef enum LockTagType
{
	LOCKTAG_RELATION,			/* whole relation */
	LOCKTAG_RELATION_EXTEND,	/* the right to extend a relation */
	LOCKTAG_DATABASE_FROZEN_IDS,	/* pg_database.datfrozenxid */
	LOCKTAG_PAGE,				/* one page of a relation */
	LOCKTAG_TUPLE,				/* one physical tuple */
	LOCKTAG_TRANSACTION,		/* transaction (for waiting for xact done) */
	LOCKTAG_VIRTUALTRANSACTION, /* virtual transaction (ditto) */
	LOCKTAG_SPECULATIVE_TOKEN,	/* speculative insertion Xid and token */
	LOCKTAG_OBJECT,				/* non-relation database object */
	LOCKTAG_USERLOCK,			/* reserved for old contrib/userlock code */
	LOCKTAG_ADVISORY			/* advisory user locks */
} LockTagType;

image20240104174056577.png

如下是PostgreSQL里的常规锁,其中AccessShareLock、RowShareLock、RowExclusiveLock属于弱锁,ShareLock、ShareRowExclusiveLock、ExclusiveLock 、AccessExclusiveLock属于强锁。

/* NoLock is not a lock mode, but a flag value meaning "don't get a lock" */
#define NoLock 0
#define AccessShareLock 1 /* SELECT */
#define RowShareLock 2 /* SELECT FOR UPDATE/FOR SHARE */
#define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE */
#define ShareUpdateExclusiveLock 4 /* VACUUM (non-FULL),ANALYZE, CREATE * INDEX CONCURRENTLY */
#define ShareLock 5 /* CREATE INDEX (WITHOUT CONCURRENTLY) */
#define ShareRowExclusiveLock 6 /* like EXCLUSIVE MODE, but allows ROW * SHARE */
#define ExclusiveLock 7 /* blocks ROW SHARE/SELECT...FOR * UPDATE */
#define AccessExclusiveLock 8 /* ALTER TABLE, DROP TABLE, VACUUM * FULL, and unqualified LOCK TABLE */

1.3 创建普通索引表上需要获取的锁(ShareLock)

使用如下的语句进行测试

postgres=# create table tab_test_1(id int);
CREATE TABLE
postgres=# insert into tab_test_1 values(1);
INSERT 0 1
postgres=# insert into tab_test_1 values(2);
INSERT 0 1
postgres=# insert into tab_test_1 values(3);
INSERT 0 1
postgres=# begin;
BEGIN
postgres=*# create index idx_1 on tab_test_1(id);
CREATE INDEX
postgres=*#

普通创建索引获取ShareLock,是5级锁,这里通过SQL可以查询到,其实在创建索引的过程中,不仅原始的表上会申请锁。对应的几张系统表和系统表索引上也会申请锁,因为创建索引也涉及到系统表元数据的校验和更改,这里访问pg_namespace的意义在于:索引将与其父表位于同一命名空间中。

当有多个事务同时对表进行读操作时,如果不做任何锁定,会导致数据不一致。例如,一个事务在读取数据时,另一个事务做了修改,然后第一个事务读到的数据已经不是最新的。这时候就需要使用AccessShareLock来控制并发的读取操作,保持数据的一致性。

postgres=# select l.locktype,ns.nspname,a.relname,a.relkind,l.pid,l.mode,l.granted,p.query_start,p.query,p.state from pg_locks l,pg_stat_activity p,pg_class a,pg_namespace ns where l.locktype='relation' and l.pid=p.pid and query not like '%pg_stat_activity%' and l.relation=a.oid and a.relnamespace=ns.oid;
 locktype |  nspname   |              relname              | relkind |  pid   |      mode       | granted |          query_start          |
      query                 |        state
----------+------------+-----------------------------------+---------+--------+-----------------+---------+-------------------------------+-----------
----------------------------+---------------------
 relation | public     | tab_test_1                        | r       | 409706 | ShareLock       | t       | 2024-01-04 11:48:11.807816+08 | create index idx_1 on tab_test_1(id); | idle in transaction
 relation | pg_catalog | pg_class                          | r       | 409706 | AccessShareLock | t       | 2024-01-04 11:48:11.807816+08 | create index idx_1 on tab_test_1(id); | idle in transaction
 relation | pg_catalog | pg_namespace                      | r       | 409706 | AccessShareLock | t       | 2024-01-04 11:48:11.807816+08 | create index idx_1 on tab_test_1(id); | idle in transaction
 relation | pg_catalog | pg_namespace_nspname_index        | i       | 409706 | AccessShareLock | t       | 2024-01-04 11:48:11.807816+08 | create index idx_1 on tab_test_1(id); | idle in transaction
 relation | pg_catalog | pg_namespace_oid_index            | i       | 409706 | AccessShareLock | t       | 2024-01-04 11:48:11.807816+08 | create index idx_1 on tab_test_1(id); | idle in transaction
 relation | pg_catalog | pg_class_oid_index                | i       | 409706 | AccessShareLock | t       | 2024-01-04 11:48:11.807816+08 | create index idx_1 on tab_test_1(id); | idle in transaction
 relation | pg_catalog | pg_class_relname_nsp_index        | i       | 409706 | AccessShareLock | t       | 2024-01-04 11:48:11.807816+08 | create index idx_1 on tab_test_1(id); | idle in transaction
 relation | pg_catalog | pg_class_tblspc_relfilenode_index | i       | 409706 | AccessShareLock | t       | 2024-01-04 11:48:11.807816+08 | create index idx_1 on tab_test_1(id); | idle in transaction
(8 rows)

1.4 关于ShareLock的其他场景

在源码的注释部分,可以看到CREATE INDEX (WITHOUT CONCURRENTLY) 的时候会在表上申请ShareLock,这和我们的测试结果相符合,但是ShareLock不仅仅创建索引的时候会获取,当多个事务更新同一行的时候,也会申请ShareLock,不过这个ShareLock不是在表级别申请的,而是在分配事务ID时对这个事务ID进行加锁, 用于元组并发更新时做事务等待。

分别用两个session做如下操作:

//session 1,pid:434865

postgres=# begin;
BEGIN
postgres=*# update tab_test_1 set id=7 where id=1;
UPDATE 1
postgres=*#

//session 2,pid:433290

postgres=# begin;
BEGIN
postgres=*# update tab_test_1 set id=7 where id=1;

然后用另一个session查询锁的状态

postgres=# select * from pg_locks where pid='434865';
 locktype    | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  pid   |       mode
    | granted | fastpath | waitstart
---------------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+--------+--------------
----+---------+----------+-----------
 relation      |    13008 |    16725 |      |       |            |               |         |       |          | 3/16               | 434865 | RowExclusiveL
ock | t       | t        |
 virtualxid    |          |          |      |       | 3/16       |               |         |       |          | 3/16               | 434865 | ExclusiveLock
    | t       | t        |
 transactionid |          |          |      |       |            |          1699 |         |       |          | 3/16               | 434865 | ExclusiveLock
    | t       | f        |
(3 rows)

postgres=# select * from pg_locks where pid='433290';
   locktype    | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  pid   |       mode
    | granted | fastpath |           waitstart
---------------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+--------+--------------
----+---------+----------+-------------------------------
 relation      |    13008 |    16725 |      |       |            |               |         |       |          | 4/3                | 433290 | RowExclusiveLock | t       | t        |
 virtualxid    |          |          |      |       | 4/3        |               |         |       |          | 4/3                | 433290 | ExclusiveLock
    | t       | t        |
 transactionid |          |          |      |       |            |          1700 |         |       |          | 4/3                | 433290 | ExclusiveLock
    | t       | f        |
 tuple         |    13008 |    16725 |    0 |     1 |            |               |         |       |          | 4/3                | 433290 | ExclusiveLock
    | t       | f        |
 transactionid |          |          |      |       |            |          1699 |         |       |          | 4/3                | 433290 | ShareLock
    | f       | f        | 2024-01-04 13:33:16.667889+08
(5 rows)

可以通过上述测试看到,pid为433290的这个session,它的pg_locks的最后一行 的granted为’f’,说明该进程被阻塞,并且是在申请类型为tansactionid的锁时被阻塞了,对应tansactionid=1699的事务。从表上可以看出这个tansactionid已经被进程pid为434865的session1会话持有了。

行锁的阻塞信息是通过tansactionid类型的锁体现的,行锁是会在数据行上加自己的tansactionid的,另一个进程读到这一行时,如果发现上一个操作该行的事务未结束,会把上一个事务的tansactionid读出来,然后申请在这个tansactionid上加上ShareLock,等待上一个事务结束,再获得ExclusiveLock。而持有行锁的进程已经在此tansactionid上加了ExclusiveLock,所以后面要更新这行的进程会被阻塞。

ShareLock即读锁,ExclusiveLock即写锁。对事务ID加ShareLock是为了事务不提交,其他人看不到修改后的行,而ExclusiveLock是防止并发操作的。

1.5 创建普通索引时阻塞的一些操作

如下是对创建普通索引申请的ShareLock后的一些阻塞情况

1.5.1 建普通索引阻塞DML操作
//开一个session,开启事务创建索引,然后不提交

postgres=# begin;
BEGIN
postgres=*# create index idx_1 on tab_test_1(id);
CREATE INDEX
postgres=*#

//新开一个session,查询这张表,可以正常访问,不阻塞读
postgres=# select * from tab_test_1 ;
 id
----
  1
  2
  3
(3 rows)

//新开多个session,分别做dml操作
//session A:
postgres=# insert into tab_test_1 values(6);
//session B:
postgres=# update tab_test_1 set id=7 where id=1;
//session C:
postgres=# delete from tab_test_1 where id=1;

//另开一个session,查看获取的锁

postgres=#  select l.locktype,ns.nspname,a.relname,a.relkind,l.pid,l.mode,l.granted,p.query_start,p.query,p.state from pg_locks l,pg_stat_activity p,pg_class a,pg_namespace ns where l.locktype='relation' and l.pid=p.pid and query not like '%pg_stat_activity%' and l.relation=a.oid and a.relnamespace=ns.oid and a.relname='tab_test_1';
 locktype | nspname |  relname   | relkind |  pid   |       mode       | granted |          query_start          |                 query                  |
        state
----------+---------+------------+---------+--------+------------------+---------+-------------------------------+----------------------------------------+
---------------------
 relation | public  | tab_test_1 | r       | 409706 | ShareLock        | t       | 2024-01-04 11:48:11.807816+08 | create index idx_1 on tab_test_1(id);  |
 idle in transaction
 relation | public  | tab_test_1 | r       | 410009 | RowExclusiveLock | f       | 2024-01-04 12:48:34.640655+08 | insert into tab_test_1 values(6);      |
 active
 relation | public  | tab_test_1 | r       | 420570 | RowExclusiveLock | f       | 2024-01-04 12:48:37.450632+08 | update tab_test_1 set id=7 where id=1; |
 active
 relation | public  | tab_test_1 | r       | 420581 | RowExclusiveLock | f       | 2024-01-04 12:48:39.828598+08 | delete from tab_test_1 where id=1;     |
 active
(4 rows)

//以及查看阻塞源,可以看到表上的dml操作都是被create index 给阻塞了。

  pid   | blocked_by |  state  |       wait        | wait_age |  tx_age  | xid_age |   xmin_ttf    | datname  | usename  | blkd |                       que
ry
--------+------------+---------+-------------------+----------+----------+---------+---------------+----------+----------+------+--------------------------
--------------------------
 409706 | {}         | idletx  | Client:ClientRead |          | 01:02:44 | 5       |               | postgres | postgres |    3 |  [409706] create index idx_1 on tab_test_1(id);
 410009 | {409706}   | waiting | Lock:relation     | 00:01:57 | 00:01:57 |         | 2,147,483,642 | postgres | postgres |    0 |  [410009] . insert into tab_test_1 values(6);
 420570 | {409706}   | waiting | Lock:relation     | 00:01:54 | 00:01:54 |         | 2,147,483,642 | postgres | postgres |    0 |  [420570] . update tab_test_1 set id=7 where id=1;
 420581 | {409706}   | waiting | Lock:relation     | 00:01:51 | 00:01:51 |         | 2,147,483,642 | postgres | postgres |    0 |  [420581] . delete from tab_test_1 where id=1;
(4 rows)
1.5.2 建普通索引阻塞DDL操作
//开一个session,开启事务创建索引,然后不提交

postgres=# begin;
BEGIN
postgres=*# create index idx_1 on tab_test_1(id);
CREATE INDEX
postgres=*#

//新开一个session,执行DDL操作,alter table加列

postgres=# alter table tab_test_1 add column name varchar(20);

 //另开一个session,查看获取的锁
postgres=#  select l.locktype,ns.nspname,a.relname,a.relkind,l.pid,l.mode,l.granted,p.query_start,p.query,p.state from pg_locks l,pg_stat_activity p,pg_class a,pg_namespace ns where l.locktype='relation' and l.pid=p.pid and query not like '%pg_stat_activity%' and l.relation=a.oid and a.relnamespace=ns.oid and a.relname='tab_test_1';
 locktype | nspname |  relname   | relkind |  pid   |        mode         | granted |          query_start          |                        query
               |        state
----------+---------+------------+---------+--------+---------------------+---------+-------------------------------+--------------------------------------
---------------+---------------------
 relation | public  | tab_test_1 | r       | 409706 | ShareLock           | t       | 2024-01-04 11:48:11.807816+08 | create index idx_1 on tab_test_1(id);
               | idle in transaction
 relation | public  | tab_test_1 | r       | 422658 | AccessExclusiveLock | f       | 2024-01-04 12:51:52.948432+08 | alter table tab_test_1 add column nam
e varchar(20); | active
(2 rows)

//查看阻塞源,可以看到表上alter table加列的DDL操作被create index给阻塞了。

 pid   | blocked_by |  state  |       wait        | wait_age |  tx_age  | xid_age |   xmin_ttf    | datname  | usename  | blkd |
    query
--------+------------+---------+-------------------+----------+----------+---------+---------------+----------+----------+------+--------------------------
---------------------------------------
 409706 | {}         | idletx  | Client:ClientRead |          | 01:04:09 | 6       |               | postgres | postgres |    1 |  [409706] create index idx_1 on tab_test_1(id);
 422658 | {409706}   | waiting | Lock:relation     | 00:00:03 | 00:00:03 | 1       | 2,147,483,641 | postgres | postgres |    0 |  [422658] . alter table tab_test_1 add column name varchar(20);
(2 rows)


//新开一个session,执行DDL操作,drop table删除表
postgres=# drop table tab_test_1;

//另开一个session,查看获取的锁

postgres=#  select l.locktype,ns.nspname,a.relname,a.relkind,l.pid,l.mode,l.granted,p.query_start,p.query,p.state from pg_locks l,pg_stat_activity p,pg_class a,pg_namespace ns where l.locktype='relation' and l.pid=p.pid and query not like '%pg_stat_activity%' and l.relation=a.oid and a.relnamespace=ns.oid and a.relname='tab_test_1';
 locktype | nspname |  relname   | relkind |  pid   |        mode         | granted |          query_start          |                 query
 |        state
----------+---------+------------+---------+--------+---------------------+---------+-------------------------------+--------------------------------------
-+---------------------
 relation | public  | tab_test_1 | r       | 409706 | ShareLock           | t       | 2024-01-04 11:48:11.807816+08 | create index idx_1 on tab_test_1(id);
 | idle in transaction
 relation | public  | tab_test_1 | r       | 422669 | AccessExclusiveLock | f       | 2024-01-04 12:53:17.664053+08 | drop table tab_test_1;
 | active
(2 rows)

//查看阻塞源,可以看到表上drop table删除表的DDL操作被create index给阻塞了。

  pid   | blocked_by |  state  |       wait        | wait_age |  tx_age  | xid_age |   xmin_ttf    | datname  | usename  | blkd |                      quer
y
--------+------------+---------+-------------------+----------+----------+---------+---------------+----------+----------+------+--------------------------
-----------------------
 409706 | {}         | idletx  | Client:ClientRead |          | 01:05:43 | 7       |               | postgres | postgres |    1 |  [409706] create index idx_1 on tab_test_1(id);
 422669 | {409706}   | waiting | Lock:relation     | 00:00:12 | 00:00:12 | 1       | 2,147,483,640 | postgres | postgres |    0 |  [422669] . drop table tab_test_1;
(2 rows)
1.5.3 阻塞vacuum,vacuum full,analyze
//开一个session,开启事务创建索引,然后不提交

postgres=# begin;
BEGIN
postgres=*# create index idx_1 on tab_test_1(id);
CREATE INDEX
postgres=*#

//新开一个session,vacuum该表

postgres=# vacuum tab_test_1;

 //另开一个session,查看获取的锁

postgres=# select l.locktype,ns.nspname,a.relname,a.relkind,l.pid,l.mode,l.granted,p.query_start,p.query,p.state from pg_locks l,pg_stat_activity p,pg_class a,pg_namespace ns where l.locktype='relation' and l.pid=p.pid and query not like '%pg_stat_activity%' and l.relation=a.oid and a.relnamespace=ns.oid;
 locktype | nspname |  relname   | relkind |  pid   |           mode           | granted |          query_start          |                 query
      |        state
----------+---------+------------+---------+--------+--------------------------+---------+-------------------------------+---------------------------------
------+---------------------
 relation | public  | tab_test_1 | r       | 452619 | ShareLock                | t       | 2024-01-04 14:50:00.524497+08 | create index idx_1 on tab_test_1
(id); | idle in transaction
 relation | public  | tab_test_1 | r       | 452585 | ShareUpdateExclusiveLock | f       | 2024-01-04 14:50:35.244644+08 | vacuum tab_test_1 ;
      | active
(2 rows)

//查看阻塞源,可以看到表vacuum操作被create index给阻塞了。

pid   | blocked_by |  state  |       wait        | wait_age |  tx_age  | xid_age |   xmin_ttf    | datname  | usename  | blkd |                      quer
y
--------+------------+---------+-------------------+----------+----------+---------+---------------+----------+----------+------+--------------------------
-----------------------
 452619 | {}         | idletx  | Client:ClientRead |          | 00:00:40 | 1       |               | postgres | postgres |    1 |  [452619] create index id
x_1 on tab_test_1(id);
 452585 | {452619}   | waiting | Lock:relation     | 00:00:02 | 00:00:02 |         | 2,147,483,646 | postgres | postgres |    0 |  [452585] . vacuum tab_te
st_1 ;
(2 rows)

可以看到vacuum改表被create index阻塞了,vacuum full,analyze测试方法类似,也是一样被阻塞了。

//阻塞vacuum full

  pid   | blocked_by |  state  |       wait        | wait_age |  tx_age  | xid_age |   xmin_ttf    | datname  | usename  | blkd |                      quer
y
--------+------------+---------+-------------------+----------+----------+---------+---------------+----------+----------+------+--------------------------
-----------------------
 452619 | {}         | idletx  | Client:ClientRead |          | 00:12:38 | 2       |               | postgres | postgres |    1 |  [452619] create index id
x_1 on tab_test_1(id);
 452585 | {452619}   | waiting | Lock:relation     | 00:00:07 | 00:00:07 | 1       | 2,147,483,645 | postgres | postgres |    0 |  [452585] . vacuum full t
ab_test_1 ;
(2 rows)

//阻塞analyze

  pid   | blocked_by |  state  |       wait        | wait_age |  tx_age  | xid_age |   xmin_ttf    | datname  | usename  | blkd |                      quer
y
--------+------------+---------+-------------------+----------+----------+---------+---------------+----------+----------+------+--------------------------
-----------------------
 452619 | {}         | idletx  | Client:ClientRead |          | 00:14:04 | 2       |               | postgres | postgres |    1 |  [452619] create index id
x_1 on tab_test_1(id);
 452585 | {452619}   | waiting | Lock:relation     | 00:00:07 | 00:00:07 |         | 2,147,483,645 | postgres | postgres |    0 |  [452585] . analyze tab_t
est_1;
(2 rows)

1.6 创建普通B-tree索引的可能遇到的问题

问题:

​ 创建普通B-tree索引在表上申请的是ShareLock,ShareLock和RowExclusiveLock是冲突的,所以create index会等待表上所有的DML(增删改)结束。但是实际在生产环境下,如果业务不停运行,涉及到要加索引的表不停的有DML操作,那么,执行了create index操作后,可能会发现长时间都没有反应,因为create index操作可能长时间获取不到锁,然后一直处于锁的等待队列里。

​ 就算能获取到了锁,可以创建索引了,但是在创建索引期间,也会阻塞所有的DML(增删改),如果需要加索引的表是一个大表,并且需要加索引的这列数据也比较复杂,那么可能执行时间比较长,那么对DML的阻塞时间也会比较长,这对于某些业务场景可能是不能接受的。除了阻塞时间长一个问题外,如果建索引期间,业务较多,被阻塞的DML大量累积,有可能导致pg_locks里累积的越来越多,最后导致OOM。

解决方法:

(1)对于建索引的操作尽量选择业务量较少的时候执行,或者有条件在停业务的窗口内完成。

(2)设置lock_timeout,不让其长时间的获取锁,阻塞业务。

(3)可以使用create index concurrently 在线创建索引(CIC),降低了创建索引在表上申请的锁的级别,ShareUpdateExclusiveLock级别的锁和RowExclusiveLock不冲突,不会阻塞DML操作。

创建索引慢:
至于常见的创建索引慢的原因也可以参考我的这篇文章常见的创建索引慢的原因

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

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

相关文章

2024年游泳耳机十大品牌排行榜,游泳耳机哪个牌子好

游泳耳机市场正呈现蓬勃发展的趋势,而在众多品牌中选择一款适合自己的游泳耳机变得愈发重要。游泳不仅是锻炼身体的有效方式,还是缓解压力的良好途径。然而,游泳时的单调可能成为一些人的困扰,特别是那些希望在运动中欣赏音乐或聆…

二叉树的层序遍历经典问题(算法村第六关白银挑战)

基本的层序遍历与变换 二叉树的层序遍历 102. 二叉树的层序遍历 - 力扣(LeetCode) 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 示例 1: 输入…

25 心形按钮

效果演示 实现了一个心形的心形图案&#xff0c;当用户点击图案时&#xff0c;图案会旋转并缩小&#xff0c;同时背景颜色会变成白色。 Code <div class"love"><input id"switch" type"checkbox"><label class"love-heart&…

*5.1 Global Memory Bandwidth

并行程序的执行速度可能因计算硬件的资源限制而有很大差异。虽然管理并行代码和硬件资源约束之间的交互对于在几乎所有并行编程模型中实现高性能很重要&#xff0c;但这是一种实用技能&#xff0c;最好通过为高性能设计的并行编程模型中的实践练习来学习。在本章中&#xff0c;…

xss-labs(1-5)

环境准备: 靶场下载:下载仓库 zhangmanhui/xss-labs - Gitee.com 启动phpStudy 搭建将文件解压拉到phpStudy的www目录下就行 最后直接访问:127.0.0.1/xss-labs-master/ 最后再准备一个浏览器的插件用来发送请求:HackBar 插件都配置好了,直接加载到你的浏览器的扩展…

以unity技术开发视角对android权限的讲解

目录 前言 Android权限分类 普通权限 普通权限定义 普通权限有哪些 危险权限 危险权限的定义 危险权限有哪些 动态申请权限实例 申请单个权限实例 第一步&#xff1a;在清单文件中声明权限 第二步&#xff1a;在代码中进行动态申请权限 申请多个权限实例 第一步&am…

【win11 绕过TPM CPU硬件限制安装】

Qt编程指南 VX&#xff1a;hao541022348 ■ 下载iso文件■ 右键文件点击装载出现如下问题■ 绕过TPM CPU硬件限制安装方法■ 虚拟机安装win11 ■ 下载iso文件 选择Windows11 &#xff08;multi-edition ISO&#xff09;在选择中文 ■ 右键文件点击装载出现如下问题 ■ 绕过T…

希尔顿花园酒店喜迎入华十周年里程碑

【2024年1月8日&#xff0c;中国&#xff0c;上海】作为希尔顿集团旗下标志性高端精选服务酒店品牌&#xff0c;希尔顿花园酒店于今年正式迎来其在华经营十周年的里程碑。自2014年中国首家希尔顿花园酒店在深圳开业以来&#xff0c;中国市场已经成为希尔顿花园酒店全球增长的重…

C++中的返回值优化(RVO)

一、命名返回值优化&#xff08;NRVO&#xff09; 是Visual C2005及之后版本支持的优化。 具体来说&#xff0c;就是一个函数的返回值如果是一个对象。那么&#xff0c;正常的返回语句的执行过程是&#xff0c;把这个对象从当前函数的局部作用域&#xff0c;或者叫当前函数的…

2024第15届电子教育、电子商务、电子管理和电子学习国际会议

第十五届电子教育、电子商务、电子管理和电子学习国际会议&#xff08;IC4E 2024&#xff09;将于2024年3月18日-21日在日本福冈举办。本次会议以电子技术为核心&#xff0c;围绕电子教育、电子商务、电子管理以及电子学习等各个方面展开研讨&#xff0c;为相关领域的专家学者们…

【Spring 篇】深入浅出:用Spring注解开发的奇妙之旅

在编程的世界里&#xff0c;Spring框架如同一位慈祥的导师&#xff0c;为我们打开了无尽可能性的大门。而在Spring的广袤领域中&#xff0c;注解是我们最亲密的伙伴之一。本篇博客将深入浅出地介绍使用Spring注解进行开发的奇妙之旅&#xff0c;为你解开注解的神秘面纱。 前奏…

2024年远控软件年度盘点:安全、稳定、功能之选

这目录 前言2024年热门远控软件ToDesk向日葵TeamViewerAnyDeskSplashtopAirDroidChrome Remote DesktopMicrosoft远程桌面RayLinkParallels Access 远程控制软件如何选择&#xff1f;1、功能性2、安全性3、易用性4、稳定性 未来展望与建议结语 前言 随着信息技术不断发展&…

使用命令行方式搭建uni-app + Vue3 + Typescript + Pinia + Vite + Tailwind CSS + uv-ui开发脚手架

使用命令行方式搭建uni-app Vue3 Typescript Pinia Vite Tailwind CSS uv-ui开发脚手架 项目代码以上传至码云&#xff0c;项目地址&#xff1a;https://gitee.com/breezefaith/uniapp-vue3-ts-scaffold 文章目录 使用命令行方式搭建uni-app Vue3 Typescript Pinia V…

移动端对大批量图片加载的优化方法(一)

移动端对大批量图片加载的优化方法&#xff08;一&#xff09;iOS 本篇主要从iOS开发中可以使用到的对大批量图片加载的优化方法进行整理。 1.异步加载 将图片加载任务放在后台线程中进行&#xff0c;避免阻塞主线程&#xff0c;这样可以保证应用的响应性和流畅性&#xff1…

视频剪辑方法:智能转码从视频到图片序列,高效转换攻略

在视频编辑和后期处理中&#xff0c;经常要将视频转换为图片序列&#xff0c;以便进行单独编辑或应用。下面一起来看云炫AI智剪如何批量智能转码的方法&#xff0c;高效地将视频转换为图片序列。 视频转为序列图片缩略图效果 视频转为序列图片的效果图&#xff0c;画面清晰&a…

vue3 img图片怎么渲染

在 Vue3 中加载图片&#xff08;img&#xff09;src地址时&#xff0c;出现无法加载问题。网上很多都建议使用 require 加载相对路径&#xff0c;如下&#xff1a; <img :src"require(../assets/img/icon.jpg)"/>但是按照这种方式加载又会报错如下&#xff1a;…

Access、Trunk、Hybrid接口接收发送数据帧标签剥离区分

以太网二层接口类型 Access Trunk Hybrid 总结&#xff1a; VLAN原理最全最详细讲解&#xff01;彻底搞懂VLAN打和摘tag过程

亚马逊鲲鹏自动测评系统:提升店铺流量与销售的利器

在跨境电商领域&#xff0c;提升店铺流量、排名以及销售业绩一直是卖家们关注的焦点。近期&#xff0c;亚马逊鲲鹏自动测评系统的推出备受关注&#xff0c;成为卖家们提升竞争力的得力工具。据真实客户反馈&#xff0c;该系统不仅能够全自动化批量操作&#xff0c;而且内置了防…

.NET学习教程一——.net基础定义+VS常用设置

一、定义 .NET分为.NET平台和.NET框架。 .NET平台&#xff08;厨房&#xff09;.NET FrameWork 框架&#xff08;柴米油盐酱醋茶&#xff09; .NET平台&#xff08;中国移动联通平台&#xff09;.NET FrameWork 框架&#xff08;信号塔&#xff09; .NET平台基于.NET Fra…