MySQL 如何避免 RC 隔离级别下的 INSERT 死锁?

本文分析了 INSERT 及其变种(REPLACE/INSERT ON DUPLICATE KEY UPDATE)的几个场景的死锁及如何避免。

作者:张洛丹,DBA 数据库技术爱好者~

爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

本文共 3200 字,预计阅读需要 10 分钟。

说在前面

本文分析了 INSERT 及其变种(REPLACE/INSERT ON DUPLICATE KEY UPDATE)的几个场景的死锁及如何避免:

  • 场景一:INSERT 唯一键冲突
  • 场景二/三:REPLACE INTO 唯一键冲突(来自线上业务)
  • 场景四:INSERT 主键冲突(来自官方案例)

其实 Google 一番,也会有大量这样的文章。本文只是就几个场景进行了分析,不过一遍走下来,对 INSERT 加锁情况、如何导致的死锁也就掌握了,个人能力有限,如文中内容有错误和纰漏,也欢迎大佬指出。

有兴趣的就继续往下看吧~

回顾行锁

在此之前,先浅浅回顾一下 InnoDB 中的行锁类型。

记录锁(RECORD LOCK)

对索引记录加锁。

间隙锁(GAP LOCK,也叫范围锁)

对索引记录的所在间隙加锁,在 RR 隔离级别下,用于解决幻读的问题(实际上在 RC 隔离级别下,也会产生间隙锁)。

S 间隙锁和 X 间隙锁是兼容的,不同的事务可以在同一个间隙加锁。

NEXT-KEY 锁

相当于 RECORD LOCK + GAP LOCK。

插入意向锁(INSERT INTENTION LOCK)

GAP 锁的一种,在执行 INSERT 前,如果待插入记录的下一条记录上被加了 GAP 锁,则 INSERT 语句被阻塞,且生成一个插入意向锁。

仅会被 GAP 锁阻塞。

隐式锁

新插入的记录,不生成锁结构,但由于事务 ID 的存在,相当于加了隐式锁;别的事务要对这条记录加锁前,先帮助其生成一个锁结构,然后再进入等待状态。


这里产生死锁的关键就是 GAP 锁。GAP 锁是在 RR 隔离级别下用于解决幻读问题,但是 RC 隔离级别下,在重复键检查和外键检查时也会用到。

再浅浅回顾一下 INSERT 语句加锁类型:

  1. 被 GAP 锁阻塞时,生成一个插入意向锁。
  2. 遇到重复键冲突时
    • 主键冲突,产生 S 型记录锁(RR 和 RR 隔离级别,实际上在 INSERT 阶段时还是会请求 GAP 锁)。
    • 唯一键冲突,产生 S 型 NEXT-KEY 锁(RR 和 RR 隔离级别)。

注意:INSERT 语句正常执行时,不会生成锁结构。

另外,对于 INSERT ... ON DUPLICATE KEY UPDATEREPLACE 稍有一些不同:

锁类型的不同

INSERT ... ON DUPLICATE KEY UPDATEREPLACE 如果遇到重复键冲突。

  • 如果是主键冲突,加 X 型记录锁(RR 和 RR 隔离级别,实际上在 INSERT 阶段时还是会请求 GAP 锁)。
  • 如果是唯一键冲突,加 X 型 NEXT-KEY 锁(RR 和 RR 隔离级别)。

锁范围不同

  • INSERTINSERT ... ON DUPLICATE KEY UPDATE 在插入或 UPDATE 的行上加 NEXT-KEY 锁。
  • REPLACE 在加 NEXT-KEY 锁时,会在 REPLACE 的记录及其下一条记录上加 NEXT-KEY 锁。

    这里和官方文档描述有些不同。如下,官方仅说了会在被 REPLACE 的行上加 NEXT-KEY 锁,但是测试下来其下一行也会加 NEXT-KEY 锁,具体见后文的场景。

最后浅浅回顾一下死锁的产生条件以及观测手段:

死锁的产生条件

两个或两个以上事务,互相等待对方持有的锁,且持有对方需要的锁,从而造成循环等待。

死锁观测手段

performance_schema.data_locks 查看会话产生的锁结构信息。

SELECT ENGINE_TRANSACTION_ID, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;

show engine innodb status 查看死锁信息。

正式开始

正式开始前还是要说一下基本的环境信息:

  • MySQL 8.0.32
  • transaction_isolation:READ-COMMITTED

准备数据

每个案例初始数据都是这些。

DROP TABLE IF EXISTS t1;
CREATE TABLE t1 (id INT NOT NULL AUTO_INCREMENT,a INT NULL,b INT NULL,PRIMARY KEY (id),UNIQUE INDEX uk_a (a ASC)
);
INSERT INTO t1 (id, a, b) VALUES (1, 10, 0);
INSERT INTO t1 (id, a, b) VALUES (2, 20, 0);
INSERT INTO t1 (id, a, b) VALUES (3, 30, 0);
INSERT INTO t1 (id, a, b) VALUES (4, 40, 0);
INSERT INTO t1 (id, a, b) VALUES (5, 50, 0);

场景一

时刻session1session2
T1BEGIN;
INSERT INTO t1(a,b) VALUES (35,0);
T2BEGIN;
INSERT INTO t1(a,b) VALUES (35,0); --被阻塞
T3INSERT INTO t1(a,b) VALUES (33,0)
T4DEADLOCK

不同时刻持有锁状态如下:

说明:示意图中仅画出我们分析的唯一索引上的锁,实际上在对唯一索引加上锁后,还会对对应的聚簇索引加记录锁,对主键索引但这里不去体现了,下文同。

过程解说

T1 时刻

session1 插入记录成功,此时对应的索引记录被隐式锁保护,未生成锁结构。

T2 时刻

session2 插入记录检测到插入值和 session1 唯一键冲突。

  • session2 帮助 session1 对 a=35 的记录产生了一个显式的锁结构。
  • session2 自身产生 S 型的 NEXT-KEY LOCK,请求范围为 (30,35],但是其只能获取到 (30,35) 的 GAP LOCK,而被 session1 的 a=35 的记录锁阻塞。
mysql> SELECT ENGINE_TRANSACTION_ID, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
| ENGINE_TRANSACTION_ID | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
|               xxxxxx2 | t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
|               xxxxxx2 | t1          | uk_a       | RECORD    | S             | WAITING     | 35, 7     |
|               xxxxxx1 | t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
|               xxxxxx1 | t1          | uk_a       | RECORD    | X,REC_NOT_GAP | GRANTED     | 35, 7     |
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
4 rows in set (0.01 sec)
T3 时刻
  • session1 插入 a=33,被 session2 (30,35)间隙锁阻塞。

至此,形成闭环锁等待,死锁条件达成:

  • session1 持有 session2 需要的 a=35 记录锁,且请求 session2 持有的 (30,35) GAP 锁。
  • session2 持有 session1 需要的 (30,35) GAP 锁,且请求 session1 持有的记录锁。

下面是打印的死锁日志。

针对该场景的死锁该如何避免:

  • 在一个事务中的 INSERT 按照主键或唯一键的顺序增序插入,即 session1 可以先插入 a=33 的记录,再插入 a=35 的记录,可一定程度避免受到 GAP 锁的影响。
  • 一个事务中只插入一行记录,且尽快提交。

场景二

时刻session1session2session3
T1BEGIN; REPLACE INTO t1 (a, b) VALUES (40, 1);
T2BEGIN; REPLACE INTO t1 (a, b) VALUES (30, 1); -- 被阻塞
T3BEGIN; REPLACE INTO t1 (a, b) VALUES (40, 1);  -- 被阻塞
T4COMMIT;
T52 rows affected;DEADLOCK,ROLLBACK;

不同时刻持有锁状态如下:

过程解说

T1 时刻

session1 检测到唯一键冲突,对 REPLACE 的记录和其下一条记录加 X 型 NEXT-KEY 锁,即锁范围为 (30,40],(40,50]。

注意:这里和 INSERT 区分,INSERT 遇到唯一键冲突被阻塞时,在插入的记录上加的 NEXT-KEY 锁,这里 REPLACE 是在插入记录和下一条记录上加的 NEXT-KEY 锁(官方文档描述似乎有欠妥当)。

锁情况

mysql> SELECT ENGINE_TRANSACTION_ID, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
| ENGINE_TRANSACTION_ID | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
|               xxxxxx1| t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
|               xxxxxx1| t1          | uk_a       | RECORD    | X             | GRANTED     | 40, 4     |
|               xxxxxx1| t1          | uk_a       | RECORD    | X             | GRANTED     | 50, 5     |
|               xxxxxx1| t1          | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 4         |
|               xxxxxx1| t1          | uk_a       | RECORD    | X,GAP         | GRANTED     | 40, 10    |
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
5 rows in set (0.00 sec)
T2 时刻

session2 遇到唯一键冲突,对 REPLACE 的记录和其下一条记录加 X 型 NEXT-KEY 锁,即锁范围是 (20,30],(30,40],对 (20,30],(30,40) 加锁成功,但是等待 session1 a=40 的记录锁。

mysql> SELECT ENGINE_TRANSACTION_ID, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
| ENGINE_TRANSACTION_ID | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
|               xxxxxx2 | t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
|               xxxxxx2 | t1          | uk_a       | RECORD    | X             | GRANTED     | 30, 3     |
|               xxxxxx2 | t1          | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 3         |
|               xxxxxx2 | t1          | uk_a       | RECORD    | X             | WAITING     | 40, 4     |
|               xxxxxx1 | t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
|               xxxxxx1 | t1          | uk_a       | RECORD    | X             | GRANTED     | 40, 4     |
|               xxxxxx1 | t1          | uk_a       | RECORD    | X             | GRANTED     | 50, 5     |
|               xxxxxx1 | t1          | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 4         |
|               xxxxxx1 | t1          | uk_a       | RECORD    | X,GAP         | GRANTED     | 40, 10    |
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
9 rows in set (0.00 sec)
T3 时刻

session3 请求的锁类型和 session1 相同,锁范围为(30,40],(40,50],在获取(30,40] NEXT-KEY 锁时,只获取到了(30,40) GAP 锁,等待 session1 a=40 的记录锁。

注意:这里还未对(40,50] 加上锁,InnoDB 行锁是逐行获取的,无法获取到则被阻塞。

锁情况

mysql> SELECT ENGINE_TRANSACTION_ID, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
| ENGINE_TRANSACTION_ID | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
|               xxxxxx3 | t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
|               xxxxxx3 | t1          | uk_a       | RECORD    | X             | WAITING     | 40, 4     |
|               xxxxxx2 | t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
|               xxxxxx2 | t1          | uk_a       | RECORD    | X             | GRANTED     | 30, 3     |
|               xxxxxx2 | t1          | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 3         |
|               xxxxxx2 | t1          | uk_a       | RECORD    | X             | WAITING     | 40, 4     |
|               xxxxxx1 | t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
|               xxxxxx1 | t1          | uk_a       | RECORD    | X             | GRANTED     | 40, 4     |
|               xxxxxx1 | t1          | uk_a       | RECORD    | X             | GRANTED     | 50, 5     |
|               xxxxxx1 | t1          | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 4         |
|               xxxxxx1 | t1          | uk_a       | RECORD    | X,GAP         | GRANTED     | 40, 10    |
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
11 rows in set (0.01 sec)
T4 时刻
  • session1 提交后,持有的锁释放。
  • session2 获取到 a=40 的记录锁,至此,session2 持有的锁为 (20,30],(30,40] NEXT-KEY 锁 ;session2获取到锁后,执行插入操作,由于插入的间隙是 (20,40),被 session3 的 (30,40) GAP 锁阻塞,产生插入意向锁,并进入等待状态。

至此,形成闭环锁等待,死锁条件达成:

  • session2 持有 (20,30],(30,40] NEXT-KEY 锁,请求插入意向锁,被 session3 的 (30,40) GAP 锁阻塞。
  • session3 持有阻塞 session2 的 (30,40) GAP 锁,请求 sesion2 持有的 a=40 记录锁。

下面是打印的死锁日志。

场景三

时刻session1session2session3
T1BEGIN; SELECT * FROM t1 WHERE a=40 for UPDATE;
T2BEGIN; REPLACE INTO t1 (a, b) VALUES (30, 1);-- 被阻塞
T3BEGIN; REPLACE INTO t1 (a, b) VALUES (40, 1); -- 被阻塞
T4COMMIT;
T52 rows affected;DEADLOCK,ROLLBACK;

不同时刻持有锁状态如下:

该场景和场景二死锁情况基本相同,只是 session1 持有锁类型不同,就不一一解说了。

下面是打印的死锁日志。

针对场景二和场景三的死锁该如何避免?

从前面的分析中,可以看到在唯一键冲突时,INSERTINSERT ... ON DUPLICATE KEY UPDATE 的加锁范围要比 REPLACE 加锁范围小,在该场景下,可使用 INSERT ... ON DUPLICATE KEY UPDATE 代替 REPLACE 来避免死锁,有兴趣的可以自己测试下。

场景四

说明

  • 本案例测试主键冲突的情况,先删除了表上的唯一键,避免干扰。
  • 对于唯一键冲突的该种场景下同样会产生死锁,死锁情况相同,有兴趣可自行验证。
时刻session1session2session3
T1BEGIN;INSERT INTO t1 (id,a, b) VALUES (6,60, 0);
T2BEGIN;INSERT INTO t1 (id,a, b) VALUES(6,70, 0); --被阻塞
T3BEGIN;INSERT INTO t1 (id,a, b) VALUES(6,80, 0);-- 被阻塞
T4ROLLBACK;
T51 rows affected;DEADLOCK,ROLLBACK;

锁情况

在 T1、T2、T3 阶段锁情况如下,此时并没有 GAP 锁,是记录锁,相应的锁状态如下:

mysql>  SELECT ENGINE_TRANSACTION_ID, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
| ENGINE_TRANSACTION_ID | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
|               xxxxxx3 | t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
|               xxxxxx3 | t1          | PRIMARY    | RECORD    | S,REC_NOT_GAP | WAITING     | 6         |
|               xxxxxx2 | t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
|               xxxxxx2 | t1          | PRIMARY    | RECORD    | S,REC_NOT_GAP | WAITING     | 6         |
|               xxxxxx1 | t1          | NULL       | TABLE     | IX            | GRANTED     | NULL      |
|               xxxxxx1 | t1          | PRIMARY    | RECORD    | X,REC_NOT_GAP | GRANTED     | 6         |
+-----------------------+-------------+------------+-----------+---------------+-------------+-----------+
6 rows in set (0.00 sec)
T4 时刻

session1 ROLLBACK,session2 和 session3 都获取到了 S 锁,在 INSERT 阶段,却产生了 NEXT-KEY 锁,锁范围为 (5,supremum]。

至此,形成闭环锁等待,死锁条件达成: session2 和 session3 分别想要在插入的间隙 (5,supremum) 获得插入意向锁,但分别被对方持有的 GAP 锁阻塞。

下面是打印的死锁日志。

触发死锁后,我们再看锁持有情况。

此时 session2 持有 (5,supremum),再插入该范围内的记录都会被阻塞了。

mysql>  SELECT ENGINE_TRANSACTION_ID, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
+-----------------------+-------------+------------+-----------+--------------------+-------------+------------------------+
| ENGINE_TRANSACTION_ID | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE          | LOCK_STATUS | LOCK_DATA              |
+-----------------------+-------------+------------+-----------+--------------------+-------------+------------------------+
|               xxxxxx2 | t1          | NULL       | TABLE     | IX                 | GRANTED     | NULL                   |
|               xxxxxx2 | t1          | PRIMARY    | RECORD    | S                  | GRANTED     | supremum pseudo-record |
|               xxxxxx2 | t1          | PRIMARY    | RECORD    | X,INSERT_INTENTION | GRANTED     | supremum pseudo-record |
|               xxxxxx2 | t1          | PRIMARY    | RECORD    | S,GAP              | GRANTED     | 6                      |
+-----------------------+-------------+------------+-----------+--------------------+-------------+------------------------+
4 rows in set (0.00 sec)

小结

从前面的实验中可以看到无论是 INSERT 还是 REPLACE,在高并发的情况下由于唯一键的存在,即使在 RC 隔离级别下,仍然有较大概率会触发到死锁。当前只能在业务端做好容错处理,以下是一些小建议来减少或避免 INSERT 死锁:

  1. RC 隔离级别相较 RR 隔离级别产生死锁的概率小,但仍不可避免。
  2. INSERT ... ON DUPLICATE KEY UPDATEREPLACE 产生死锁的几率小且更安全高效。
  3. 并发事务按照相同的顺序处理数据。
  4. 事务尽快提交,避免大事务、长事务。

另外,通过前面的实验,大家可能会有以下疑问:

  1. 为什么 RC 隔离级别要使用 GAP 锁?
  2. 为什么主键和唯一键的处理方式不同?
  3. ...???

有兴趣的可以到下面文章寻找答案: http://mysql.taobao.org/monthly/2022/05/02/ 更多技术文章,请访问:https://opensource.actionsky.com/

关于 SQLE

爱可生开源社区的 SQLE 是一款面向数据库使用者和管理者,支持多场景审核,支持标准化上线流程,原生支持 MySQL 审核且数据库类型可扩展的 SQL 审核工具。

SQLE 获取

类型地址
版本库https://github.com/actiontech/sqle
文档https://actiontech.github.io/sqle-docs/
发布信息https://github.com/actiontech/sqle/releases
数据审核插件开发文档https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse

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

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

相关文章

Mysql存储过程与存储函数

文章目录 1. 简介2. 存储过程的特点3. 存储过程操作语法4. 存储过程变量5. 其它语法6. 存储函数 1. 简介 存储过程是事先经过编译并存储在数据库中的一段SQL集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输&a…

vs2019 c++开发linux应用

VS2019 C的跨平台开发——Linux开发_Mr_L_Y的博客-CSDN博客前言由于前段时间正好买了一个服务器来跑Tensorflow的推理模型,所以借这个机会把Linux的开发也一并补上。先声明我的服务器是Ubuntu16.04,下面文章的内容也是基于Ubuntu16.04的。为什么标题要写…

MySQL——连接查询

2023.9.4 连接查询相关sql92语句笔记: #连接查询。 又称多表查询,当查询的字段来自多个表时,就会用到连接查询。 #等值连接 /* ①多表等值连接的结果为多表的交集部分 ②n表连接,至少需要n-1个连接条件 ③多表的顺序没有要求 ④一…

论文阅读《Nougat:Neural Optical Understanding for Academic Documents》

摘要 科学知识主要存储在书籍和科学期刊中,通常以PDF的形式。然而PDF格式会导致语义信息的损失,特别是对于数学表达式。我们提出了Nougat,这是一种视觉transformer模型,它执行OCR任务,用于将科学文档处理成标记语言&a…

数据结构之栈的实现(附源码)

目录 一、栈的概念及结构 ​二、栈的实现 三、初学栈的练习题 一、栈的概念及结构 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出…

JS数组迭代方法实操

数组迭代方法有 1. every() 2.some() 3.foreach() 4.map() 5.filter 逐一操作,并简要区分之。 1 every() every() 方法使用指定的函数测试数组中所有的项,在数组的所有项都满足该条件时,才返回true,否则返回false; …

Win10下使用vim9

作为一个经常与文字打交道的Writer,你在学会Vim的基本操作之后,就一定会爱上Vim的。 以下是Windows10_64位(专业版)环境中安装、使用Vim9的全过程,分享一下: 一、下载、安装Vim9 去Vim官网去下载最新的Vi…

切片机制和MR工作机制

InputFormat基类 TextInputFormat:TextInputFormat是默认的FileInputFormat实现类。按行读取每条记录。键是存储该行在整个文件中的起始字节偏移量, LongWritable类型。 CombineTextInputFormat:CombineTextInputFormat用于小文件过多的场景…

React原理 - React Reconciliation-上

目录 扩展学习资料 React Reconciliation Stack Reconciler【15版本、栈协调】 Stack Reconciler-事务性 事务性带来的弊端: 扩展学习资料 名称 链接 备注 官方文档 Reconciliation – React 英文 stack reconciler Implementation Notes – React 英文…

Ubuntu22.04安装Mongodb7.0

Ubuntu安装Mongodb 1.平台支持2.安装MongoDB社区版2.1导入包管理系统使用的公钥2.2为MongoDB创建列表文件2.3重新加载本地包数据库2.4安装MongoDB包1.安装最新版MongoDB2.安装指定版MongoDB 3.运行MongoDB社区版1.目录2.配置文件3.初始化系统4.启动MongoDB5.验证MongoDB是否成功…

Python的pandas库来实现将Excel文件转换为JSON格式的操作

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

物联网应用中蓝牙模块怎么选?_蓝牙模块厂家

在蓝牙模块选型前期,一定要了解应用场景以及需要实现的功能(应用框图),以及功能实现过程中所能提供调用的接口(主从设备,功能),考虑模块供电,尺寸,接收灵敏度…

【已更新建模代码】2023数学建模国赛B题matlab代码--多波束测线问题

一、 问题重述 1.1问题背景 海洋测深是测定水体深度与海底地形的重要任务,有两种主要技术:单波束测 深与多波束测深。单波束适用于简单任务,但多波束可提供更精确的地形数据。多 波束系统的关键在于覆盖宽度与重叠率的设计,以确保…

【JavaSE】面试01

文章目录 1. JDK、JRE、JVM之间的关系2. 补充3. 面试题:重载和重写的区别?4. super和this5. (重点!!)若父类和子类均有静态代码块、实例代码块以及无参构造方法,则继承关系上的执行顺序&#xf…

RK3588平台产测之ArmSoM-W3 DDR压力测试

1. 简介 RK3588从入门到精通 ArmSoM团队在产品量产之前都会对产品做几次专业化的功能测试以及性能压力测试,以此来保证产品的质量以及稳定性 优秀的产品都要进行多次全方位的功能测试以及性能压力测试才能够经得起市场的检验 2. 环境介绍 硬件环境: …

Hadoop生态之hive

一 概述与特点 之所以把Hive放在Hadoop生态里面去写,是因为它本身依赖Hadoop。Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供类 SQL 查询功能。 其本质是将 SQL 转换为 MapReduce/Spark 的任务进行运算,底层由 HDFS 来提供…

JavaExcel:自动生成数据表并插入数据

故事背景 出于好奇,当下扫描excel读取数据进数据库 or 导出数据库数据组成excel的功能层出不穷,代码也是前篇一律,poi或者easy excel两种SDK的二次利用带来了各种封装方法。 那么为何不能直接扫描excel后根据列的属性名与行数据的属性建立S…

基于JAVA SSM框架和JSP的超市小卖部管理系统设计

摘要 随着时代的发展,传统的超市购物方式已经不能满足人们的需求,对于顾客来说,排队购物和支付购物费用的问题亟待解决。对于实体超市来说,他们面临着网上购物的竞争压力,作为超市经理,他们要降低成本&…

如何自启动MySQL服务与解决MySQL字符集问题

1、自启动mysql服务 (1)查看mysql是否自启动(默认自启动) systemctl list-unit-files|grep mysqld.service (2)如不是enabled可以运行如下命令设置自启动 systemctl enable mysqld.sercice2、字符集…

SpringBoot 博客网站

SpringBoot 博客网站 系统功能 登录注册 博客列表展示 搜索 分类 个人中心 文章分类管理 我的文章管理 发布文章 开发环境和技术 开发语言:Java 使用框架: SpringBoot jpa H2 Spring Boot是一个用于构建Java应用程序的开源框架,它是Spring框架的一…