数据库相关面试重点知识

一、Mysql索引

1.索引的本质

索引是帮助 Mysql 高效获取数据的排好序数据结构。

索引的数据结构:

  • 二叉树
  • 红黑树
  • Hash表
  • B-Tree(B+Tree) 

Question:为什么加入索引之后效率就会变高呢? 

以上图为例,如果不依靠索引,我们直接查找值为 89 的元素, 那就是一行接着一行。这种情况下,每一次查询比对,都要对磁盘进行依次 I/O 操作,总共要进行 6 次 I/O 才能查到,效率是非常低的!

但如果我们依靠索引来查询,底层假设由二叉树进行存储。K 是 col2 的值,V 是 col2 所在这一行记录的磁盘文件地址。

这时,我们查询的话,先查找根节点 34,89 比 34 大,去右子树上找,然后就找到了。一共就花费了 2 次 I/O 操作进行比对,效率就会比较高。

2.B+Tree 结构

(1)二叉树 OUT

Question1:可能大多数人都知道,索引的底层最终选择的是 B+ 树,那么它为什么不选择二叉树呢?

原因:如果 K 的值已经高度有序,这时二叉树就会退化为链表,查询一个值,还是一个一个去取出对比,对磁盘进行 I/O 操作(索引也是存储在磁盘中的),效率就还是会很低,和全表扫描没区别,况且索引还要占空间。

(2)红黑树 OUT

Question2:红黑树的查询效率要优于二叉树,那么它为什么也没有选择红黑树呢?

我们知道,HashMap 底层用的就是红黑树,它的增删改查性能都很好,为什么设计索引时也放弃了它呢?

Java集合进阶_java中add的使用方法-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/xpy2428507302/article/details/139444333?spm=1001.2014.3001.5501

原因:从图中就可以看出来了,红黑树的层级比较高。因此,在较少数据量的情况下,红黑树的查询效率还可以。然而实际工作中,数据库中的数据都是几万、几十万甚至几百万的数据量。

假设存储了 500 万个索引数据,那么此时红黑树的层级就会非常高。存放 500 万个数据之后,红黑树的高度大约是 h = 44。如果很不巧,我们查询的元素落在最底层的叶子节点,我们就得做 44 次的查询对比,和磁盘进行 44 次的 I/O 交互,这个效率显然也就不高了。

(3)B树 OUT

综上,我们希望在进行存储大数据量数据时,数的高度尽量越小越好。 

原来的二叉树或者红黑树,一个节点只存储一个数据。

而在 B 树中,一个节点可以有许多个数据,并且它们按序排列起来。不仅如此,原来二叉树中每个节点最多有两个分支,而 B 树中,每个节点可以有很多很多分支。

 B-Tree:

  •  叶节点具有相同的深度,叶节点的指针为空
  • 所有索引元素不重复
  • 节点中的数据索引从左到右递增排列

查找的时候, 先从根节点中出发。因为节点中的所有数据都是有序排列的,按照二分查找就可以迅速定位到(节点内的二分查找会先将该节点的所有数据加载到内存中,在内存中进行二分查找,速度非常快)

如果没查到,就根据数据节点所在的区间,来到下一级分叉的节点,继续这个过程。

因为分叉很多,所以 B 树会变得非常扁平化,即便是非常大的数据量,也只需要很少几次 I/O 访问就能完成查找。

Question3:为什么索引最终也没有使用 B 树呢?

B 树虽然好,但它也存在一些问题:

查询效率不太稳定,有些在根节点或者根节点附近就能找到,搜索起来就很快。有些在叶子节点上,那查询起来就嫩慢。

另外,它也不适合用来做范围查找,因为数据散落在不同的节点上,要查询某个范围的数据,就需要在不同层级的节点见进进出出。 

(4)B+ 树 PASS

B+ Tree:

  • 非叶子节点不存储 data,只存储索引(冗余),可以放更多的索引
  • 叶子节点包含所有索引字段
  • 叶子节点用指针连接,提高区间访问的性能

B+ 树在 B 树基础上做了进一步优化,将数据全部放在叶子节点上。这样不管查询哪个数据,最终都要走到叶子节点,从而解决了查询性能不稳定的问题。

其次,上面的非叶子节点都不存储数据了,腾出的空间用来存储指向其他节点的指针。

这样一来,每个节点能够存储的索引数据就更多了,每个节点产生的分支也就更多了。整棵树变得更加扁平,进一步减少了 I/O 的次数。

最后,将所有的叶子节点用指针进行连接,就可以解决范围查询的问题了。

(5)B+ 树效率分析

Mysql 中最小的存储单元是 Page(页),一个页占用的存储空间默认是 16 KB,相当于每次 I/O 操作都可以缓存 16 KB 的数据。即每个节点默认的大小是 16 KB。

假设我们的索引元素是 BigInt 类型,占 8 B。而索引指针占用的字节默认是 6 B。

一个 Page(16KB) / [ key 大小(8B) + 指针(6B) ] = 1170 条

也就是,每个节点可以存储 1170 个索引元素,同时又有 1170 个分支,每个分支又可以存储 1170 个索引元素。

我们假设数据库表中一行记录是 512 B,那么一个 Page 可以存储:

一个 Page(16KB) / 每条记录大小(512B) = 32 条

所以,仅是二级索引,所有叶子节点中可以存储:

1170(根节点) * 1170(中间节点) * 32(叶子节点) = 43,804,800 条数据 

一共 4 千万条数据,B+树的高度却只是 3。这说明,面对一张存储 4 千万条数据的表,我们查询其中一条数据,也仅仅只需要 3 次磁盘 I/O 操作,效率是非常恐怖的。

然而,在Mysql 较高的版本中,都会将非叶子节点的索引提前存储在内存中的,这意味着查找的时候,只需要进行一次 I/O 操作读取叶子节点而已。

3.MyISAM存储引擎索引实现

MyISAM 索引文件和数据文件是分离的(非聚集)

对于 MyISAM 存储引擎,它的表在底层事实上会在磁盘中存储以下 3 个文件:

假设我们现在有一条 SQL 语句:select * from t where Col1 = 30;

底层执行过程:

① 先看查询 where 条件中的属性是不是索引列;

② 如果是索引列,去 MYI 文件中去定位到这个条件值(上面 B+ 树的查询流程);

③ 定位到了之后,拿到索引元素中 data 存储的磁盘文件地址 0xF3(MyISAM 中叶子节点内索引元素的 data 存储的是磁盘文件地址),根据这个地址去 MYD 文件中快速找到对应地址所在行的记录加载出来(称为:回表)

4.InnoDB存储引擎索引实现

InnoDB索引实现(聚集):

  • 表数据文件本身就是按 B+Tree 组织的一个索引结构文件
  • 聚集索引:叶节点包含了完整的数据记录

 对于 InnoDB 存储引擎,它的表在底层会在磁盘中只存储以下 2 个文件:

对于主键索引,存储方式如下:

可以看到,与 MyISAM 明显不同的就是,其叶子节点内索引元素的 data 存储的是完整的数据。

所以,很显然 InnoDB 的效率要比 MyISAM 高,MyISAM 由于是非聚集的,索引文件和数据文件分开存储,所以要多一次回表操作,而 InnoDB 就直接可以拿到完整数据。


对于非主键索引,存储方式如下:

 叶子节点内索引元素的 data 存储的是其主键值。

所以其流程是:先通过非主键索引(二级索引)中找到对应索引元素的主键值,再去主键索引中根据主键值定位到对应索引元素,获取完整数据。

聚集(聚簇)索引和非聚集索引:

两者不是一种指单独的索引类型,而是一种数据存储方式。

  • 聚集索引:叶节点包含了完整的数据记录
  • 非聚集索引:叶子节点没有存储数据,即索引文件和数据文件是分离的

所以,InnoDB 中的非主键索引(二级索引)其实也就是一种非聚集索引。

Question:为什么建议 InnoDB 表必须建主键,并且推荐使用整型的自增主键?

InnoDB 表在设计时,其数据必须要依靠一个 B+ 树进行组织,所以如果这个表已经有了主键,就会根据这个主键来进行维护整张表的 B+ 树的结构,来存储表的数据。

如果不建主键,这个表怎么进行维护呢?

  • 它会先找这张表中第一个唯一索引()放到 B+ 树中进行维护。
  • 如果这张表没有创建唯一索引(create unique index),在表中其实存在一个隐藏列:ROW_ID,它是 Mysql 在底层帮我们维护的唯一的一列数据。此时,InnoDB 就会使用这个 ROW_ID 作为索引来构建 B+ 树进行维护。

所以,如果使用 ROW_ID 来进行构建,一方面会给 mysql 带来很大的压力,本来数据库的性能就很紧张。其次,ROW_ID 是隐藏的,并不直接开放给我们查看和使用。

所以,建议 InnoDB 表必须创建一个主键,因为查找时大多都是使用主键的,这样效率会更高。

为什么推荐选择整型作为主键类型呢? 

根据索引在查找的过程中,会涉及很多的比较操作,如果使用整型比较大小就会很快。而如果使用 UUID 进行比较,它会从左往右依次将每个字符转成 ASCII 码进行比较大小,效率就会很慢。

同时,用整型进行存储肯定比用 UUID 字符串进行存储时,存储空间更小。

为什么主键要是自增的呢?

在前面曾提到,索引其实还有另一种存储结构:hash 表

Hash:

  • 对索引的 key 进行一次 hash 计算就可以定位出数据存储的位置
  • 很多时候 Hash 索引要比 B+ 树索引更高效

那为什么更多使用 B+ 树来进行存储索引,而不是使用 hash 表呢?

缺点: 

① hash冲突问题:会形成很长的一个链表,如果元素位于表尾,遍历次数就会比较多

② 仅能满足 “=”,"IN",不支持范围查询

对于 Hash 冲突问题,Mysql 其实并不是很严重,其内部有很多针对 hash 冲突的优化算法

对于范围查询问题,这才是最主要的。hash 表可以迅速定位到某一个元素,但无法查找比它大或者小的元素。比如:想要查询 name > "Tom" 的元素,就没办法去查询了。

而对于 B+ 树,它的叶子节点是一个双向链表,而且是有序进行存储的。

所以当查询某一个范围的数据,比如:查群 id >= 16 and id < 23,只需要定位到 16 索引元素,从左往右进行遍历直到小于 23 即可。 

那回到之前的问题,为什么必须要求主键自增呢?

因为叶子节点中的所有索引元素是有序排列的,即使主键不是自增的,但在 B+ 树中,它还是得按照从小到大的顺序进行排序,以便后续支持等值查找和范围查找。

如果主键不是按自增进行插入数据的,那么就有可能会改动原有的 B+ 树结构。

比如,对于 4阶 B+ 树,每个节点最多只能存储 3 个元素。最右边的叶子节点已经插入了 5、6、8三个元素,已经满了。

此时,如果插入 7 这个元素,就会打破原有的 B+ 树结构,引发分裂。

最右边的叶子节点就会分裂成两个叶子节点,B+ 树的结构结构还会进行调整,这些额外的操作,是十分耗费性能的!

而如果按照递增顺序插入,就会很少出现这种情况,大部分只要往后面去放就行。不会时不时发现已经存满了,引发分裂的情况。所以,使用自增,是为了对插入数据时效率更高。

综上,推荐使用自增的整型主键作为索引,而不是使用 UUID,因为 UUID 是随机的,并不是按顺序生成的,就会引发上述问题。

对于分库分表时,自增 ID 会不会重复呢?

我们往往也采用雪花算法等一系列优化的算法,确保主键是趋势递增的。比如之前 Redis 中对于订单 id 生成全局唯一 ID。

Redis实战篇(三:优惠券秒杀及优化)_抢券redis-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/xpy2428507302/article/details/144374215?spm=1001.2014.3001.5501

5.联合索引(复合索引)

 联合索引的底层存储结构是什么样子呢?

对于由 name,age,position 三者构成的联合索引,会先按 name 大小进行排序,name 相同再按 age 进行排序,age 还相同最后按 position 进行排序。

联合索引从本质上来说也属于非聚集索引,所以它的叶子节点内索引元素的 data 存储的值为主键值。

所以,先通过联合索引定位到对应索引元素的位置,在通过 data 中的主键值去聚集索引中查询,获取完整数据。

Test:对于以下三条 SQL 查询,哪些会走联合索引?

联合索引遵循最左前缀法则:查询从索引的最左前列(name)开始,并且不跳过索引中的列。

所以,只有第一条 SQL 查询可以满足,走联合索引,后面两条均不满足。

为什么必须要遵循最左前缀法则?

以第一条 SQL 查询为例:

它会先去查找 name = "Bill" 的索引元素,接着再根据 age = 31 找到对应元素,往后遍历,如果不满足就停止。


而以第二条 SQL 查询为例,如果走联合查询,就会先找到 age = 30 的索引元素。

此时它在向后遍历,发现下一个元素 age = 31,不满足就停止。在继续判断当前元素 position 是否等于 "dev",发现相等就直接拿主键值去聚集索引中找完整数据返回。

显然,这是不合理的,因为后面还有 age  = 30 的元素,它没有遍历,提前就停止了。

所以,必须要遵循最左前缀法则的原理一看便知:只有在保证第一个索引相等的情况下,第二个索引的大小顺序才有意义。如果不从第一个索引列开始,那么直接看第二个索引的排列就是无序的,没办法直接按顺序查找返回,还是得全表扫描。

同理,对于:

 EXPLAIN SELECT * FROM employee WHERE name = 'Bill' AND position = 'dev';

即使从第一个索引列开始,但是跳过了中间列,也是一样的。不看第二个索引列,直接看第三个索引的排序也是无序的。

此时虽然会走联合联合索引,但只会部分使用联合索引(只利用了索引中的第一列 name),但对 position 的过滤是通过全表扫描完成的

扩展:Mysql 索引规约

超过三个表禁止 join!即使双表 join 也要注意索引、sql性能。

为了满足这个规约,我们就不得不将原来的一条多表关联的 sql 语句进行拆分,拆分成多条 sql 语句。同时,在 java 的业务就会变得复杂,要执行多次 sql,还有组装数据。

表关联时,如果数据量比较大,即便走了索引,底层也有很复杂的连接运算,导致执行效率会很低,而且不好优化。所以,虽然拆分后会变得很复杂,但是效率会提高很多,这是可以接受的。

因为 mysql 的性能是存在瓶颈的,想扩容是不容易的。但对于 java 来说,它做扩容是很简单的。

二、Mysql事务 

1.事务ACID特性 

 事务:一组操作要么全部成功,要么全部失败,目的是为例保证数据最终的一致性。

(1)原子性 

原子性(Atomicity):当前事务的操作要么同时成功,要么同时失败。原子性由undo log日志来保证

在每次进行增删改时,都需要把对应的 undo log 记录下来。通常一行数据 1 次改动,会对应一条 undo log。某些情况下的更新操作,可能对应 2 条。

可以认为当 delete 一条记录时,undolog 中会记录一条对应的 insert 记录,反之亦然。当 update一条记录时,它记录一条对应相反的 update 记录。

比如我们执行一条update语句:

update user set name = "李四" where id = 1;   ---修改之前name=张三

那么此时 undo log 会记录一条对应的 update 语句【反向操作的语句】,以保证在事务回滚时,将数据还原回去。 

update user set name = "张三" where id = 1;

当一组操作中,有某个操作异常,则会触发回滚动作,反向执行每次操作记录的 undo log,将数据恢复到这些操作之前,等同于所有操作都没有执行。

undo log 日志里面不仅存放着数据更新前的记录,还记录着 RowID、事务ID、回滚指针。

其中事务 ID 每次递增,回滚指针第一次如果是 insert 语句的话,回滚指针为 NULL。第二次update 之后的 undo log 的回滚指针就会指向刚刚那一条 undo log 日志。

依次类推,就会形成一条 undo log 的回滚链,方便找到该条记录的历史版本。

(2)一致性

一致性(Consistency):使用事务的最终目的,由业务代码正确逻辑保证

其他三个特性都是为了一致性所服务的,同时也需要由业务代码逻辑正确保证:一旦发送异常就要往外抛。 

(3)隔离性

隔离性(lsolation):在事务并发执行时,他们内部的操作不能互相干扰

隔离性分为两种:写与写隔离、写与读隔离,分别依靠锁和 MVCC 实现。

在多个事务同时执行时,会有三大明显问题:

① 脏读:A 事务执行过程中,读取到 B 事务还未提交的数据,成为脏读。

② 不可重复读:A 事务执行过程中,若对同一条数据进行两次读取,在这两次读取之间,B事务修改了这条数据,并且进行了完整提交。A事务的两次读取,却读到了两次不同的数据。

③ 幻读:A 事务执行过程中,若对同一个集合数据进行两次读取,在这两次读取之间,B事务在集合中增加或者删除了部分数据。A事务的两次读取,读到了数量不一致的行数据。

又或者,A 事务在执行过程中,先查询数据,发现没有准备插入数据。这期间 B 事务已经将该数据插入,并成功提交。

而此时 A 事务再进行插入,就会失败。A 事务发现这行数据已经存在,接下来继续查询,但由于已经解决了不可重复读的问题,所以两次查询的结构都一样,都是 NULL。

这时就好像出现了幻觉,明明数据库中已经存在该数据,可 A  事务偏偏又读取的 NULL,还无法插入。

(4)持久性

持久性(Durability):一旦提交了事务,它对数据库的改变就应该是永久性的。持久性由 redo log日志来保证。

2.事务底层锁机制

锁的作用:在并发访问时,为了解决数据的一致性、有效性问题

MySQL 中的锁,按照锁的粒度分,分为以下三类:

  • 全局锁:锁定数据库中的所有表。
  • 表级锁:每次操作锁住整张表。
  • 行级锁:每次操作锁住对应的行数据。

(1)全局锁

全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的 DML 的写语句,DDL 语句,已经更新操作的事务提交语句都将被阻塞。

其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。

举例:如果不加全局锁,直接数据库备份

加上全局锁后,在进行数据库备份:

数据库中加全局锁,是一个比较重的操作,存在以下问题:

  • 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
  • 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。 

在 InnoDB 引擎中,我们可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致性数据备份。

mysqldump --single-transaction -uroot -p123456 db01 >db01.sql

(2)表级锁

表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MISAM、InnoDB、BDB等存储引擎中。

对于表级锁,主要分为以下三类:

  • 表锁
  • 元数据锁(meta datalock,MDL)
  • 意向锁
Ⅰ.表锁

对于表锁,分为两类:

  • 表共享读锁 / 表共享锁(read lock)


            一旦加了读锁,自己只能进行读操作,不能进行写操作的。
            对于其他客户端,不会阻塞读操作,但会阻塞其写操作。
  • 表独占写锁 / 表排他锁(write lock)


            一旦加了读锁,自己读、写操作都可以进行。
            对于其他客户端,读、写操作都会被阻塞

语法:

加锁:lock tables 表名... read/write

释放锁:unlock tables / 客户端断开连接

Ⅱ.元数据锁(meta datalock,MDL)

MDL 加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。

MDL锁主要作用是维护表元数据(可理解为:表结构)的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免 DML 与 DDL 冲突,保证读写的正确性。

在 MySQL5.5 中引入了 MDL,当对一张表进行增删改査的时候,加 MDL 读锁(共享);当对表结构进行变更操作的时候,加 MDL 写锁(排他)。

MDL 读锁之间是共享的,即:A 事务和 B 事务都开启后,A事务可以进行增删改查操作,B事务也可以进行增删改查操作。

MDL 写锁与其他 MDL 都互斥,即:A 事务和 B 事务都开启后,A 事务进行增删改查操作后(自动加上 MDL 读锁),B事务此时如果想要修改表结构(alter table ...),就会被阻塞,直到 A 事务提交完成。

我们可以通过下面的 SQL ,来查看数据库中的元数据锁的情况:

select object_type,object_schema,object_name,lock_type,lock_duration from
performance_schema.metadata_locks ;
 Ⅲ.意向锁

为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB 中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。

不使用意向锁的情况:

使用意向锁的情况:

意向锁分为两类:

  • 意向共享锁(IS):由语句 select ... lock in share mode 自动添加。与表锁共享锁(read)兼容,与表锁排他锁(write)互斥。
  • 意向排他锁(IX): 由 insert、update、delete、select...for update 自动添加。与表锁共享锁(read)及表锁排他锁(write)都互斥,意向锁之间不会互斥。

可以通过以下 SQL ,查看意向锁及行锁的加锁情况:

select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;

(3)行级锁

行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在 InnoDB存储引擎中。

InnoDB 的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的 锁。对于行级锁,主要分为以下三类:

  • 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行 update 和 delete。在RC 、 RR 隔离级别下都支持。

  • 间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行 insert ,产生幻读。在 RR 隔离级别下都支持。

  • 临键锁( Next-Key Lock ):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙 Gap 。在 RR 隔离级别下支持。

Ⅰ.行锁

InnoDB 实现了以下两种类型的行锁:

  • 读锁(共享锁、S 锁):select ... lock in share mode
    读锁是共享的,多个事务可以同时读取同一个资源,但不允许其他事务修改
  • 写锁(排它锁、X 锁):select ... for update、update、delete、insert
    写锁是排他的,允许获取排他锁的事务更新数据,会阻塞其他的写锁和读锁

两种行锁的兼容情况如下 : 

常见的 SQL 语句,在执行时,所加的行锁如下:

① 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。

② InnoDB 的行锁是针对于索引加的锁不通过索引条件检索数据,那么 InnoDB 将对表中的所有记录加锁,此时 就会升级为表锁

Ⅱ.间隙锁、临键锁

默认情况下, InnoDB 在 RR(可重复读) 事务隔离级别运行, InnoDB 使用 next-key(临键) 锁进行搜索和索引扫描,以防止幻读。

① 索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁 。

② 索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁。

③ 索引上的范围查询(唯一索引) --> 会访问到不满足条件的第一个值为止。

注意:间隙锁唯一目的是防止其他事务插入间隙,造成幻读现象。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。

3.事务四大隔离级别

(1)介绍

InnoDB 引擎中,定义了四种隔离级别供我们使用,级别越高事务隔离性越好,但性能就越低,而隔离性是由 MySQL 的各种以及 MVCC 机制来实现的。

隔离级别脏读不可重复读幻读脏写(更新丢失
Read Uncommit(读未提交)会出现会出现会出现第二类
Read Commit(读已提交)解决会出现会出现第二类
Repeatable Read(可重复读,默认)解决解决会出现解决
Serializable(串行)解决解决解决解决

更新丢失(Lost Update):

  • 第一类更新丢失:当两个事务更新相同的数据时,如果第一个事务被提交,然后第二个事务被撤销,导致第一个事务的更新也被撤销。
  • 第二类更新丢失:当两个事务同时读取某个记录后分别进行修改提交,造成先提交事务的修改丢失。

(2)可串行化底层原理

对于 select 查询语句,会在后面自动加上 lock in share mode,给这条记录加上共享锁(S 锁),此时其他线程就只能读,因为共享锁之间相互兼容,但修改操作会被阻塞,以此实现可串行化的效果。

对于 update、delete、insert 语句,会自动加一把排他锁(X 锁),此时其他线程什么都做不了,只能被阻塞,因为排他锁和其他锁都互斥,以此实现可串行化的效果

总的来说,可串行化就是利用了行锁中的 S 锁和 X 锁,以实现串行执行的效果。

但是,可串行化这种隔离级别实际开发中很少用,因为效率很低,无法应对高并发场景需求。一般工作中,大多使用 RC 或者 RR 两种隔离级别。

(3)MVCC并发优化机制

MVCC(Multi-Version Concurrency Control)多版本并发控制指维护一个数据的多个版本,使得读写操作没有冲突,就可以做到读写不阻塞且避免了类似脏读这样的问题。

底层实现主要是通过隐藏字段、undo log 日志和 readview。

Ⅰ.隐藏字段(事务id、回滚指针、隐藏主键

隐藏字段:mysql 给每一张表都设置了 3 个隐藏字段:

  • trx_id:事务 id,记录每一次操作的事务 id,是自增的;
  • roll_pointer:回滚指针,指向上一个版本的事务版本记录地址
  • row_id:隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。

Ⅱ.undo log日志
i.介绍

undo log日志:在 insert、update、delete 的时候产生的便于数据回滚的日志

当 insert 的时候,产生的 undo log 日志只在回滚时需要,在事务提交后,可被立即删除。

而 update、delete的时候,产生的 undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。

ii.版本链

undo log 日志会存储老版本数据,在内部形成一个版本链。在多个事务并行操作某一行记录时,记录不同事务修改数据的版本,通过 roller_pointer 指针形成一个链表

举例:

有一张表原始数据为:

DB TRX ID : 代表最近修改事务 ID,记录插入这条记录或最后一次修改该记录的事务 ID,是自增的。

DB_ROLL_PTR :  由于这条数据是新插入的,没有被更新过,所以该字段值为null。

在 undo log 日志中,之前的 insert 语句生成的日志已经在事务提交后被删除了,所以现在undo log 日志中为空。


接下来,有四个并发事务同时在访问这张表。

① 事务 2 执行过程:

当事务 2 执行第一条修改语句时,会记录 undo log 日志,记录数据变更之前的样子。

然后更新记录, 并且记录本次操作的事务 ID,回滚指针,回滚指针用来指定如果发生回滚,回滚到哪一个版本。 

② 事务 3 执行过程:

 当事务 3 执行第一条修改语句时,也会记录 undo log 日志,记录数据变更之前的样子。

然后更新记录,并且记录本次操作的事务 ID,回滚指针,回滚指针用来指定如果发生回滚,回滚到哪一个版本。

③ 事务 4 执行过程:

当事务 4 执行第一条修改语句时,也会记录 undo log 日志,记录数据变更之前的样子。

然后更新记录,并且记录本次操作的事务 ID,回滚指针,回滚指针用来指定如果发生回滚,回滚到哪一个版本。

最终我们发现,不同事务或相同事务对同一条记录进行修改,会导致该记录的 undolog 生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录

Ⅲ.readview

readview:解决的是一个事务查询选择版本的问题,在内部定义了一些匹配规则和当前的一些事务 id 应该访问哪一个版本的数据。不同隔离级别的快照读不一样,最终访问的数据也不一样。

  • 如果是 RC(读已提交)隔离级别:语句级快照,每次快照读时都会生成 readview
  • 如果是 RR(可重复读)隔离级别:事务级快照,只有第一次快照读会生成 readview

简单的 select (不加锁)就是快照读,读取的是记录数据的可见版本,不一定是最新数据,有可能是历史数据。

当前读读取的数据是当前数据的最新版本,在读取的时候保证数据不被其他并发事务所修改。

select ... lock in share mode(共享锁), select ...for update、update、 insert、delete(排他锁)都是一种当前读。

ReadView 中包含了四个核心字段:

在 readview 中也规定了版本链数据的访问规则:

 (4)RC 底层原理

RC 隔离级别下,在事务中每一次执行快照读时生成 ReadView。

在事务 5 中,查询了两次 id 为 30 的记录,由于隔离级别为 Read Committed,所以每一次进行快照读都会生成一个 ReadView,那么两次生成的 ReadView 如上。 

那么这两次快照读在获取数据时,就需要根据所生成的 ReadView 以及 ReadView 的版本链访问规则, 到 undolog 版本链中匹配数据,最终决定此次快照读返回的数据。

以下是这条记录的版本链:

每一次快照读,都会从 undo log 的版本链,从链表头到链表尾挨个进行匹配。

对于第一次快照读:

trx_id = 链表头中 DB_TRX_ID = 4

我们发现,经过访问规则比对,4 个规则全部不成立,说明 trx_id = 4 的这条记录不是我们要快照读的记录。

此时,继续往 undo log 版本链的下一个节点走,trx_id = 3

同样,4 个规则也全部不成立, 说明 trx_id = 3 的这条记录不是我们要快照读的记录。

此时,继续往 undo log 版本链的下一个节点走,trx_id = 2

我们发现,第二个规则成立了,说明 trx_id = 2 的这条记录正是我们要快照读的记录!

此时,就会把 undo log 日志中 trx_id = 2 的日志直接返回。

 对于第二次快照读:

trx_id = 链表头中 DB_TRX_ID = 4

我们发现,经过访问规则比对,4 个规则全部不成立,说明 trx_id = 4 的这条记录不是我们要快照读的记录。

此时,继续往 undo log 版本链的下一个节点走,trx_id = 3

我们发现,第二个规则成立了,说明 trx_id = 3 的这条记录正是我们要快照读的记录!

此时,就会把 undo log 日志中 trx_id = 3 的日志直接返回。

(5)RR 底层原理

RR 隔离级别下,仅在事务中第一次执行快照读时生成 ReadView,后续复用该 ReadView。 

RR 是可重复读,在一个事务中,执行两次相同的 select 语句,查询到的结果是一样的。

在事务 5 中,查询了两次 id 为 30 的记录,由于隔离级别为 Repeatable Read,所以只会在第一次快照读时生成 ReadView,生成的 ReadView 如上。

匹配过程和上面一样,这里就不重复介绍了。 最后,两次查询读取的都是 trx_id = 2 的记录。

Tips:

前面说了,RR 隔离级别存在丢失更新,也就是脏写问题。这个问题在开发中我们如何去解决呢?

① 使用乐观锁:版本号法 / CAS 法

② 不在 java 层面中解决,在数据库层面解决

  • java 层面:
    • 取出数据:count=10
    • 数据计算:count--;-->count = 9
    • mapper 层和数据库交互:update t set count  =  9 where id = 1;
  • 数据库层面:
    • update t set count  =  count - 1 where id = 1;

 Question:查询操作方法需要使用事务吗?

 对于只有一个查询操作,那加不加事务都一样。

对于多个查询操作,就要认真考虑了。

对于上述两条 SQL 查询语句,加了事务,和不加事务的结果是截然不同的。

设想一下,如果对于出报表的业务场景来说,是选择加事务,还是不加事务呢?

没错,加事务更好,因为虽然前者数据不是最新的,但是它可以保证查询到的多个数据,都是在同一时间维度上的。对于出报表,我们肯定选择同一时间点的数据,而不是不同时间点的数据。

所以,对于一些传统的软件公司,常用 RR 这种隔离级别,因为经常要出各种报表。

对于互联网大厂,常用 RC 这种隔离级别,因为 RC 效率要比 RR 高一点。而且一些前端的网站对于并发性能要求比较高,也没有什么出报表业务,所以 RC 是非常合适的。对于出报表的一些业务,都是借助数据中心实现,这时候可能会用 RR。

(6)BufferPool缓存机制

Redo log:记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。

该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。

当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。

如果没有redolog,可能会存在什么问题的?

我们知道,在 InnoDB 引擎中的内存结构中,主要的内存区域就是缓冲池,在缓冲池中缓存了很多的数据页。

当我们在一个事务中,执行多个增删改的操作时, InnoDB引擎会先操作缓冲池中的数据。如果缓冲区没有对应的数据,会通过后台线程将磁盘中的数据加载出来,存放在缓冲区中,然后将缓冲池中的数据修改,修改后的数据页我们称为脏页。

而脏页则会在一定的时机,通过后台线程刷新到磁盘中,从而保证缓冲区与磁盘的数据一致。

但是缓冲区的脏页数据并不是实时刷新的,而是一段时间之后将缓冲区的数据刷新到磁盘中。假如刷新到磁盘的过程出错了,而提示给用户事务提交成功,而数据却没有持久化下来,这就出现问题了,没有保证事务的持久性

在InnoDB中提供了一份日志  redo log,通过 redo log 就能解决这个问题。

有了 redo log 之后,当对缓冲区的数据进行增删改之后,会首先将操作的数据页的变化,记录在redo log buffer中。在事务提交时,会将 redo log buffer 中的数据刷新到 redo log file 磁盘文件中。

过一段时间之后,如果刷新缓冲区的脏页到磁盘时,发生错误,此时就可以借助于 redo log file 进行数据恢复。mysql 会先从 redo log file 中的数据加载到缓存中,后续再将缓存中的脏页写到磁盘,这样就保证了事务的持久性。

而如果脏页成功刷新到磁盘或者涉及到的数据已经落盘,此时 redo log file 就没有作用了,就可以删除了,所以每隔一段时间就会去清理 redo log 日志,存在的两个 redo log file 是循环写的。

Question:那为什么每一次提交事务,要刷新 redo log 到磁盘中呢,而不是直接将 buffer pool 中的脏页刷新到磁盘呢?

因为在业务操作中,我们操作数据一般都是随机读写磁盘的,而不是顺序读写磁盘(涉及多个表[.ibd文件],在磁盘上写经常要跳来跳去)。

而 redo log 在 往磁盘文件中写入数据,由于是日志文件,所以都是顺序写的。顺序写的效率,要远大于随机写。这种先写日志的方式,称之为  WAL(Write-Ahead Logging)。

三、分库分表

在分布式架构中,数据库往往都会采用主从结构,主节点负责写,从节点负责读。

因为实际场景中,用户大部分操作都是读,小部分操作是写,这样构建是十分合理的。

但是对于一些互联网大厂,或者在某些特殊时期,比如淘宝的双十一。这时即使是写操作,每天也可能几十万甚至几百万条,这对主节点的压力是相当大的,就可能撑不住。

所以,这时我们就需要进行分库分表了。

分库分表分为两种:垂直拆分和水平拆分

一般来说就是,垂直拆库,水平拆表

1.分表方式

(1)Hash分表

通过 Hash 思想,对 id 进行取模运算,从而存储在不同的表上。

例如:12 % 4 = 0,所以 id =12 的数据存到 t_order_0 表中。 

但是随着数据量继续增大,终有一天我们要继续拆分成更多的表,进行扩容。 

在这种情况下,我们要查找 id = 12 的数据,可计算结果为 12 % 8 = 4,最后会去 t_order_4 表中查询这条数据。但是很显然,这条数据之前存储在 t_order_0 表中,我们肯定拿不到。

所以,我们就不得不将之前的数据进行迁移。但面对如此庞大的数据量,迁移是非常痛苦的,而且是十分耗时的。而且一般来说,我们在公司中不可能暂停系统如此长的时间来进行数据迁移。

(2)range 分表

可以按照范围,将不同范围的数据存在不同的表上。

例如:计算 id =12 在哪个范围内,然后将 id =12 的数据存到 t_order_0 表中

这样一来,就可以解决数据扩容问题,不需要进行数据迁移。后续如果要拆分更多的表,直接计算存就行了。

但是这种方式存在另一个问题:热点问题。如果某一个范围内的数据访问频率比较高,其中一张表就会承担高并发的压力,而其他范围的表却不能很好的利用。

而这个问题,Hash 分表的方式却恰好可以规避。

(3)ShardingSphere分库分表

Apache ShardingSphere 是一款开源的分布式数据库中间件生态系统,旨在将现有的数据库转变为分布式数据库解决方案。它通过数据分片、读写分离、分布式事务和数据加密等功能,增强了原有数据库的能力。

ShardingSphere 提供了 ShardingSphere-JDBC 和 ShardingSphere-Proxy 两种核心组件,分别适用于不同的应用场景。

这里不再过多介绍,感兴趣的可以自行学习。

2.分库分表后关联查询

我们知道,一个数据库的压力毕竟是有限的,总有一天分表不再能解决问题。所以我们不得不水平分库。

通常在一些互联网大厂公司中, 像订单表会有很多个数据库,加起来成百上千张表都有可能。

如果按之前的方法分表,就可能会出现,同一个用户下的订单散落在不同库的不同表中。这样,后续进行关联查询,就会跨很多数据库很多表去查询,最后组合起来,这显然是不可能的,效率极低!

所以,我们要合理的选择分库分表策略,以电商为例:

用户端:

我们希望一个用户下的订单会在一个数据库,甚至一张表中,这样查看用户订单就不会跨数据库查询。所以,很容易想到,我们不应该使用 orderId 进行取模,而应该选择 userId。

那又会牵扯到一个问题,如果我要根据 orderId 去查询订单,怎么去查呢?

一般来说,登录之后 userId 肯定是能够获取到的,它会被存在某个地方(如:ThreadLocal)。这样就可以根据 userId 先去计算在哪个数据库表中,在用 orderId 去对应表中查询。

如果某些情况下,只能由 orderId 去查的话,例如:我们会将 userId 中的后4位拼接到 orderId 后。存储时,取模用这后 4 位进行计算,然后存到对应数据库表。取数据时,我们再根据 orderId 获取到这后 4 位,计算出对应数据库表位置,进行查询。

商家端:

如果按上述思维,商家端应该是按商家ID,即 businessId 去计算的。但这样商家如果想看自己所有卖出去的订单,刚才已经规定了订单用 userId 计算,此时商家就没办法通过 userId 进行获取了,就必须要跨多个库多张表了。

所以一般来说,在互联网公司中,下单时会通过 MQ 开启一个异步线程将商家端的数据会单独再存一份,通过 businessId 进行分库分表。

运营管理端:

查全量:

  • 非实时 --> 数据仓库
  • 实时--> ElasticSearch

3.线上单库迁移到多库多表

假设原本的系统是单库单表的,现在系统更新,变成了 8 库 8 表,我们如何进行数据迁移?

先来考虑最简单的,直接将系统暂停,我们提前开发一个数据迁移系统。每次从旧数据库中读 1000 条数据,写到新数据库中。

同时,写的过程中也需要构建一些迁移批次表、迁移数据明细表,方便后续如果出错,可以及时找到错误。

上述是停机的情况下,但实际工作中,不可能会让系统长时间停止运行,在不停机的情况下,我们如何解决数据迁移?

如果不停机进行迁移,就有可能在迁移过程中,又有新的请求对旧数据库中的数据进行修改,而在这之前这条数据已经完成迁移。那么这样一来,迁移后的新数据库中的数据反而没被修改,是旧数据。我们总不可能再从头来一遍,再进行一次迁移。

数据库中存在一个 bin log 日志,用来记录所有对数据库的修改操作。我们可以通过 canal 组件(阿里开源框架)去监听这个 bin log 日志,解析后写到 ES 中,最后写到新数据库中去。

这样就会在数据迁移的过程中,同时对旧数据库和新数据库进行修改。

相当于下面的迁移是在做全量的迁移,而 binlog 相当于做增量的迁移。

但这样做就可以了吗?不会存在问题吗?

在这个过程中操作都是并发执行的,就可能存在并发问题。

  • 对于 insert 操作,可能会先插入到新数据库中,再插入到旧数据库中,旧数据库进行数据迁移的时候就会发现数据已经存在,出现主键冲突。或者,先插入到旧数据库中,完成迁移,再插入到新数据库中,此时也会发现已经存在,出现主键冲突。
     
  • 对于 delete 操作,可能会先删除新数据库中的数据,而这时还没有完成数据迁移,所以删除了个寂寞。接着下一刻,数据完成迁移,新数据库中又存在了该数据。可是马上旧数据库又执行了删除操作,旧数据库中就不存在该数据。此时,就会出现旧数据库中没有,而新数据中有这个垃圾数据的情况,长此以往,垃圾数据越来越多,数据库可受不了。
     
  • 对于 update 操作,可能会先更新新数据库中的数据,而这时还没有完成数据迁移,所以更新了个寂寞。接着下一刻,数据完成迁移,新数据库中又存在了该(旧)数据。可是马上旧数据库又执行了更新操作,旧数据库中就修改了该数据,变成了新数据。此时,就会出现,新数据库中是旧数据,而旧数据库中是新数据的情况。

在这三者中,insert 操作还好,只是发生冲突,不会产生什么数据一致性问题。但是后面两个,旧很麻烦,如何解决呢?

我们很容易想到,如果新数据库修改返回的是空,说明它没有修改成功,我们将来让他过段时间再修改一次就可以了。所以,我们可以通过 RocketMQ 的延迟队列,让它过一段时间后再重新去执行。

这样一来,通过这种方式进行数据迁移,当旧数据库数据全部迁移完成后,将旧数据库和新数据库进行一次比对校验(比如:判断记录的总数量,抽查抽检),没有问题之后,新系统就可以上线了。

4.灰度发布

数据迁移完成后,我们是直接下架旧系统,上线新系统吗?

如果新系统出现了一些测试阶段没有发现的 bug,导致系统崩溃,那不就完了。

所以,一般我们采用灰度发布,保留原来的旧系统,开放新系统的少部分服务器。这样就会有少部分请求会通过负载均衡打到新系统上,如果一段时间内没有出现问题,就可以完全下架旧系统,上线全部新系统了。

但这样也会出现少部分问题,比如一条 insert 请求被分发到了新系统的服务器中,此时新数据库就会插入数据。而下一个 select 请求查询该数据被分发到了旧系统的服务器中,但旧数据库中却没有该数据(旧数据库会一直往新数据库迁移,新数据库不会往旧数据库中进行迁移),此时就会出现问题。

所以,一般情况下,灰度发布的持续时间不会太长,偶尔一些错误是可以容忍的。

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

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

相关文章

ansible-api分析(VariableManager变量)

一. 简述&#xff1a; ansible是一个非常强大的工具&#xff0c;可以支持多种类型(字符,数字,列表&#xff0c;字典等)的变量。除了有大量的内置变量及fact变量&#xff0c;也可以通过多种方式进行变量自定义 。不同方式定义的变量&#xff0c;优先级也不太一样&#xff0c;之…

【Uniapp-Vue3】image媒体组件属性

如果我们想要在页面上展示图片就需要使用到image标签。 这部分最重要的是图片的裁剪&#xff0c;图片的裁剪和缩放属性&#xff1a; mode 图片裁剪、缩放的模式 默认值是scaleToFill 我将用两张图片对属性进行演示&#xff0c;一张是pic1.jpg&#xff08;宽更长&#xf…

VisionPro软件Image Stitch拼接算法

2D图像拼接的3种情景 1.一只相机取像位置固定&#xff0c;或者多只相机固定位置拍图&#xff0c;硬拷贝拼图&#xff0c;采用CopyRegion工具实现 2.一只或多只相机在多个位置拍照&#xff0c;相机视野互相重叠&#xff0c;基于Patmax特征定位后&#xff0c;无缝 拼图&#xff…

“多维像素”多模态雷视融合技术构建自动驾驶超级感知能力|上海昱感微电子创始人蒋宏GADS演讲预告

2025年1月14日&#xff0c;第四届全球自动驾驶峰会将在北京中关村国家自主创新示范区展示交易中心-会议中心举行。经过三年的发展&#xff0c;全球自动驾驶峰会已经成长为国内自动驾驶领域最具影响力、规模最大的产业峰会之一。在主会场下午的城市NOA专题论坛上&#xff0c;上海…

C语言初阶习题【25】strcpy的模拟实现

1. 首先先调用下库函数&#xff0c;看它实现了什么 2. 我们自己实现一个strcpy函数 3. 改进1 把*destnation和source 写上去&#xff0c;使用后置 4. 改进2 这里直接把赋值操作放到了while的判断条件里面&#xff0c;然后while循环语句什么都不做&#xff0c;放了一个空语句…

使用 SQL 和表格数据进行问答和 RAG(6)—将指定目录下的 CSV 或 Excel 文件导入 SQLite 数据库

将指定目录下的 CSV 或 Excel 文件导入 SQLite 数据库。以下是详细代码逻辑&#xff1a; 1. 类结构 该类包含三个主要方法&#xff1a; _prepare_db&#xff1a;负责将文件夹中的 CSV 和 XLSX 文件转换为 SQL 表。_validate_db&#xff1a;用于验证 SQL 数据库中创建的表是否…

设计模式 行为型 策略模式(Strategy Pattern)与 常见技术框架应用 解析

策略模式&#xff08;Strategy Pattern&#xff09;核心思想是将算法的实现从使用该算法的类中分离出来&#xff0c;作为独立的对象&#xff0c;通过接口来定义算法家族&#xff0c;这样就可以很容易地改变或扩展算法。通过这种方式&#xff0c;可以避免在客户端代码中使用大量…

如何操作github,gitee,gitcode三个git平台建立镜像仓库机制,这样便于维护项目只需要维护一个平台仓库地址的即可-优雅草央千澈

如何操作github&#xff0c;gitee&#xff0c;gitcode三个git平台建立镜像仓库机制&#xff0c;这样便于维护项目只需要维护一个平台仓库地址的即可-优雅草央千澈 问题背景 由于我司最早期19年使用的是gitee&#xff0c;因此大部分仓库都在gitee有几百个库的代码&#xff0c;…

B+树的原理及实现

文章目录 B树的原理及实现一、引言二、B树的特性1、结构特点2、节点类型3、阶数 三、B树的Java实现1、节点实现2、B树操作2.1、搜索2.2、插入2.3、删除2.4、遍历 3、B树的Java实现示例 四、总结 B树的原理及实现 一、引言 B树是一种基于B树的树形数据结构&#xff0c;它在数据…

大纲笔记幕布的替换

文章目录 前言类似的大纲软件探索 DynalistLogseq通过国内代码仓库建立 Git 仓库Logseq 的使用PC 端安卓端Git 操作Termux git 步骤Termux 的桌面组件&#xff1a;Termux widget 报错参考 前言 之前我一直用幕布&#xff0c;买了三年&#xff0c;奈何要过期了&#xff0c;又三…

MoEs and Transformers 笔记

ref:https://huggingface.co/blog/zh/moe#%E7%94%A8router-z-loss%E7%A8%B3%E5%AE%9A%E6%A8%A1%E5%9E%8B%E8%AE%AD%E7%BB%83 MoEs and Transformers Transformer 类模型明确表明&#xff0c;增加参数数量可以提高性能&#xff0c;因此谷歌使用 GShard 尝试将 Transformer 模型…

ubuntu为Docker配置代理

终端代理 我们平常在ubuntu终端中使用curl或git命令时&#xff0c;往往会很慢。 所以&#xff0c;首先需要给ubuntu终端环境添加代理。 查看自身那个软件的端口号&#xff0c;我这里是7890。 sudo gedit ~/.bashrcexport http_proxyhttp://localhost:7890 export https_pr…

【安卓开发】【Android Studio】项目构建失败提示【Could not read metadata.bin】解决方法

一、问题说明 在Android Studio中开发安卓项目时&#xff0c;项目构建失败&#xff0c;提示如下&#xff1a; Could not read workspace data from xxx/xxx/&#xff08;某个目录&#xff0c;和gradle有关&#xff09;&#xff1a;could not read ...metadata.bin&#xff08…

EXCEL: (二) 常用图表

10. 图表 134-添加.删除图表元素 图表很少是一个单独的整体&#xff0c;而是由十几种元素/对象拼凑出来的。 学习图表就是学习当中各类元素的插删改。 ①图表中主要元素的定义 图表上的一个颜色就是一个系列。 横轴是分类轴&#xff0c;将每个系列都分为几类。 ②选中图…

sys.dm_exec_connections:查询与 SQL Server 实例建立的连接有关的信息以及每个连接的详细信息(客户端ip)

文章目录 引言I 基于dm_exec_connections查询客户端ip权限物理联接时间范围dm_exec_connections表see also: 监视SQL Server 内存使用量资源信号灯 DMV sys.dm_exec_query_resource_semaphores( 确定查询执行内存的等待)引言 查询历史数据库客户端ip应用场景: 安全分析缺乏…

阿里云发现后门webshell,怎么处理,怎么解决?

当收到如下阿里云通知邮件时&#xff0c;大部分管理员都会心里一惊吧&#xff01;出现Webshell&#xff0c;大概是网站被入侵了。 尊敬的 xxxaliyun.com&#xff1a; 云盾云安全中心检测到您的服务器&#xff1a;47.108.x.xx&#xff08;xx机&#xff09;出现了紧急安全事件…

【大模型】百度千帆大模型对接LangChain使用详解

目录 一、前言 二、LangChain架构与核心组件 2.1 LangChain 核心架构 2.2 LangChain 核心组件 三、环境准备 3.1 前置准备 3.1.1 创建应用并获取apikey 3.1.2 开通付费功能 3.2 获取LangChain文档 3.3 安装LangChain依赖包 四、百度千帆大模型对接 LangChain 4.1 LL…

maven如何从外部导包

1.找到你项目的文件位置&#xff0c;将外部要导入的包复制粘贴进你当前要导入的项目下。 2.从你的项目目录下选中要导入的包的pom文件即可导包成功 注意一定是选中对应的pom文件 导入成功之后对应的pom.xml文件就会被点亮

graylog配置日志关键字邮件Email告警

文章目录 前言一、配置邮箱报警二、邮件告警配置1.设置邮箱告警通知2.设置告警事件 三、告警结果示例 前言 在TO/B、TO/C项目的告警链路中&#xff0c;端口告警、服务器基础资源告警、接口告警等不同类型的告警都扮演着重要角色。这些告警能够帮助团队及时发现潜在的系统问题&…

GraphQL:强大的API查询语言

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…