前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >国内IT外包公司汇总(2024最新版)

国内IT外包公司汇总(2024最新版)

作者头像
沉默王二
发布2024-04-26 16:20:31
850
发布2024-04-26 16:20:31
举报
文章被收录于专栏:沉默王二沉默王二

MySQL

数据库用过哪些,对哪个比较熟?

我经常使用的数据库是 MySQL,它是一个开源的关系型数据库管理系统,现在隶属于 Oracle 旗下。

也是我们国内使用频率最高的一种数据库,我在本地安装的 MySQL 的社区版,最新的 8.0 版本。

MySQL 官网

MySQL索引结构,为什么用B+树?

MySQL 的默认存储引擎是 InnoDB,它采用的是 B+树索引。

那在说 B+树之前,必须得先说一下 B 树(B-tree)。

B 树是一种自平衡的多路查找树,和红黑树、二叉平衡树不同,B 树的每个节点可以有 m 个子节点,而红黑树和二叉平衡树都只有 2 个。

换句话说,红黑树、二叉平衡树是细高个,而 B 树是矮胖子。

二哥的 Java 进阶之路:B 树

好,我继续说。

内存和磁盘在进行 IO 读写的时候,有一个最小的逻辑单元,叫做页(Page),页的大小一般是 4KB。

二哥的 Java 进阶之路:IO 读写

那为了提高读写效率,从磁盘往内存中读数据的时候,一次会读取至少一页的数据,比如说读取 2KB 的数据,实际上会读取 4KB 的数据;读取 5KB 的数据,实际上会读取 8KB 的数据。我们要尽量减少读写的次数

因为读的次数越多,效率就越低。就好比我们在工地上搬砖,一次搬 10 块砖肯定比一次搬 1 块砖的效率要高,反正我每次都搬 10 块(😁)。

对于红黑树、二叉平衡树这种细高个来说,每次搬的砖少,因为力气不够嘛,那来回跑的次数就越多。

是这个道理吧,树越高,意味着查找数据时就需要更多的磁盘 IO,因为每一层都可能需要从磁盘加载新的节点。

用户1260737:二叉树

B 树的节点大小通常与页的大小对齐,这样每次从磁盘加载一个节点时,可以正好是一个页的大小。因为 B 树的节点可以有多个子节点,可以填充更多的信息以达到一页的大小。

用户1260737:B 树

B 树的一个节点通常包括三个部分:

  • 键值:即表中的主键
  • 指针:存储子节点的信息
  • 数据:表记录中除主键外的数据

不过,正所谓“祸兮福所倚,福兮祸所伏”,正是因为 B 树的每个节点上都存了数据,就导致每个节点能存储的键值和指针变少了,因为每一页的大小是固定的,对吧?

于是 B+树就来了,B+树的非叶子节点只存储键值,不存储数据,而叶子节点存储了所有的数据,并且构成了一个有序链表。

用户1260737:B 树

这样做的好处是,非叶子节点上由于没有存储数据,就可以存储更多的键值对,树就变得更加矮胖了,于是就更有劲了,每次搬的砖也就更多了(😂)。

由此一来,查找数据进行的磁盘 IO 就更少了,查询的效率也就更高了。

再加上叶子节点构成了一个有序链表,范围查询时就可以直接通过叶子节点间的指针顺序访问整个查询范围内的所有记录,而无需对树进行多次遍历。

总结一下,InnoDB 之所以选择 B+树是因为:

  • 更高效的磁盘 IO,因为它减少了磁盘寻道时间和页的加载次数。
  • 支持范围查询,与 B 树相比,B+树的叶子节点通过指针连接成一个链表,这使得范围查询变得非常高效。在 B+树上执行范围查询可以简单地从范围的起始点开始,然后沿着链表向后遍历,直到结束点。
  • 查询性能稳定,B+树的所有查找操作都要查到叶子节点,这使得所有的查询操作都有着相同的访问深度,因此查询性能非常稳定。不像某些其他数据结构,如 B 树,其查询性能因为数据存在所有的节点上导致深度不一致,性能不稳定。

聚簇索引和非聚簇索引的区别?

聚簇索引不是一种新的索引,而是一种数据存储方式

三分恶面渣逆袭:聚簇索引和非聚簇索引

在聚簇索引中,表中的行是按照键值(索引)的顺序存储的。这意味着表中的实际数据行和键值之间存在物理排序的关系。因此,每个表只能有一个聚簇索引。例如,在 MySQL 的 InnoDB 存储引擎中,主键就是聚簇索引。

在非聚簇索引中,索引和数据是分开存储的,索引中的键值指向数据的实际存储位置。因此,非聚簇索引也被称为二级索引或辅助索引。表可以有多个非聚簇索引。

这意味着,当使用非聚簇索引检索数据时,数据库首先在索引中查找,然后通过索引中的指针去访问表中实际的数据行,这个过程称为“回表”(Bookmark Lookup)。

举例来说:

  • InnoDB 采用的是聚簇索引,如果没有显式定义主键,InnoDB 会选择一个唯一的非空列作为隐式的聚簇索引;如果这样的列也不存在,InnoDB 会自动生成一个隐藏的行 ID 作为聚簇索引。这意味着数据与主键是紧密绑定的,行数据直接存储在索引的叶子节点上。
  • MyISAM 采用的是非聚簇索引,表数据存储在一个地方,而索引存储在另一个地方,索引指向数据行的物理位置。

说说最左前缀原则

最左前缀原则,也叫最左匹配原则,或者最左前缀匹配原则。

最左匹配原则是指在使用联合索引(即包含多列的索引)时,查询条件从索引的最左列开始并且不跳过中间的列。

如果一个复合索引包含(col1, col2, col3),那么它可以支持 col1col1,col2col1, col2, col3 的查询优化,但不会优化只有 col2 或 col3 的查询。

也就说,在进行查询时,如果没有遵循最左前缀,那么索引可能不会被利用,导致查询效率降低。

为什么不从最左开始查,就无法匹配呢?

比如有一个 user 表,我们给 name 和 age 建立了一个联合索引 (name, age)

代码语言:javascript
复制
ALTER TABLE user add INDEX comidx_name_phone (name,age);

联合索引在 B+ 树中是复合的数据结构,按照从左到右的顺序依次建立搜索树的 (name 在左边,age 在右边)。

三分恶面渣逆袭:联合索引

注意,name 是有序的,age 是无序的。当 name 相等的时候,age 才有序。

当我们使用 where name= '张三' and age = '20' 去查询的时候, B+ 树会优先比较 name 来确定下一步应该搜索的方向,往左还是往右。

如果 name 相同的时候再比较 age。

但如果查询条件没有 name,就不知道应该怎么查了,因为 name 是 B+树中的前置条件,没有 name,索引就派不上用场了。

MySQL的主从复制过程

MySQL 的主从复制(Master-Slave Replication)是一种数据同步机制,用于将数据从一个主数据库(master)复制到一个或多个从数据库(slave)。

广泛用于数据备份、灾难恢复和数据分析等场景。

三分恶面渣逆袭:主从复制

复制过程的主要步骤有:

  • 在主服务器上,所有修改数据的语句(如 INSERT、UPDATE、DELETE)会被记录到二进制日志中。
  • 主服务器上的一个线程(二进制日志转储线程)负责读取二进制日志的内容并发送给从服务器。
  • 从服务器接收到二进制日志数据后,会将这些数据写入自己的中继日志(Relay Log)。中继日志是从服务器上的一个本地存储。
  • 从服务器上有一个 SQL 线程会读取中继日志,并在本地数据库上执行,从而将更改应用到从数据库中,完成同步。

场景题:sql查询很慢怎么排查

三分恶面渣逆袭:发现慢 SQL

定位慢 SQL 主要通过两种手段:

  • 慢查询日志:开启 MySQL 慢查询日志,再通过一些工具比如 mysqldumpslow 去分析对应的慢查询日志,找出问题的根源。
  • 服务监控:可以在业务的基建中加入对慢 SQL 的监控,常见的方案有字节码插桩、连接池扩展、ORM 框架过程,对服务运行中的慢 SQL 进行监控和告警。

找到对应的慢SQL 后,使用 EXPLAIN 命令查看 MySQL 是如何执行 SQL 语句的,这会帮助我们找到问题的根源。

SQL题:一个学生成绩表,字段有学生姓名、班级、成绩,求各班前十名

这是一个典型的 SQL 题,主要考察 SQL 的基本语法和分组查询。

第一步,建表:

代码语言:javascript
复制
CREATE TABLE student_scores (
    student_name VARCHAR(100),
    class VARCHAR(50),
    score INT
);

第二步,插入数据:

代码语言:javascript
复制
INSERT INTO student_scores (student_name, class, score) VALUES
('沉默王二', '三年二班', 88),
('沉默王三', '三年二班', 92),
('沉默王四', '三年二班', 87),
('沉默王五', '三年二班', 85),
('沉默王六', '三年二班', 90),
('沉默王七', '三年二班', 95),
('沉默王八', '三年二班', 82),
('沉默王九', '三年二班', 78),
('沉默王十', '三年二班', 91),
('沉默王十一', '三年二班', 79),
('沉默王十二', '三年三班', 84),
('沉默王十三', '三年三班', 81),
('沉默王十四', '三年三班', 90),
('沉默王十五', '三年三班', 88),
('沉默王十六', '三年三班', 87),
('沉默王十七', '三年三班', 93),
('沉默王十八', '三年三班', 89),
('沉默王十九', '三年三班', 85),
('沉默王二十', '三年三班', 92),
('沉默王二十一', '三年三班', 84);

第三步,查询各班前十名:

代码语言:javascript
复制
SET @cur_class = NULL, @cur_rank = 0;

SELECT student_name, class, score
FROM (
    SELECT 
        student_name, 
        class, 
        score,
        @cur_rank := IF(@cur_class = class, @cur_rank + 1, 1) AS rank,
        @cur_class := class
    FROM student_scores
    ORDER BY class, score DESC
) AS ranked
WHERE ranked.rank <= 10;

使用 @cur_class@cur_rank 来跟踪当前行的班级和排名。

在 SELECT 语句中,通过检查当前班级(@cur_class)是否与上一行相同来决定排名。如果相同,则增加排名;如果不同,则重置排名为 1。

然后通过 ORDER BY 子句确保在计算排名前按班级和分数排序。

二哥的 Java 进阶之路

Java 基础(包括并发编程和 JVM)

手写单例模式,各种情况,怎么保证线程安全?

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。单例模式主要用于控制对某些共享资源的访问,例如配置管理器、连接池、线程池、日志对象等。

refactoringguru.cn:单例模式

01、饿汉式如何实现单例?

饿汉式单例(Eager Initialization)在类加载时就急切地创建实例,不管你后续用不用得到,这也是饿汉式的来源,简单但不支持延迟加载实例。

代码语言:javascript
复制
public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}
02、懒汉式如何实现单例?

懒汉式单例(Lazy Initialization)在实际使用时才创建实例,“确实懒”(😂)。这种实现方式需要考虑线程安全问题,因此一般会带上 synchronized 关键字。

代码语言:javascript
复制
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
03、双重检查锁定如何实现单例?

双重检查锁定(Double-Checked Locking)结合了懒汉式的延迟加载和线程安全,同时又减少了同步的开销,主要是用 synchronized 同步代码块来替代同步方法。

代码语言:javascript
复制
public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

当 instance 创建后,再次调用 getInstance 方法时,不会进入同步代码块,从而提高了性能。

在 instance 前加上 volatile 关键字,可以防止指令重排,因为 instance = new Singleton() 并不是一个原子操作,可能会被重排序,导致其他线程获取到未初始化完成的实例。

04、静态内部类如何实现单例?

利用 Java 的静态内部类(Static Nested Class)和类加载机制来实现线程安全的延迟初始化。

代码语言:javascript
复制
public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

当第一次加载 Singleton 类时并不会初始化 SingletonHolder,只有在第一次调用 getInstance 方法时才会导致 SingletonHolder 被加载,从而实例化 instance。

05、枚举如何实现单例?

使用枚举(Enum)实现单例是最简单的方式,也能防止反射攻击和序列化问题。

代码语言:javascript
复制
public enum Singleton {
    INSTANCE;
    // 可以添加实例方法
}

手写单例的过程中提到了synchronized和volatile,顺便问了这两个的实现原理

volatile 关键字主要有两个作用,一个是保证变量的内存可见性,一个是禁止指令重排序。

volatile 怎么保证可见性的呢?

当一个变量被声明为 volatile 时,Java 内存模型会确保所有线程看到该变量时的值是一致的。

深入浅出 Java 多线程:Java内存模型

也就是说,当线程对 volatile 变量进行写操作时,JMM 会在写入这个变量之后插入一个 Store-Barrier(写屏障)指令,这个指令会强制将本地内存中的变量值刷新到主内存中。

三分恶面渣逆袭:volatile写插入内存屏障后生成的指令序列示意图

当线程对 volatile 变量进行读操作时,JMM 会插入一个 Load-Barrier(读屏障)指令,这个指令会强制让本地内存中的变量值失效,从而重新从主内存中读取最新的值。

三分恶面渣逆袭:volatile写插入内存屏障后生成的指令序列示意图

例如,我们声明一个 volatile 变量 x:

代码语言:javascript
复制
volatile int x = 0

线程 A 对 x 写入后会将其最新的值刷新到主内存中,线程 B 读取 x 时由于本地内存中的 x 失效了,就会从主内存中读取最新的值,内存可见性达成!

三分恶面渣逆袭:volatile内存可见性

volatile 怎么保证有序性的呢?

在程序执行期间,为了提高性能,编译器和处理器会对指令进行重排序。但涉及到 volatile 变量时,它们必须遵循一定的规则:

  • 写 volatile 变量的操作之前的操作不会被编译器重排序到写操作之后。
  • 读 volatile 变量的操作之后的操作不会被编译器重排序到读操作之前。

这意味着 volatile 变量的写操作总是发生在任何后续读操作之前。

HashMap的底层实现,它为什么是线程不安全的?

JDK 8 中 HashMap 的数据结构是数组+链表+红黑树

三分恶面渣逆袭:JDK 8 HashMap 数据结构示意图

HashMap 的核心是一个动态数组(Node[] table),用于存储键值对。这个数组的每个元素称为一个“桶”(Bucket),每个桶的索引是通过对键的哈希值进行哈希函数处理得到的。

当多个键经哈希处理后得到相同的索引时,会发生哈希冲突。HashMap 通过链表来解决哈希冲突——即将具有相同索引的键值对通过链表连接起来。

不过,链表过长时,查询效率会比较低,于是当链表的长度超过 8 时(且数组的长度大于 64),链表就会转换为红黑树。红黑树的查询效率是 O(logn),比链表的 O(n) 要快。数组的查询效率是 O(1)。

HashMap 是线程安全的吗?多线程下会有什么问题?

HashMap 不是线程安全的,主要有以下几个问题:

①、多线程下扩容会死循环。JDK1.7 中的 HashMap 使用的是头插法插入元素,在多线程的环境下,扩容的时候就有可能导致出现环形链表,造成死循环。

二哥的 Java 进阶之路

不过,JDK 8 时已经修复了这个问题,扩容时会保持链表原来的顺序。

②、多线程的 put 可能会导致元素的丢失。因为计算出来的位置可能会被其他线程的 put 覆盖,很好理解。本来哈希冲突是应该用链表的,但多线程时由于没有加锁,相同位置的元素可能就被干掉了。

二哥的 Java 进阶之路

③、put 和 get 并发时,可能导致 get 为 null。线程 1 执行 put 时,因为元素个数超出阈值而导致出现扩容,线程 2 此时执行 get,就有可能出现这个问题。

二哥的 Java 进阶之路:源码截图

因为线程 1 执行完 table = newTab 之后,线程 2 中的 table 此时也发生了变化,此时去 get 的时候当然会 get 到 null 了,因为元素还没有转移。

知道哪些线程安全的集合类型?

在 Java 中,有 3 种线程安全的 Map 实现,最常用的是ConcurrentHashMap和Collections.synchronizedMap(Map)包装器。

Hashtable 也是线程安全的,但它的使用已经不再推荐使用,因为 ConcurrentHashMap 提供了更高的并发性和性能。

①、HashTable 是直接在方法上加 synchronized 关键字,比较粗暴。

②、Collections.synchronizedMap 返回的是 Collections 工具类的内部类。

内部是通过 synchronized 对象锁来保证线程安全的。

③、ConcurrentHashMap 在 JDK 7 中使用分段锁,在 JKD 8 中使用了 CAS(Compare-And-Swap)+ synchronized 关键字,性能得到进一步提升。

ConcurrentHashMap、CopyOnWriteList 的实现原理?

ConcurrentHashMap 在 JDK 7 时采用的是分段锁机制(Segment Locking),整个 Map 被分为若干段,每个段都可以独立地加锁。因此,不同的线程可以同时操作不同的段,从而实现并发访问。

在 JDK 8 及以上版本中,ConcurrentHashMap 的实现进行了优化,不再使用分段锁,而是使用了一种更加精细化的锁——桶锁,以及 CAS 无锁算法。每个桶(Node 数组的每个元素)都可以独立地加锁,从而实现更高级别的并发访问。

初念初恋:JDK 8 ConcurrentHashMap

同时,对于读操作,通常不需要加锁,可以直接读取,因为 ConcurrentHashMap 内部使用了 volatile 变量来保证内存可见性。

对于写操作,ConcurrentHashMap 使用 CAS 操作来实现无锁的更新,这是一种乐观锁的实现,因为它假设没有冲突发生,在实际更新数据时才检查是否有其他线程在尝试修改数据,如果有,采用悲观的锁策略,如 synchronized 代码块来保证数据的一致性。

能说一下 CopyOnWriteArrayList 的实现原理吗?

CopyOnWriteArrayList 是一个线程安全的 ArrayList,它遵循写时复制(Copy-On-Write)的原则,即在写操作时,会先复制一个新的数组,然后在新的数组上进行写操作,写完之后再将原数组引用指向新数组。

CL0610:最终一致性

这样,读操作总是在一个不变的数组版本上进行的,就不需要同步了。

参考链接

  • 三分恶的面渣逆袭:https://javabetter.cn/sidebar/sanfene/nixi.html
  • 二哥的 Java 进阶之路:https://javabetter.cn
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 沉默王二 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • MySQL
    • 数据库用过哪些,对哪个比较熟?
      • MySQL索引结构,为什么用B+树?
        • 聚簇索引和非聚簇索引的区别?
          • 说说最左前缀原则
            • 为什么不从最左开始查,就无法匹配呢?
          • MySQL的主从复制过程
            • 场景题:sql查询很慢怎么排查
              • SQL题:一个学生成绩表,字段有学生姓名、班级、成绩,求各班前十名
              • Java 基础(包括并发编程和 JVM)
                • 手写单例模式,各种情况,怎么保证线程安全?
                  • 01、饿汉式如何实现单例?
                  • 02、懒汉式如何实现单例?
                  • 03、双重检查锁定如何实现单例?
                  • 04、静态内部类如何实现单例?
                  • 05、枚举如何实现单例?
                • 手写单例的过程中提到了synchronized和volatile,顺便问了这两个的实现原理
                  • volatile 怎么保证可见性的呢?
                  • volatile 怎么保证有序性的呢?
                • HashMap的底层实现,它为什么是线程不安全的?
                  • HashMap 是线程安全的吗?多线程下会有什么问题?
                • 知道哪些线程安全的集合类型?
                  • ConcurrentHashMap、CopyOnWriteList 的实现原理?
                    • 能说一下 CopyOnWriteArrayList 的实现原理吗?
                • 参考链接
                相关产品与服务
                云数据库 MySQL
                腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档