Mysql中的锁机制详解

news2024/11/15 18:04:32

一、概述

锁是计算机协调多个进程或线程并发访问某一资源的机制。 在数据库中,除了传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题。

数据库锁机制就是为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则

MySQL数据库由于其自身架构的特点,存在多种数据存储引擎,每种存储引擎所针对的应用场景特点都不太一样,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计,所以各存储引擎的锁定机制也有较大区别。

Mysql锁按照不同维度划分可以分为多种锁,具体如下

  1. 按照多数据操作粒度从大到小划分:表锁、页锁行锁;以及特殊场景下的全局锁
  2. 按照对数据库操作类型划分:共享(读)锁(S锁)、排它(写)锁(X锁)、意向共享(读)锁以及意向排它(写)锁
  3. 以及Innodb引擎为解决幻读等并发场景下事务存在的数据问题,引入的:行记录锁(Record Lock)、间隙锁(Gap Lock)、临键锁(Next-Key Lock)等。
  4. 面向编程思想的:乐观锁悲观锁

二、表锁、页锁、行锁

Mysql数据按照数据操作粒度划分了 表锁、页锁、行锁

1.1、表锁(Table-Level Lock)

表锁顾名思义就是对表级别的锁定,每次操作锁住整张表。表级别的锁定是 MySQL 各存储引擎中最大颗粒度的锁定机制

该锁定机制最大的特点是实现逻辑简单、系统开销小。所以获取锁和释放锁的速度很快不会出现死锁;

该锁定机制锁定粒度大,发生锁冲突的概率最高,大大降低并发度;一般用在整表数据迁移的场景。

使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎。

基本操作

‐‐建表SQL
CREATE TABLE `mylock` (

 `id` INT (11) NOT NULL AUTO_INCREMENT,

  `NAME` VARCHAR (20) DEFAULT NULL,

  PRIMARY KEY (`id`)

) ENGINE = MyISAM DEFAULT CHARSET = utf8;

‐‐插入数据
INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('1', 'a');

INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('2', 'b');

INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('3', 'c');

INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('4', 'd');

‐‐手动增加表锁

lock table 表名称 read(write),表名称2 read(write);

‐‐查看表上加过的锁

show open tables;

5 ‐‐删除表锁
unlock tables;

1.2、页锁(Page-Level Lock)

除了表锁、行锁外,MySQL还有一种相对偏中性的页锁,页锁是MySQL中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。

页锁就是在页的粒度上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录。当我们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行。页锁的开销介于表锁和行锁之间,锁定粒度介于表锁和行锁之间,并发度一般。与行锁一样会出现死锁

只有BerkeleyDB存储引擎支持页级锁定。

1.3、行锁(Row-Level Lcok)

与表锁正相反,行锁最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁冲突的概率最低,能够大大提高并发度,从而提高系统的整体性能

虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁带来的开销就更大了。此外,行级锁定也最容易发生死锁

使用行级锁定的主要是InnoDB存储引擎。

注意:InnoDB的行锁实际上是针对索引加的锁(在索引对应的索引项上做标记),不是针对整个行记录加的锁。并且该索引不能失效,否则会从行锁升级为表锁。(RR级别会升级为表锁,RC级别不会升级为表锁)

如在RR级别下执行SQL

select * from account where name = 'lilei' for update; ‐‐where条件里的name字段无索引

则其它线程对该表任意一行记录做修改操作都会被阻塞住。

RR级别行锁升级为表锁的原因分析

因为在RR级别下,我们需要解决不可重读和幻读问题,所以在遍历扫描聚簇索引记录时,为了防止扫描过的索引被其他事务修改(不可重复读问题)或间隙被其他事务插入记录(幻读问题),从而导致数据不一致。所以Mysql的解决方案就是把所有扫描过的索引记录和间隙都锁上。这里要注意,并不是直接将整张表加表锁,因为不一定能加上表锁,可能有其他事务锁住了表中的其他记录。

1.4、全局锁(Global Lock)

首先全局锁,是对整个数据库实例加锁。使用场景一般是在全库逻辑备份

Mysql提供了加全局读锁的命令:Flush tables with read lock (FTWRL),这个命令可以使整个库处于只读状态。使用该命令之后,数据更新语句、数据定义语句和更新类事务的提交语句等修改数据库的操作都会被阻塞。

使用全局锁的风险

  • 若在主库备份,在备份期间不能更新,业务停摆。
  • 若在从库备份,备份期间不能执行主库同步的biglog,导致主从延迟同步。

还有一种锁全局的方式:set global readonly=true ,相当于将整个库设置成只读状态,但这种修改global配置量级较重,和全局锁不同的是:如果执行Flush tables with read lock 命令后,如果客户端发生异常断开,那么MySQL会自动释放这个全局锁,整个库回到可以正常更新的状态。但将库设置为readonly后,客户端发生异常断开,数据库依旧会保持readonly状态,会导致整个库长时间处于不可写状态。

1.4、总结

总的来说,MySQL 这 3 种锁的特性可大致归纳如下:

  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
  • 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

三、共享锁、排它锁

从对数据库操作的类型分,分为共享(读)锁排它(写)锁

2.1、共享(读)锁(Share Lock)

共享锁,又叫读锁或S锁,是读取操作(SELECT)时创建的锁。其他用户可以并发读取数据,但在读锁未释放前,不会阻塞其他事务对同一行的读操作,但会阻塞对同一行的写操作。也就是查询事务结束前,任何事务都不能对数据进行修改(获取数据上的写锁),直到已释放所有读锁,才会执行其它事物的写操作。

针对同一份数据,多个读操作可以同时进行而不会互相影响,如下

select * from T where id=1 lock in share mode;

在查询语句后面增加LOCK IN SHARE MODE,MySQL就会对查询结果中的每行都加读锁,当没有其他线程对查询结果集中的任何一行使用写锁时,可以成功申请读锁,否则会被阻塞。其他线程也可以读取使用了读锁的表,而且这些线程读取的是同一个版本的数据。

2.2、排它(写)锁(Exclusive Lock)

排他锁又称写锁、独占锁或X锁,当前写操作没有完成前,它会阻断其他写锁和读锁,数据修改操作都会加写锁,查询也可以通过for update加写锁,如下:

select * from T where id=1 for update;

对于排它锁而言,会阻塞其他事务对同一行的读和写操作,只有当写锁释放后,才会执行其它事务的读写操作。

2.3、总结

简而言之,就是:读锁会阻塞写操作 ,但是不会堵塞读操作。而写锁则会把读操作 和写操作都堵塞。

对于InnoDB 在 RR级别下(MySQL默认隔离级别),对于 update、delete 和 insert 语句, 会自动给涉及的数据集加排它锁

对于普通 select 语句,InnoDB不会加任何锁。如果想在 select 操作的时候加上共享锁 或 排它锁锁,需要我们手动加锁。

-- 加共享锁
select * from table_name where ... lock in share mode

-- 加排它锁
select * from table_name where ... for update

读写锁、意向锁的兼容性如下所示

锁类型共享(读)锁排它(写)锁
共享(读)锁兼容冲突        
排它(写)锁冲突冲突

四、加锁模式划分

4.1、记录锁(Record Lock)

记录锁其实很好理解,对表中的记录加锁,叫做记录锁也称为行锁。

SELECT * FROM `test` WHERE `id`=1 FOR UPDATE;

它会在 id=1 的记录上加上记录锁,以阻止其他事务插入,更新,删除 id=1 这一行。

需要注意的是:

  • id 列必须为唯一索引列或主键列,否则上述语句加的锁就会变成临键锁。
  • 同时查询语句必须为精准匹配(=),不能为 >、<、like 等,否则也会退化成临键锁。

在通过 主键索引 与 唯一索引 对数据行进行 UPDATE 操作时,也会对该行数据加记录锁:

-- id 列为主键列或唯一索引列 
UPDATE SET age = 50 WHERE id = 1;
  • 记录锁是锁住索引记录,而不是真正的数据记录。
  • 如果要锁的列没有索引,就会进行全表记录加锁
  • 记录锁也是排它 (写) 锁,所以会阻塞其他事务对其插入、更新、删除。

4.2、间隙锁(Gap Lock)

间隙锁 是 InnoDB 在 RR(可重复读)隔离级别下为了解决 幻读问题 时引入的锁机制。间隙锁是InnoDB 中行锁的一种。使用间隙锁会锁住左开右开这个区间的间隔,而不仅仅是这个区间中的每一条数据。间隙锁是行级锁定所以是行锁。

假设account表里数据如下:

那么间隙就有 id 为 (3,10),(10,20),(20,正无穷) 这三个区间,在Session_1下面执行如下sql:

select * from account where id = 18 for update;

则其他Session没法在这个(10,20)这个间隙范围里插入任何数据。

如果执行下面这条sql:

select * from account where id = 25 for update;

则其他Session没法在这个(20,正无穷)这个间隙范围里插入任何数据。

也就是说,只要在间隙范围内锁了一条不存在的记录会锁住整个间隙范围,不锁边界记录,这样就能防止其它Session在这个间隙范围内插入数据,就解决了可重复读隔离级别的幻读问题。

4.3、临键锁(Next-Key Lock)

临键锁是行锁与间隙锁的组合,它指的是加在 某条记录以及这条记录前面间隙上 的锁。

也可以理解为一种特殊的间隙锁。通过临建锁可以解决幻读的问题。每个数据行上的非唯一索引列(该索引里面的值允许重复)上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段 左开右闭 区间的数据。

需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。

假设有如下表:id 主键,age 普通索引

idnameage
110lilei
224hanmeimei
1032lucy
2045zhaoliu

该表中 age 列潜在的临键锁有:(-∞, 10],(10, 24],(24, 32],(32, 45],(45, +∞]。

在事务 A 中执行如下命令

-- 根据非唯一索引列 UPDATE 某条记录 
UPDATE account SET name = Vladimir WHERE age = 24; 

-- 或根据非唯一索引列 锁住某条记录 
SELECT * FROM account WHERE age = 24 FOR UPDATE;

不管执行了上述 SQL 中的哪一句,之后如果在事务 B 中执行以下命令,则该命令会被阻塞:

INSERT INTO account VALUES(100, 26, 'tianqi');

很明显,事务 A 在对 age 为 24 的列进行 UPDATE 操作的同时,也获取了 (10, 32] 这个区间内的 临键锁

对 记录锁、间隙锁、临键锁 做一个总结:

InnoDB 中的行锁的实现依赖于索引,一旦某个加锁操作没有使用到索引,那么该锁就会升级为表锁。
记录锁 存在于包括 主键索引 在内的 唯一索引 中,锁定单条索引记录。
间隙锁 存在于 非唯一索引 中,锁定开区间(左开右开区间)范围内的一段间隔。
临键锁 存在于 非唯一索引 中,该类型的每条记录的索引上都存在这种锁,它是一种特殊的间隙锁,锁定一段 左开右闭 的索引区间。

4.4、意向锁(Intention Lock)

意向锁又称I锁,针对是表级锁定。主要是为了提高加表锁的效率,是mysql数据库自己加的。

当有事务给表的数据行加了共享锁或排他锁,同时会给表设置一个标识,代表已经有行锁了,其他事务要想对表加表锁时,就不必逐行判断有没有行锁可能跟表锁冲突了,直接读这个标识就可以确定自己该不该加表锁。特别是表中的记录很多时,逐行判断加表锁的方式效率很低。而这个标识就是意向锁。 意向锁存在的目的 就是 为了让 InnoDB 中的行锁和表锁更高效的共存

意向锁主要分为 意向共享锁(IS锁)和 意向排他锁(IX锁)

意向共享锁(IS锁):事务有意向对表中的某些行加共享锁(S锁),对整个表加共享锁之前,需要先获取到意向共享锁。

-- 事务要获取某些行的 S 锁,必须先获得表的 IS 锁。
SELECT column FROM table ... LOCK IN SHARE MODE;

意向排他锁(IX锁):事务有意向对表中的某些行加排他锁(X锁),对整个表加排他锁之前,需要先获取到意向排他锁。

 -- 事务要获取某些行的 X 锁,必须先获得表的 IX 锁。
 SELECT column FROM table ... FOR UPDATE;

首先我们要明白四点:

  • 意向共享锁(IS)和 意向排他锁(IX)都是 表锁(表级锁)
  • 意向锁是一种 不与行级锁冲突的表级锁,这一点非常重要。
  • 意向锁是 InnoDB 自动加的, 不需用户干预
  • 意向锁是在 InnoDB 下存在的内部锁,对于MyISAM 而言没有意向锁之说。

看下 共享(读)锁、排他(写)锁、意向共享锁(IS)、意向排他锁(IX) 的兼容性

锁类型共享(读)锁排它(写)锁意向读锁意向写锁
共享(读)锁兼容冲突        兼容冲突
排它(写)锁冲突冲突冲突冲突
意向读锁兼容冲突兼容兼容
意向写锁冲突冲突兼容兼容

 这里的 排他(写)锁 / 共享(读)锁 指的都是表锁!意向锁不会与 行级的共享 / 排他锁 互斥!

可以看出 意向锁之间是互相兼容的。意向锁不会为难意向锁,也不会为难 行级排他(写)锁 / 共享(读)锁,它的存在是为难 表级排他(写)锁 / 共享(读)锁

意向锁与意向锁之间永远是兼容的,因为当你不论加行级的 读锁或 写锁,都会自动获取表级的 意向读锁或者 意向写锁。也就是你有 10 个事务,对不同的 10 行加了行级 写锁,那么这个时候就存在 10 个 意向写锁。

这 10 个 意向写锁存在的作用是啥呢,就是假如这个时候有个事务,想对整个表加排它 (写)锁,那它不需要遍历每一行是否存在 共享锁 或 排它锁,而是看有没有存在 意向锁,只要存在一个意向锁,那这个事务就加不了表级排它(写)锁,要等上面 10 个 意向写锁全部释放才行。

4.5、插入意向锁(Insert Intention Lock)

在讲解插入意向锁之前,先来思考一个问题?

下面有张表 id 主键,age 普通索引。

idnameage
1Mr10
2Tome20
3Jon30

首先事务 A 插入了一行数据,并且没有 commit:

INSERT INTO users SELECT 4, 'Bill', 15;

随后事务 B 试图插入一行数据:

INSERT INTO users SELECT 5, 'Louis', 16;

请问:

  • 事务 A 使用了什么锁?
  • 事务 B 是否会被事务 A 阻塞?

插入意向锁是在插入一条记录行前,由 INSERT 操作产生的一种间隙锁。

该锁用以表示插入意向,当多个事务在同一区间(gap)插入位置不同的多条数据时,事务之间不需要互相等待。

虽然插入意向锁中含有意向锁三个字,但是它并不属于意向锁而属于间隙锁,因为意向锁是表锁,而插入意向锁是行锁

假设存在两条值分别为 4 和 7 的记录,两个不同的事务分别试图插入值为 5 和 6 的两条记录,每个事务在获取插入行上独占的(排他)锁前,都会获取(4,7 ] 之间的临键锁(间隙锁),但是因为数据行之间并不冲突,所以两个事务之间并不会产生冲突(阻塞等待)。

插入意向锁 的特性可以分成两部分:

  • 插入意向锁是 一种特殊的间隙锁 —— 间隙锁可以锁定开区间内的部分记录。
  • 插入意向锁之间互不排斥,所以即使多个事务在同一区间插入多条记录,只要记录本身(主键、唯一索引)不冲突,那么事务之间就不会出现冲突等待。

现在我们可以回答开头的问题了:

  • 使用插入意向锁与记录锁。
  • 事务 A 不会阻塞事务 B。

为什么不用间隙锁?

如果只是使用普通的间隙锁会怎么样呢?我们在看事务 A,其实它一共获取了 3 把锁:

  • id 为 4 的记录行的记录锁。
  • age 区间在(10,15 ] 的间隙锁。
  • age 区间在(15,20 ] 的间隙锁。

最终,事务 A 插入了该行数据,并锁住了(10,20 ] 这个区间。

随后事务 B 试图插入一行数据:

INSERT INTO users SELECT 5, 'Louis', 16;

因为 16 位于(15,20 ] 区间内,而该区间内又存在一把间隙锁,所以事务 B 别说想申请自己的间隙锁了,它甚至不能获取该行的记录锁,自然只能乖乖的等待事务 A 结束,才能执行插入操作。

很明显,这样做事务之间将会频发陷入阻塞等待,插入的并发性非常之差。这时如果我们再去回想我们刚刚讲过的插入意向锁,就不难发现它是如何优雅的解决了并发插入的问题。

总结

  • InnoDB 在 RR 的事务隔离级别下,使用插入意向锁来控制和解决并发插入。
  • 插入意向锁是 一种特殊的间隙锁。
  • 插入意向锁在锁定区间相同但记录行本身不冲突的情况下互不排斥。

五、乐观锁、悲观锁

从面向对象思想上分为乐观锁(用版本对比或CAS机制)悲观锁。

5.1、乐观锁

顾名思义乐观锁就是持比较乐观态度的锁。就是在操作数据时非常乐观,认为别的线程不会同时修改数据,所以不会上锁,但是在更新的时候会判断在此期间别的线程有没有更新过这个数据。

比如数据库提供的类似于write_condition机制,Java API 并发工具包下面的原子变量类就是使用了乐观锁的CAS(Compare and Swap,比较并替换)来实现的。

它适用于读多写少的场景,也就是说减少操作冲突,这样可以省去锁竞争的开销,提高系统的吞吐量。

5.2、悲观锁

悲观锁就是持悲观态度的锁。就在操作数据时比较悲观,每次去拿数据的时候认为别的线程也会同时修改数据,所以每次在拿数据的时候都会上锁,这样别的线程想拿到这个数据就会阻塞直到它拿到锁。

比如行锁、表锁、读锁、写锁,都是在操作之前先上锁,Java API 中的 synchronizsd 和ReentrantLock等独占锁都是悲观锁思想的实现。

它适用于写多读少的场景。如果还使用乐观锁的话,会经常出现操作冲突,这样会导致应用层会不断地 Retry,反而会降低系统性能。

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

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

相关文章

新电脑Win11系统想要降级为Win10怎么操作?

前言 现在的电脑大部分都是Windows 11系统&#xff0c;组装机还好一些&#xff0c;如果想要使用Windows 10&#xff0c;只需要在安装系统的时候选择Windows 10镜像即可。 但是对于新笔记本、厂商的成品机、一体机来说&#xff0c;只要是全新的电脑&#xff0c;基本上都是Wind…

黑马JavaWeb开发笔记14——Tomcat(介绍、安装与卸载、启动与关闭)、入门程序解析(起步依赖、SpringBoot父工程、内嵌Tomcat)

文章目录 前言一、Web服务器-Tomcat1. 简介1.1服务器概述1.2 Web服务器1.3 Tomcat 2. 基本使用2.1 下载2.2 安装与卸载2.3 启动与关闭2.4 常见问题 二、入门程序解析1. 起步依赖2. SpringBoot父工程3. 内嵌Tomcat 总结 前言 本篇文章是2023年最新黑马JavaWeb开发笔记14&#x…

[Java]SpringBoot登录认证流程详解

登录认证 登录接口 1.查看原型 2.查看接口 3.思路分析 登录核心就是根据用户名和密码查询用户信息,存在则登录成功, 不存在则登录失败 4.Controller Slf4j RestController public class LoginController {Autowiredprivate EmpService empService;/*** 登录的方法** param …

【Python】一文详细向您介绍 bisect_left 函数

【Python】一文详细向您介绍 bisect_left 函数 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;985高校的普通本硕&#x…

shell脚本1----编程规范与变量

shell脚本 shell的功能 Shell&#xff08;壳程序&#xff09;是一个特殊的应用程序&#xff0c;它介于操作系统内核与用户之间&#xff0c;充当了一个“命令解释器”的角色&#xff0c;负责接收用户输入的操作指令&#xff08;命令&#xff09;并进行解释&#xff0c;将需要执…

(前端)面试300问之(3)this的指向判断

一、this的相关理解与解读 1、各角度看this。 1&#xff09;ECMAScript规范&#xff1a; this 关键字执行为当前执行环境的 ThisBinding。 2&#xff09;MDN&#xff1a; In most cases, the value of this is determined by how a function is called. 在绝大多数情况下&…

图片损坏,如何修复?

在数字化时代&#xff0c;图片已成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;有时我们可能会遇到图片损坏的情况&#xff0c;无论是珍贵的家庭照片、工作文档中的关键图像&#xff0c;还是社交媒体上的分享内容&#xff0c;图片损坏都可能带来不小的困扰。那么…

网络传输加密及openssl使用样例(客户端服务器)

文章目录 背景常用加密方式SSLOpenSSL主要功能 库结构 交互流程证书生成生成 RSA 私钥私钥的主要组成部分私钥的格式 创建自签名证书: 签发证书服务器端代码客户端代码常见错误版本问题证书问题证书格式 背景 网络传输中为保证数据安全&#xff0c;通常需要加密 常用加密方式…

Open3D 基于曲率大小的特征点提取

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 三、实现效果 3.1原始点云 3.2提取特征点 Open3D点云算法汇总及实战案例汇总的目录地址&#xff1a; Open3D点云算法与点云深度学习案例汇总&#xff08;长期更新&#xff09;-CSDN博客 一、概述 基于曲率…

STM32 外部中断(EXTI)

STM32 外部中断(EXTI) 实验&#xff1a;配置一个引脚的下降沿作为外部中断。 参考&#xff1a;江协科技 相关缩写 RCC(Reset and Clock Control) 复位和时钟控制 GPIO(General Purpose Input/Output) 通用输入/输出 AFIO(Alternate Function Input Output) 复用功能输入输…

6.Lab five —— Lazy Page Allocation

首先先切换到lazy分支 git checkout lazy make clean Xv6应用程序使用sbrk()系统调用向内核请求堆内存。sbrk()分配物理内存并将其映射到进程的虚拟地址空间。内核为一个大请求分配和映射内存可能需要很长时间。为了提高效率&#xff0c;故采用懒分配的策略 Eliminate alloc…

Scratch在线玩:3D地铁跑酷

小虎鲸Scratch资源站-免费Scratch作品源码,素材,教程分享平台! 作品介绍&#xff1a; 欢迎体验在 Scratch 上重新制作的 3D 地铁跑酷游戏&#xff01;这款游戏完全采用 3D 技术打造&#xff0c;带来流畅的视觉效果和出色的游戏体验。游戏的目标是避免列车和障碍物&#xff0c;同…

力扣 1419. 数青蛙

力扣 1419. 数青蛙 1. 题目 2. 思路 本题就是一道 字符串模拟题&#xff1b; 题目说到了&#xff0c; 会混杂着青蛙的叫声&#xff0c; 如果字符串 croakOfFrogs 不是由若干有效的 “croak” 字符混合而成&#xff0c;请返回 -1, 那就是说如果有多余的 c, r, o等等, 比如 &quo…

【机器学习】机器学习引领未来:赋能精准高效的图像识别技术革新

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀目录 &#x1f50d;1. 引言&#x1f4d2;2. 机器学习基础与图像识别原理&#x1f341;机器学习概述&#xff1a;监督学习、无监督学习与强化学…

「深入理解」HTML Meta标签:网页元信息的配置

「深入理解」HTML Meta标签&#xff1a;网页元信息的配置 HTML的<meta>元素用于提供关于HTML文档的元数据&#xff08;metadata&#xff09;&#xff0c;这些信息对于浏览器和其他处理HTML文档的应用程序来说是非常有用的&#xff0c;如&#xff1a;<base>、<li…

虚幻引擎VR游戏开发02 | 性能优化设置

常识&#xff1a;VR需要保持至少90 FPS的刷新率&#xff0c;以避免用户体验到延迟或晕眩感。以下是优化性能的一系列设置&#xff08;make sure the frame rate does not drop below a certain threshold&#xff09; In project setting-> &#xff08;以下十个设置都在pr…

用于全栈自动化测试的最佳Python工具

我知道大多数测试人员会说Java是他们创建自动化测试的首选语言。 但是我最喜欢的是Python。为什么?为什么是Python ? Al Sweigart&#xff0c;《自动化那些无聊的东西》的作者&#xff0c;Python一直是他的首选语言&#xff0c;因为:它有一个温和的学习曲线。它适用于Windows…

42.哀家要长脑子了!

1.965. 单值二叉树 - 力扣&#xff08;LeetCode&#xff09; 深度优先搜索&#xff0c;看边两端的结点是不是一样的值 class Solution { public:bool isUnivalTree(TreeNode* root) {if(!root) return true;if(root->right) {if(root->val ! root->right->val || …

数字图像处理基础:图像处理概念、步骤、方式介绍

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发…

5.2.数据结构-c/c++二叉树详解(下篇)(算法面试题)

本章所有代码请见&#xff1a;5.3.数据结构-c/c二叉树代码-CSDN博客 上篇:5.数据结构-c/c二叉树详解(上篇)&#xff08;遍历方法&#xff0c;完全二叉树&#xff09;-CSDN博客 目录 1 求二叉树 第k层的节点 2 查找一个节点是否在二叉树中 3 求二叉树节点的个数 4 求二叉树…