技术面:MySQL篇(InnoDB的锁机制)

news/2026/1/20 20:33:15/文章来源:https://www.cnblogs.com/jimoer/p/19508695

MySQL的InnoDB的锁机制

MySQL的InnoDB引擎下,在锁的级别上一般分为两种:共享锁(S锁)排他锁(X锁)

共享锁

共享锁又称为读锁是读取操作时创建的锁。其他用户可以并发读取数据,但是一旦某行被加上共享锁,其他事务仍可继续对该行加共享锁;但任何事务若想对该行加排他锁(即执行 UPDATE/DELETE 等),则必须等待所有共享锁释放

例如:事务T1对数据A的加上共享锁,其他事务只能对数据A加共享锁,不能加排他锁。而共享锁的事务只能读取数据,不能修改数据。

共享锁的加锁方式如下:

-- MySQL8.0之前的推荐写法
SELECT ID,NAME FROM TABLE1 WHERE ID >1 AND ID <10 LOCK IN SHARE MODE;-- MySQL8.0以及之后的推荐写法
SELECT ID,NAME FROM TABLE1 WHERE ID >1 AND ID <10 FOR SHARE;

在查询语句后面增加LOCK IN SHARE MODE,会对查询范围中的每行都加共享锁,这样的数据行还可以被其他事务成功申请共享锁,但是不能被申请排他锁。

排他锁

排他锁又称写锁,若事务T1对数据A加上排他锁之后,其他事务则不能再对数据A加任何类型的锁。而且排他锁的事务既可以读数据又可以写数据。

排他锁的加锁方式:

SELECT ID,NAME FROM TABLE1 WHERE ID >1 AND ID <10 FOR UPDATE;

在查询语句后面增加FOR UPDATE,会对查询命中的每条记录都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。

共享锁和排他锁的总结

  • 当一行数据获取了排他锁,那么其他事务就不能再对这一行数据添加共享锁或者排他锁
  • 当一行数据获取了共享锁,那么其他事务依然可以对这一行数据添加共享锁,但不能添加排他锁

使用场景

共享锁

  • 读-读并发高且不允许“脏读”:例如报表统计时,希望别的事务可以并发读,但禁止任何事务修改这些行。
  • 父子表一致性校验:先对父表主键 FOR SHARE,再读子表,防止父记录在此期间被删。

排他锁

  • 读-改-写(Read-Modify-Write):
SELECT balance FROM account WHERE id = 1 FOR UPDATE;
UPDATE account SET balance = balance - 100 WHERE id = 1;

防止并发扣款出现负余额。

  • 悲观锁实现“秒杀库存”:先 FOR UPDATE 检查库存 > 0,再 UPDATE 减库存。
-- 加排他锁并读取当前库存
SELECT stock FROM merchandise WHERE id = 123 FOR UPDATE;-- 在应用层判断返回的 stock 值,若 stock > 0,则继续;否则回滚并返回“已售罄”
-- 真正扣库存(MySQL 8 支持原子写法,也可拆两步)
UPDATE merchandise SET stock = stock - 1 WHERE id = 123 AND stock > 0;

意向锁

除了S锁(共享锁)和X锁(排他锁)之外,InnoDB还有两种锁,就是IS锁和IX锁,S和X前面的I是Intention的意思,即意向锁,IS就是意向共享锁,IX就是意向排他锁。

在MySQL的InnoDB引擎中,根据锁的不同范围也是有区分的,例如:表级锁、间隙锁、行级锁等。当多个事务同时访问同一个数据时,多个事务同时申请获取锁,那么就有可能导致互相阻塞甚至产生死锁。

例如:

事务T1对表Table1中的一行加上了行级锁,此时这行记录就不能被其他事务写了。
事务T2申请对Table1增加了表级锁,若申请成功了,那么就可以修改表中的任意一行记录。
这就跟事务T1发生了冲突。
那么,想要解决这个问题,就需要让事务T2在对Table1增加表级锁的时候,先判断一下是不是有事务增加过行级锁。但是,事务T2总不能逐条判断是否有加锁吧?

image

因此,为了解决这个问题,MySQL引入了意向锁机制,意向锁是数据库管理系统中用于实现锁协议的一种锁机制,主要是用来处理不同粒度锁(如行锁和表锁)之间的并发性问题(而同粒度的锁之间一般是通过互斥来解决并发的)

  • 意向锁不做锁定资源的操作,主要是通知的作用,防止在已经加锁的数据上设置不兼容的锁。
  • 意向锁不是直接由用户请求的,而是MySQL管理的。

当一个事务想获取一个行级锁或表级锁时,MySQL会自动获取相应的表的意向锁。这时,其他事务再请求获取表锁时,就可以先基于这个意向锁来发现是否已经加过锁并且未释放,并根据该锁的类型(意向共享锁/意向排他锁)来判断自己是否可以获取锁。这样可以在不阻塞其他事务的情况下,为当前事务锁定资源。

举个🌰:

image

1、事务 T1 执行

SELECT * FROM table1 WHERE id=1 FOR UPDATE;

InnoDB 先对表 table1 加 IX,再对 id=1 的行加 X 锁。

2、此时事务 T2 想 申请表锁

LOCK TABLES table1 WRITE;

需等待,因为检测到表 table1 上已有 IX,知道“table1里面某行正被排他锁住”,于是阻塞。
3、事务T1提交成功,释放table1的IX锁,这个时候T2被唤醒成功获取table1的IX锁。

从这个例子中可以看出来,意向锁是个表级锁,并且会在触发意向锁的事务提交或回滚后释放。

意向锁有两种:意向共享锁意向排他锁

  • 意向共享锁(IS):表示事务打算在资源上设置共享锁(读锁)。这通常用于表示事务计划读取资源,并不希望在读取时有其他事务设置排它锁。

  • 意向排它锁(IX):表示事务打算在资源上设置排它锁(写锁)。这表示事务计划修改资源,并不希望有其他事务同时设置共享或排它锁。

这两种意向锁的兼容情况如下:

已持有 \ 请求 IS(意向共享锁) IX(意向排他锁) S(表级共享) X(表级排他)
IS(意向共享锁) 兼容 兼容 兼容 冲突
IX(意向排他锁) 兼容 兼容 兼容 冲突
S(表级共享) 兼容 冲突 兼容 冲突
X(表级排他) 冲突 冲突 冲突 冲突

记录锁(Record Lock)

Record Lock 解释为记录锁,是因为它只对索引记录加锁。
例如:

SELECT * FROM users WHERE id = 5 FOR UPDATE;

上面这个SQL会对 id = 5这条记录加锁,主要就是为了防止其他事务,对id =5这条记录进行插入、更新、删除等操作。

Record Lock 记录锁可以是共享锁(S锁)排他锁(X锁)

有一点需要注意的是,Record Lock锁的不是这行记录,而是锁索引记录。并且Record lock锁且只锁索引!
如果没有索引怎么办?对于这种情况,InnoDB会创建一个隐藏的聚簇索引,并使用这个索引进行记录锁定。
如果我们在一张表中没有定义主键,那么,MySQL会默认选择一个唯一的非空索引作为聚簇索引。如果没有适合的非空唯一索引,则会创建一个隐藏的主键(row_id)作为聚簇索引。

image

Gap Lock(间隙锁)

Gap Lock,间隙锁,是在索引记录之间的间隙上的锁,或者在第一个索引记录之前、最后一个索引记录之后的间隙上加锁。

Gap Lock 锁住的是索引记录之间的开区间,不包含记录本身。

Gap也可以理解为InnoDB的索引数据结构中可以插入新值的位置。

image

示例:

SELECT * FROM users WHERE id = 7 FOR UPDATE;

由于索引记录不存在,加Gap Lock:区间(5,10)。这样就能防止其他事务在间隙中插入新记录。

Next Key Lock (临键锁)

Next-Key Lock是索引记录上的记录锁和索引记录之前间隙上的间隙锁的组合。
image

工作示例:

SELECT * FROM users WHERE age = 10 FOR UPDATE;

如果上面这个SQL中users表的age为普通索引,那么执行这个SQL的时候,会添加Next-Key Lock,并锁住(5,10]这个范围,左开右闭。主要是对age索引:(5,10]的Next-Key Lock,对主键索引:id=10的记录锁

加锁规则与时机

加锁基本原则:

  1. 所有锁都是加在索引上的。
  2. 加锁的基本单位是 Next-Key Lock。
  3. 查询过程中访问到的对象都会加锁。
  4. 加锁顺序是从左向右,直到第一个不满足条件的记录。

这些锁都是MySQL的InnoDB自动加上的,不需要用户手动操作,但是我们在日常使用数据库的时候要明白加锁的原则。

不同场景的加锁规则:

  1. 等值查询 (=)
    唯一索引/主键:Next-Key Lock 退化为 Record Lock
    普通索引:保持 Next-Key Lock,左右间隙都不能优化掉
  2. 范围查询 (>、<、>=、<=)
    唯一索引/主键:保持 Next-Key Lock,不进行优化
    普通索引:同样保持 Next-Key Lock
  3. 记录不存在的情况
    会对扫描到的第一个不满足条件的记录加 Gap Lock

工作场景示例

例如有一个users表字段与现有数据如下表所示

id age name
1 5 Tom
5 10 Jack
10 15 Rose
15 20 Mike
场景一:等值查询(唯一索引)
SELECT * FROM users WHERE id = 5 FOR UPDATE;

加锁情况:

  • 由于id是主键(唯一索引),Next-Key Lock退化为Record Lock
  • 只在id=5的记录上加X型记录锁
场景二:等值查询(普通索引)
SELECT * FROM users WHERE age = 10 FOR UPDATE;

加锁情况:

  • 对age索引:(5,10]的Next-Key Lock
  • 对主键索引:id=5的记录锁
  • 因为是普通索引,间隙不能被优化掉
场景三:范围查询
SELECT * FROM users WHERE id > 5 AND id <= 15 FOR UPDATE;

加锁情况:

  • (5,10]的Next-Key Lock
  • (10,15]的Next-Key Lock
  • (15,20]的Next-Key Lock(会扫描到第一个不满足条件的记录)
场景四:记录不存在的情况
SELECT * FROM users WHERE id = 7 FOR UPDATE;

加锁情况:由于记录不存在,加Gap Lock:(5,10)

不同隔离级别下可加锁的行为

image

插入意向锁(Insert Intention Lock)

插入意向锁是一种特殊的间隙锁,表示事务计划在某个间隙插入数据的意图。

这种锁表明了插入的意图,以这样一种方式,如果多个事务插入到同一索引间隙中但不在间隙内的相同位置插入,则它们不需要相互等待。

假设有索引记录的值为4和7。分别尝试插入值为5和6的不同事务,在获取插入行的独占锁之前,各自用插入意向锁锁定4和7之间的间隙,但由于行不冲突,所以它们不会相互阻塞。但是如果他们的都要插入6,那么就会需要阻塞了。

插入意向锁,是一种优化后的间隙锁,主要目的是优化并发,允许不冲突的插入。它与间隙锁配合使用,既防止了幻读又提升了并发。

虽然这个锁的名字叫插入意向锁,但是它是行级锁,跟意向锁这个表级锁,是没什么关系的。

AUTO-INC 锁

AUTO-INC锁是一种特殊的表级锁,由插入带有AUTOINCREMENT 列的表的事务获取。
在最简单的情况下.如果一个事务正在向表中插入值,任何其他事务都必须等待,以便执行它们自己的插入操作,这样第一个事务插入的行就会接收到连续的主键值。

最主要是用于保护AUTO_INCREMENT列的自增值生成过程,确保并发插入时自增值的唯一性和连续性,防止多个事务生成相同的自增值

分三种锁模式:
通过参数 innodb_autoinc_lock_mode 控制:

模式 描述 特点
传统模式 0 表级锁 安全性最高,性能最低
连续模式 1 轻量级互斥锁 平衡安全性和性能
无锁模式 2 预先分配自增值 性能最高,可能不连续

悲观锁

数据库中的锁,按照使用方式来分,也可分为悲观锁乐观锁

悲观锁的核心思想:"悲观"地认为数据一定会被其他事务修改,所以提前加锁。所以在操作时是先获取锁,再进行业务操作。口诀就是“一锁二查三更新”。

悲观锁示例(行级锁)

-- 悲观锁示例
START TRANSACTION;SELECT * FROM products WHERE id = 1 FOR UPDATE;  -- 加排他锁
UPDATE products SET stock = stock - 1 WHERE id = 1;COMMIT;

在对id=1的记录修改前,先通过for update的方式进行加锁,然后再进行修改。这就是比较典型的悲观锁策略。

如果以上修改库存的代码发生并发,同一时间只有一个线程可以开启事务并获得id=1的锁,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。

悲观锁示例(表级锁)

-- 表级锁
LOCK TABLES products WRITE;
-- 执行业务操作
......
-- 表解锁
UNLOCK TABLES;

当对products表进行操作的时候,会先对表加上锁,然后再操作。这样其他业务就不可以在对表中的数据进行修改了。体现了悲观的思想,业务使用方感觉“总有刁民想谋害朕”,加了锁之后安全感暴增!

悲观锁实际场景示例-金融交易(转账场景)

-- 转账操作:从账户A转100元到账户B
START TRANSACTION;-- 锁定付款方账户
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- 业务需先检查balance的余额是否大于等于100,然后再执行扣减
UPDATE accounts SET balance = balance - 100 WHERE id = 1;-- 锁定收款方账户  
SELECT balance FROM accounts WHERE id = 2 FOR UPDATE;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;COMMIT;

悲观锁实际场景示例-库存扣减(防止超卖)

-- 商品库存扣减
START TRANSACTION;SELECT stock FROM products WHERE id = 1 FOR UPDATE;-- 检查库存是否充足
-- 如果充足则扣减
UPDATE products SET stock = stock - 1 WHERE id = 1;COMMIT;

乐观锁

乐观锁总是"乐观"地认为数据不会被其他事务修改,不加锁直接操作。
会先进行操作,提交时检查是否发生冲突。主要通过版本号、时间戳等方式检测冲突。

MySQL 本身不提供内置乐观锁,需要用户在表结构设计或应用层通过 CAS 思想自行实现。CAS本质就是一项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

悲观锁示例

-- 1. 查询数据时获取版本号
SELECT id, name, stock, version FROM products WHERE id = 1;-- 2. 更新时检查版本号
UPDATE products 
SET stock = stock - 1, version = version + 1 
WHERE id = 1 AND version = 旧版本号;-- 3. 检查更新是否成功
-- 如果 update 成功记录数为0,说明数据被其他事务修改,版本号已经不再试第一步查出来的那个版本号了。

完整的示例

-- 创建带有版本号的表
CREATE TABLE products (id INT PRIMARY KEY,name VARCHAR(50),stock INT,version INT DEFAULT 1
);-- 乐观锁更新过程
START TRANSACTION;-- 步骤1:读取数据和版本号
SELECT id, stock, version FROM products WHERE id = 1;
-- 假设获取到:version = 5, stock = 100-- 步骤2:执行业务逻辑(在应用程序中)
-- 计算新库存:new_stock = 100 - 1 = 99-- 步骤3:更新数据并检查版本号
UPDATE products 
SET stock = 99, version = 6 WHERE id = 1 AND version = 5;-- 步骤4:检查更新结果
-- 如果 update 成功记录数为1,更新成功
-- 如果 update 成功记录数为0,数据被其他事务修改,需要重试COMMIT;

在实际使用过程中,可以直接用更新时间的时间戳来实现乐观锁。示例如下:

-- 使用时间戳实现乐观锁
-- 查询出更新时间
SELECT id, stock, updated_at FROM products WHERE id = 1;-- 用更新时间戳做版本号,再将现在时间更新为最新版本
UPDATE products 
SET stock = stock - 1, updated_at = NOW() 
WHERE id = 1 AND updated_at = '2025-01-16 10:30:00';

在实际业务中重试都是在业务层处理的,示例如下:

// Java中的乐观锁重试机制
public boolean updateProductStock(Long productId, int quantity) {int maxRetries = 3;for (int i = 0; i < maxRetries; i++) {// 1. 查询数据和版本号Product product = productMapper.selectById(productId);// 2. 尝试更新int affectedRows = productMapper.updateStock(productId, product.getStock() - quantity,product.getVersion());// 3. 检查更新结果if (affectedRows > 0) {return true; // 更新成功}// 4. 更新失败,短暂休眠后重试Thread.sleep(100);}return false; // 重试多次后仍然失败
}

悲观锁与乐观锁总结

悲观锁与乐观锁对比

image

悲观锁与乐观锁实际使用中的选型建议

悲观锁适用场景
  • 写操作频繁(写占比 > 30%)
  • 冲突概率高的业务
  • 临界区执行时间长(包含IO操作)
  • 强一致性要求(金融交易)
乐观锁适用场景
  • 读操作频繁(读占比 > 80%)
  • 冲突概率低的业务
  • 临界区执行时间短(纯内存操作)
  • 系统吞吐量要求高
  • 可以接受偶尔的重试
应用案例

案例一:电商商品库存

-- 悲观锁方案(适合秒杀场景)
START TRANSACTION;
SELECT stock FROM products WHERE id = 1 FOR UPDATE;IF (stock > 0) THENUPDATE products SET stock = stock - 1 WHERE id = 1;-- 创建订单...
END IF;
COMMIT;-- 乐观锁方案(适合普通商品)
UPDATE products 
SET stock = stock - 1, version = version + 1 
WHERE id = 1 AND version = #{version} AND stock > 0;IF (affected_rows = 1) THEN-- 创建订单...
ELSE-- 库存不足或版本冲突,提示用户
END IF;

案例二:用户账户余额

-- 悲观锁方案(转账场景)
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;IF (balance >= amount) THENUPDATE accounts SET balance = balance - amount WHERE id = 1;-- 记录流水...
END IF;
COMMIT;-- 乐观锁方案(积分兑换)
UPDATE accounts 
SET points = points - cost, version = version + 1 
WHERE id = 1 
AND version = #{version} 
AND points >= cost;
最佳方案

实际使用中可以监控业务的冲突概率来作为依据从而动态调整锁策略。
用Java实现一个使用例子(不含实现):

// 智能锁选择策略
public void updateData(Data data) {// 根据冲突概率动态选择锁策略if (getConflictRate() > 0.1) {  // 冲突率 > 10%// 切换到悲观锁usePessimisticLock(data);} else {// 使用乐观锁useOptimisticLock(data);}
}

在这种方案中不光要监控冲突率,还有其他需要处理的细节点。

  • 监控冲突率:根据实际情况动态调整锁策略
  • 设置重试上限:乐观锁避免无限重试
  • 降级机制:乐观锁失败次数过多时降级到悲观锁
  • 版本号管理:确保版本号字段的索引优化
  • 死锁预防:悲观锁场景下按固定顺序加锁

总结

MySQL InnoDB 的锁体系可以概括为“两类思想(悲观 vs 乐观)三种粒度(表、页、行)四种常用算法(Record / Gap / Next-Key / Insert Intention)”。
理解它们的核心是抓住两条主线:

  1. 隔离级别决定锁的“范围”——RR 用 Next-Key Lock 防幻读,RC 用 Record Lock 提并发;
  2. 索引决定锁的“位置”——锁永远加在索引上,无索引则退化为表锁或隐藏聚簇索引锁。

实际选型时,先量化冲突概率与一致性要求:

  • 冲突高、临界区长、强一致 → 悲观锁(FOR UPDATE / LOCK TABLES);
  • 冲突低、读多写少、可重试 → 乐观锁(CAS+版本号/时间戳)。

最后别忘了监控:把“冲突率、死锁次数、重试成功率”做成仪表盘,让锁策略随业务流量动态伸缩,才是真正的“高并发正确姿势”。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使一级缓存失效的四种情况

一级缓存是SqlSession级别的&#xff0c;通过同一个SqlSession查询的数据会被缓存&#xff0c;下次查询相同的数据&#xff0c;就会从缓存中直接获取&#xff0c;不会从数据库重新访问 使一级缓存失效的四种清空: 不同的SqlSession对应不同的一级缓存同一个SqlSession但是查询条…

Linux环境编程第二天笔记

Linux环境编程第二天fork()fork()会使得进程本身被复制&#xff0c;父子进程几乎一模一样。被复制的实际的UID和GID&#xff0c;以及有效的UID和GID所有的环境变量进程组ID和会话ID当前的工作路径&#xff0c;除非用chdir()修改打开的文件信号响应函数整个内存空间、包括栈、堆…

Elasticsearch近实时搜索揭秘:1秒内可查如何实现?

当你在 Kibana 中查询刚生成的日志&#xff0c;或在电商网站搜索刚上架的商品时&#xff0c;可能会好奇&#xff1a;数据写入后不到 1 秒就能搜到&#xff0c;这是如何实现的&#xff1f; 这背后就是 Elasticsearch (ES) 的“近实时”&#xff08;Near Real-Time, NRT&#xff…

Llama 3在哪些领域有实际应用?

1.Llama 3在哪些领域有实际应用&#xff1f;Llama 3 凭借其开源、高性能、可微调、支持长上下文和多语言等优势&#xff0c;已在多个领域展现出广泛的实际应用价值。尽管原生版本对中文支持有限&#xff0c;但通过微调&#xff08;SFT/LoRA&#xff09;或结合RAG&#xff08;检…

导师严选9个AI论文工具,自考本科论文格式规范+写作神器推荐!

导师严选9个AI论文工具&#xff0c;自考本科论文格式规范写作神器推荐&#xff01; AI 工具如何让论文写作更高效 在自考本科的道路上&#xff0c;论文写作无疑是一个重要且令人头疼的环节。随着人工智能技术的不断进步&#xff0c;越来越多的 AI 工具被应用于学术写作中&#…

OpenOCD JTAG协议开发完全指南

OpenOCD JTAG协议开发完全指南 版本: 1.0 日期: 2026年1月 适用范围: RISC-V调试、FPGA开发、嵌入式系统调试 目录 JTAG基础概念 OpenOCD架构 JTAG协议层次 JTAG状态机 IR/DR扫描操作 OpenOCD API详解 RISC-V调试规范 实际应用案例 常见问题排查 JTAG基础概念 什么是JTAG? …

使用 pip3 一键卸载当前环境中所有已安装的 Python 包(Linux / macOS / Windows)

在 Python 开发过程中&#xff0c;随着不断安装和测试各种库&#xff0c;环境很容易变得臃肿、混乱。 当出现依赖冲突、版本错乱或准备重建环境时&#xff0c;一次性卸载所有第三方包是一个高效的解决方案。 本文介绍一种通用、安全、可控的方法&#xff0c;适用于&#xff1a;…

【总结】说课基本框架

高中信息技术课程说课的基本框架通常围绕教学设计的系统性、科学性和育人价值展开,强调以学生为中心、项目驱动、素养导向。根据所提供的知识库内容,可总结出一个清晰、通用且符合当前教改趋势的说课结构如下:一、教…

Linux运维必备:一个LVM管理添加和扩容脚本的诞生记(完整版)

从简单需求到踩坑无数&#xff0c;最终炼成自动化神器。本文记录了一个LVM管理脚本的完整开发历程&#xff0c;包含所有踩过的坑和最终解决方案。附赠可直接使用的纯净版脚本。一、起因&#xff1a;一个看似简单的小需求那天&#xff0c;一位运维兄弟在群里求助&#xff1a;&qu…

openocd操作ku060板子记录

OpenOCD 操作 KU060 FPGA 核心原理 概述 本文档深入解析 OpenOCD 如何通过 JTAG 接口操作 KU060 FPGA 开发板&#xff0c;包括 Flash 刷写、内存检查、GDB 调试和板子状态检查等核心功能的底层原理。1. OpenOCD 架构与连接原理 1.1 JTAG 接口连接 主机(PC) --USB--> FT2232 …

《排序算法全解析:从基础到优化,一文吃透八大排序!》

本文详解冒泡、选择、插入等基础排序,以及快排、归并、堆排、希尔等高级排序的原理、c语言代码实现,对比各算法时间复杂读/空间复杂度,附代码详细注释,帮你彻底搞懂排序!一、为什么我们需要读懂排序--不止于"…

Linux命令大全-grep命令

一、简介grep(英文全拼: Global Regular Expression Print)命令用于查找文件里符合条件的字符串或正则表达式&#xff0c;并将符合条件的内容进行输出。二、语法语法&#xff1a;grep [选项]... PATTERN [FILE]...PATTERN&#xff1a;是一个基本正则表达式(缩写为BRE)&#xff…

ue c++编译报错解决

目录 e1696 无法打开 源 文件 "IMQTTClient.h" 解决方法&#xff1a; e1696 无法打开 源 文件 "IMQTTClient.h" 解决方法&#xff1a; 删除 目录&#xff1a; .vs Binaries Intermediate Saved 选中文件&#xff1a; MetahumancharacterHeiXi.uproje…

解析nanogpt - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

计算机毕业设计springboot线上票务系统app 基于Spring Boot的移动票务管理平台开发 Spring Boot框架下的线上票务系统设计与实现

计算机毕业设计springboot线上票务系统app_ck27e &#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。随着互联网技术的飞速发展&#xff0c;线上票务系统已经成为人们日常生活中不可…

集合幂级数全家桶

集合幂级数 exp 求 \(e^{F(x)} = \sum\limits_{i \ge 0} \dfrac{F(x)^i}{i!}\),其中若 \(S \cap T = \varnothing\),则 \(x^S \times x^T = x^{S \cup T}\)。 定义二元函数 \(F(x, y) = \sum x^S y^{|S|} a_S\),这样…

计算机毕业设计springboot大气网格化治理智慧平台-报警处理子系统 基于SpringBoot的城市大气环境网格化智能预警与处置平台 SpringBoot驱动的空气质量网格监管报警协同系统

计算机毕业设计springboot大气网格化治理智慧平台-报警处理子系统9w1d1 &#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。PM2.5爆表那天&#xff0c;整个城市像被按下了灰色滤镜。…

AtCoder Beginner Contest竞赛题解 | AtCoder Beginner Contest 440

​欢迎大家订阅我的专栏&#xff1a;算法题解&#xff1a;C与Python实现&#xff01; 本专栏旨在帮助大家从基础到进阶 &#xff0c;逐步提升编程能力&#xff0c;助力信息学竞赛备战&#xff01; 专栏特色 1.经典算法练习&#xff1a;根据信息学竞赛大纲&#xff0c;精心挑选…

近十届两院增选院士籍贯 / 出生地排行:苏浙皖湘鲁霸榜

整理了近十届两院增选院士的籍贯、出生地数据&#xff1a; ✅ 维度一&#xff1a;院士「籍贯」&#xff08;最能体现地域文教底蕴&#xff09; 籍贯人数 TOP20 核心榜单&#xff08;头部集中度极高&#xff09; 1、浙江宁波 28 人&#xff08;全国第一&#xff0c;70年的稳定输…

LeetCode 379 电话目录管理系统

文章目录摘要描述题解答案题解代码分析1. 数据结构的选择2. 初始化方法3. get() 方法&#xff1a;分配号码4. check() 方法&#xff1a;检查号码是否可用5. release() 方法&#xff1a;释放号码6. 为什么使用 Set Array 的组合&#xff1f;7. 边界情况处理示例测试及结果示例 …