哈尔滨股票配资 字节跳动偏爱高校 TOP 榜

中国股票配资网_股票配资网_实盘配资排行榜
实盘配资排行榜
中国股票配资网_股票配资网_实盘配资排行榜
中国股票配资网_股票配资网_实盘配资排行榜
中国股票配资网
股票配资网
实盘配资排行榜
哈尔滨股票配资 字节跳动偏爱高校 TOP 榜
发布日期:2025-09-19 21:15    点击次数:163

哈尔滨股票配资 字节跳动偏爱高校 TOP 榜

图解学习网站:哈尔滨股票配资

大家好,我是小林。

之前在「」文章里我提过,字节跳动对学历的要求相对灵活 , 我不仅见过双非一本毕业生成功入职,甚至二本背景的同学也有机会进入。

不过话说回来,各大厂对高校往往会有自己的偏好。最近我在网上看到一份 「节跳动偏爱高校毕业生」的名单(非官方来源,仅供大家参考),正好可以和大家聊聊。

截图来源网络,如侵删

这份名单分为四档,第一档以 985 高校为主,但也有两所 211 高校格外突出,比如西电和北邮,这两所学校确实在互联网行业认可度极高,我身边不少进大厂的同学都来自这两所院校,所以也不光只是字节偏爱了,可能整个互联网公司偏爱的第一档都有这些学校。

当然,名单里不只有 985/211 高校,也能看到不少双非院校的身影,比如广东工业大学、哈尔滨理工大学、杭州电子科技大学、深圳大学、重庆邮电大学等。这些学校虽然不是传统意义上的 “双一流”,但在计算机、电子等相关领域实力强劲,属于行业内认可度很高的强一本,毕业生在求职时也很有竞争力。

展开剩余96%

这里必须提醒大家:不在这份名单里,不代表没有进字节的机会。

我身边就有双非背景、学校未出现在名单中的同学成功入职,但这类情况相对较少。如果学历不占优势,就一定要在其他方面打造 “亮点”, 比如在算法竞赛中取得好成绩、拥有含金量高的实习经历,或是做过能体现技术能力的优质项目,这些都能成为弥补学历差距的关键。

还有同学问我:“如果本科是双非,硕士考上了名单里的偏爱高校,有机会进字节吗?” 答案是肯定的。

只要硕士院校是 211 及以上,哪怕本科背景普通,字节大多愿意给面试机会,不会单纯因为本科学校卡简历。但前提是,你的简历项目经历不能平庸,必须能体现出扎实的技术基础和实践能力,才能在面试中脱颖而出。

目前秋招已经推进一个多月,最近我看到很多的同学,拿到的第一个秋招 offer 就来自字节,而且整个流程效率极高 ,从笔试到 3 轮技术面、再到 HR 面,不到 3 周就全部走完,随后很快收到了 offer 邮件。

这段时间正是秋招高峰期,明显能感觉到大家都在全力冲刺,常看到有同学晚上 10-11 点还在用我们的「」做模拟面试练习,有的人面了 80 分钟,这个是选了困难模式的,也有面了 30 分钟左右的,这些都是选了简单模式的。

之所以大家都在「」卷面试,核心是因为它的考察方向完全对标大厂难度,不管是八股的细节,还是项目经历的细节追问,都和真实面试场景高度贴合,不少同学体验后反馈,练得多了不仅能查漏补缺,更能慢慢消除对大厂面试的紧张感,真正做到对真实面试祛魅,上场时更有底气。

那么,这次就看看热乎的字节跳动秋招Java一面,主要围绕Java 并发、Redis、MySQL 事务三大模块展开,而且考察深度较深,会追问到底层原理,整体符合大厂「重基础、挖原理」的面试风格,算是很典型的大厂技术面范例。

字节跳动(秋招一面)1. 线程安全是什么?怎么保证安全?

线程安全指的是:当多个线程同时访问同一段代码或同一份数据时,程序依然能按照预期正确运行,数据不会出现“错乱”、丢失或重复。

简单来说,如果一个类或方法在多线程并发调用下,仍然能保证逻辑正确、数据一致,它就是线程安全的。

多线程编程里常见的线程安全问题主要有四类。

第一,数据竞争,也叫丢失更新。比如两个线程同时对同一个变量执行 count++,因为这个操作分三步:读、加一、写回,如果两个线程几乎同时读到相同的值,就会覆盖对方的结果,导致最终只加了一次。

第二,读写不一致。当一个线程正在修改数据,另一个线程刚好去读,可能读到一半更新完、一半还没更新的数据,拿到的是不完整的结果。

第三,可见性问题。一个线程改了共享变量,但改动只在它的 CPU 缓存里,别的线程看不到最新值,可能一直用旧值,像是死循环卡住一样。

最后,指令重排序带来的问题。为了优化性能,JVM 和 CPU 会调整指令顺序,如果没有同步措施,可能会出现对象引用先发布出去,但内部字段还没初始化完的情况。

第一,数据竞争,也叫丢失更新。比如两个线程同时对同一个变量执行 count++,因为这个操作分三步:读、加一、写回,如果两个线程几乎同时读到相同的值,就会覆盖对方的结果,导致最终只加了一次。

第二,读写不一致。当一个线程正在修改数据,另一个线程刚好去读,可能读到一半更新完、一半还没更新的数据,拿到的是不完整的结果。

第三,可见性问题。一个线程改了共享变量,但改动只在它的 CPU 缓存里,别的线程看不到最新值,可能一直用旧值,像是死循环卡住一样。

最后,指令重排序带来的问题。为了优化性能,JVM 和 CPU 会调整指令顺序,如果没有同步措施,可能会出现对象引用先发布出去,但内部字段还没初始化完的情况。

要解决线程安全问题,通常需要加锁、用原子类,或者用并发安全的数据结构,来保证原子性、可见性和有序性,常见的保障线程安全的如下:

synchronized 关键字:可以修饰方法或代码块,保证同一时刻只有一个线程能进入临界区。

synchronized 关键字:可以修饰方法或代码块,保证同一时刻只有一个线程能进入临界区。

count++;

}

ReentrantLock:更灵活的显式锁,可以尝试获取锁、设置超时、支持公平锁。**

ReentrantLock:更灵活的显式锁,可以尝试获取锁、设置超时、支持公平锁。**

try {

lock.lock;

count++;

} finally {

lock.unlock;

}

Java 提供了 java.util.concurrent.atomic包,比如 AtomicInteger,利用 CAS(Compare-And-Swap)保证原子操作。

Java 提供了 java.util.concurrent.atomic包,比如 AtomicInteger,利用 CAS(Compare-And-Swap)保证原子操作。

count.incrementAndGet;

ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue这些集合类内部已做同步或无锁优化,可以直接安全地用于多线程环境。

使用 volatile 关键字修饰共享变量,保证写入对其他线程立刻可见。

ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue这些集合类内部已做同步或无锁优化,可以直接安全地用于多线程环境。

使用 volatile 关键字修饰共享变量,保证写入对其他线程立刻可见。

2. 线程池用法是什么?参数有哪些?

线程池的核心目的是复用线程、降低频繁创建销毁线程的开销、控制并发数量。

在 Java 中,线程池一般通过 ThreadPoolExecutor来创建,或者使用 Executors提供的工厂方法(如 newFixedThreadPool、newCachedThreadPool)。

最常见的用法是提交任务给线程池执行:

ExecutorService pool = Executors.newFixedThreadPool(4);

pool.submit( -> {

System.out.println(Thread.currentThread.getName + " 执行任务");

});

pool.shutdown;

线程池的核心构造函数如下:

ThreadPoolExecutor executor = newThreadPoolExecutor(

corePoolSize,

maximumPoolSize,

keepAliveTime,

TimeUnit.SECONDS,

workQueue,

threadFactory,

handler

);

逐一解释参数:

corePoolSize(核心线程数):线程池会尽量维持的线程数量,即使它们是空闲的。

maximumPoolSize(最大线程数):线程池里能创建的最大线程数量,超过这个数量的任务会被拒绝或放进队列。

keepAliveTime(空闲存活时间):当线程数超过核心线程数时,空闲线程多长时间没有任务会被回收。

TimeUnit(时间单位):指定 keepAliveTime 的单位,比如秒、毫秒。

workQueue(任务队列):用来存放等待执行的任务,比如常用的 LinkedBlockingQueue、ArrayBlockingQueue。

threadFactory(线程工厂):用来自定义线程的创建方式,可以给线程起名字、设置为守护线程。

handler(拒绝策略):当队列满、线程数已到最大值时,处理新任务的拒绝策略。

corePoolSize(核心线程数):线程池会尽量维持的线程数量,即使它们是空闲的。

maximumPoolSize(最大线程数):线程池里能创建的最大线程数量,超过这个数量的任务会被拒绝或放进队列。

keepAliveTime(空闲存活时间):当线程数超过核心线程数时,空闲线程多长时间没有任务会被回收。

TimeUnit(时间单位):指定 keepAliveTime 的单位,比如秒、毫秒。

workQueue(任务队列):用来存放等待执行的任务,比如常用的 LinkedBlockingQueue、ArrayBlockingQueue。

threadFactory(线程工厂):用来自定义线程的创建方式,可以给线程起名字、设置为守护线程。

handler(拒绝策略):当队列满、线程数已到最大值时,处理新任务的拒绝策略。

当线程池的任务队列满了之后,线程池会执行指定的拒绝策略来应对,常用的四种拒绝策略包括:CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy,此外,还可以通过实现RejectedExecutionHandler接口来自定义拒绝策略。

四种预置的拒绝策略:

CallerRunsPolicy,使用线程池的调用者所在的线程去执行被拒绝的任务,除非线程池被停止或者线程池的任务队列已有空缺。

AbortPolicy,直接抛出一个任务被线程池拒绝的异常。

DiscardPolicy,不做任何处理,静默拒绝提交的任务。

DiscardOldestPolicy,抛弃最老的任务,然后执行该任务。

自定义拒绝策略,通过实现接口可以自定义任务拒绝策略。

CallerRunsPolicy,使用线程池的调用者所在的线程去执行被拒绝的任务,除非线程池被停止或者线程池的任务队列已有空缺。

AbortPolicy,直接抛出一个任务被线程池拒绝的异常。

DiscardPolicy,不做任何处理,静默拒绝提交的任务。

DiscardOldestPolicy,抛弃最老的任务,然后执行该任务。

自定义拒绝策略,通过实现接口可以自定义任务拒绝策略。

官方使用基准测试的结果是,单线程的 Redis 吞吐量可以达到 10W/每秒,如下图所示:

之所以 Redis 采用单线程(网络 I/O 和执行命令)那么快,有如下几个原因:

Redis 的大部分操作都在内存中完成,并且采用了高效的数据结构,因此 Redis 瓶颈可能是机器的内存或者网络带宽,而并非 CPU,既然 CPU 不是瓶颈,那么自然就采用单线程的解决方案了;

Redis 采用单线程模型可以避免了多线程之间的竞争,省去了多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题。

Redis 采用了 I/O 多路复用机制处理大量的客户端 Socket 请求,IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听 Socket 和已连接 Socket。内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。

Redis 的大部分操作都在内存中完成,并且采用了高效的数据结构,因此 Redis 瓶颈可能是机器的内存或者网络带宽,而并非 CPU,既然 CPU 不是瓶颈,那么自然就采用单线程的解决方案了;

Redis 采用单线程模型可以避免了多线程之间的竞争,省去了多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题。

Redis 采用了 I/O 多路复用机制处理大量的客户端 Socket 请求,IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听 Socket 和已连接 Socket。内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。

Zset 类型的底层数据结构是由压缩列表或跳表实现的:

如果有序集合的元素个数小于 128 个,并且每个元素的值小于 64 字节时,Redis 会使用压缩列表作为 Zset 类型的底层数据结构;

如果有序集合的元素不满足上面的条件,Redis 会使用跳表作为 Zset 类型的底层数据结构;

如果有序集合的元素个数小于 128 个,并且每个元素的值小于 64 字节时,Redis 会使用压缩列表作为 Zset 类型的底层数据结构;

如果有序集合的元素不满足上面的条件,Redis 会使用跳表作为 Zset 类型的底层数据结构;

在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了。

6. 跳表原理是什么?

链表在查找元素的时候,因为需要逐一查找,所以查询效率非常低,时间复杂度是O(N),于是就出现了跳表。跳表是在链表基础上改进过来的,实现了一种「多层」的有序链表,这样的好处是能快读定位数据。

那跳表长什么样呢?我这里举个例子,下图展示了一个层级为 3 的跳表。

图中头节点有 L0~L2 三个头指针,分别指向了不同层级的节点,然后每个层级的节点都通过指针连接起来:

L0 层级共有 5 个节点,分别是节点1、2、3、4、5;

L1 层级共有 3 个节点,分别是节点 2、3、5;

L2 层级只有 1 个节点,也就是节点 3 。

L0 层级共有 5 个节点,分别是节点1、2、3、4、5;

L1 层级共有 3 个节点,分别是节点 2、3、5;

L2 层级只有 1 个节点,也就是节点 3 。

如果我们要在链表中查找节点 4 这个元素,只能从头开始遍历链表,需要查找 4 次,而使用了跳表后,只需要查找 2 次就能定位到节点 4,因为可以在头节点直接从 L2 层级跳到节点 3,然后再往前遍历找到节点 4。

可以看到,这个查找过程就是在多个层级上跳来跳去,最后定位到元素。当数据量很大时,跳表的查找复杂度就是 O(logN)。

那跳表节点是怎么实现多层级的呢?这就需要看「跳表节点」的数据结构了,如下:

typedefstructzskiplistNode{

//Zset 对象的元素值

sds ele;

//元素权重值

doublescore;

//后向指针

structzskiplistNode*backward;

//节点的level数组,保存每层上的前向指针和跨度

structzskiplistLevel{

structzskiplistNode*forward;

unsignedlongspan;

} level[];

} zskiplistNode;

Zset 对象要同时保存「元素」和「元素的权重」,对应到跳表节点结构里就是 sds 类型的 ele 变量和 double 类型的 score 变量。每个跳表节点都有一个后向指针(struct zskiplistNode *backward),指向前一个节点,目的是为了方便从跳表的尾节点开始访问节点,这样倒序查找时很方便。

跳表是一个带有层级关系的链表,而且每一层级可以包含多个节点,每一个节点通过指针连接起来,实现这一特性就是靠跳表节点结构体中的zskiplistLevel 结构体类型的 level 数组。

level 数组中的每一个元素代表跳表的一层,也就是由 zskiplistLevel 结构体表示,比如 leve[0] 就表示第一层,leve[1] 就表示第二层。zskiplistLevel 结构体里定义了「指向下一个跳表节点的指针」和「跨度」,跨度时用来记录两个节点之间的距离。

比如,下面这张图,展示了各个节点的跨度。

第一眼看到跨度的时候,以为是遍历操作有关,实际上并没有任何关系,遍历操作只需要用前向指针(struct zskiplistNode *forward)就可以完成了。

Redis 跳表在创建节点的时候,随机生成每个节点的层数,并没有严格维持相邻两层的节点数量比例为 2 : 1 的情况。

具体的做法是,跳表在创建节点时候,会生成范围为[0-1]的一个随机数,如果这个随机数小于 0.25(相当于概率 25%),那么层数就增加 1 层,然后继续生成下一个随机数,直到随机数的结果大于 0.25 结束,最终确定该节点的层数。

这样的做法,相当于每增加一层的概率不超过 25%,层数越高,概率越低,层高最大限制是 64。

虽然我前面讲解跳表的时候,图中的跳表的「头节点」都是 3 层高,但是其实如果层高最大限制是 64,那么在创建跳表「头节点」的时候,就会直接创建 64 层高的头节点。

7. Redis内存满了会发生什么?

Redis 有个参数 maxmemory,用来限制最大可用内存。如果没设置,Redis 默认会用光系统能给它分配的内存,一直到被操作系统 OOM Kill 掉。

如果设置了 maxmemory,当内存达到上限时,Redis 不会再继续无脑接收写请求,而是会根据「淘汰策略」来决定怎么处理。

Redis 提供 8 种策略,可以通过 maxmemory-policy设置:

策略

说明

noeviction(默认)内存满时直接返回错误,不再接受写入(读操作仍可执行)。 allkeys-lru淘汰最少使用的 key(LRU),所有 key 都可能被淘汰。 volatile-lru只淘汰设置了过期时间的 key,挑最少使用的先删。 allkeys-random随机淘汰任意 key。 volatile-random随机淘汰设置了过期时间的 key。 volatile-ttl淘汰过期时间最短的 key(谁快过期就先删谁)。 allkeys-lfu淘汰最不常访问的 key(LFU,比 LRU 更精细)。 volatile-lfu只淘汰设置了过期时间的 key,按访问频率挑最少的删。

策略

说明

8. Redis某个节点挂掉怎么办?

如果你只是单机部署,Redis 节点挂掉后就没法服务了,读写都会失败。一般只能通过:

人工或脚本重启Redis 服务

如果数据持久化打开了(RDB/AOF),可以通过持久化文件恢复数据

人工或脚本重启Redis 服务

如果数据持久化打开了(RDB/AOF),可以通过持久化文件恢复数据

但这种方式会有停机时间,所以生产环境通常不会用纯单机模式。

生产环境常见的做法是:

主从架构:一台主库(Master)+ 多台从库(Slave),主库挂掉后可以把其中一台从库提升为主库。

Redis Sentinel:监控主从节点健康,一旦发现主节点不可用,会自动发起主从切换(Failover),选一个从库当新的主库,并通知客户端更新连接。

主从架构:一台主库(Master)+ 多台从库(Slave),主库挂掉后可以把其中一台从库提升为主库。

Redis Sentinel:监控主从节点健康,一旦发现主节点不可用,会自动发起主从切换(Failover),选一个从库当新的主库,并通知客户端更新连接。

这样即便某个节点挂掉,集群仍能继续提供读写服务,业务几乎无感知。

如果用的是 Redis Cluster(分布式分片),集群内部有多个主节点和从节点:

如果某个主节点挂掉,集群会投票自动把对应的从节点提升为主节点

如果主节点和它的从节点都挂了,涉及的槽位(slot)就不可用,集群进入fail state,需要运维介入

如果某个主节点挂掉,集群会投票自动把对应的从节点提升为主节点

如果主节点和它的从节点都挂了,涉及的槽位(slot)就不可用,集群进入fail state,需要运维介入

对于读数据,我会选择旁路缓存策略,如果 cache 不命中,会从 db 加载数据到 cache。对于写数据,我会选择更新 db 后,再删除缓存。

缓存是通过牺牲强一致性来提高性能的。这是由CAP理论决定的。缓存系统适用的场景就是非强一致性的场景,它属于CAP中的AP。所以,如果需要数据库和缓存数据保持强一致,就不适合使用缓存。

所以使用缓存提升性能,就是会有数据更新的延迟。这需要我们在设计时结合业务仔细思考是否适合用缓存。然后缓存一定要设置过期时间,这个时间太短、或者太长都不好:

太短的话请求可能会比较多的落到数据库上,这也意味着失去了缓存的优势。

太长的话缓存中的脏数据会使系统长时间处于一个延迟的状态,而且系统中长时间没有人访问的数据一直存在内存中不过期,浪费内存。

太短的话请求可能会比较多的落到数据库上,这也意味着失去了缓存的优势。

太长的话缓存中的脏数据会使系统长时间处于一个延迟的状态,而且系统中长时间没有人访问的数据一直存在内存中不过期,浪费内存。

但是,通过一些方案优化处理,是可以最终一致性的。

针对删除缓存异常的情况,可以使用 2 个方案避免:

删除缓存重试策略(消息队列)

删除缓存重试策略(消息队列)

消息队列方案

消息队列方案

我们可以引入消息队列,将第二个操作(删除缓存)要操作的数据加入到消息队列,由消费者来操作数据。

如果应用删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制。当然,如果重试超过的一定次数,还是没有成功,我们就需要向业务层发送报错信息了。

如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作,否则就继续重试。

如果应用删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制。当然,如果重试超过的一定次数,还是没有成功,我们就需要向业务层发送报错信息了。

如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作,否则就继续重试。

举个例子,来说明重试机制的过程。

重试删除缓存机制还可以,就是会造成好多业务代码入侵。

「先更新数据库,再删缓存」的策略的第一步是更新数据库,那么更新数据库成功,就会产生一条变更日志,记录在 binlog 里。

下图是 Canal 的工作原理:

10. 先写数据库再删缓存,这段时间有针对某个数据的大量请求怎么办?

先写库再删缓存后,删除与重建之间会出现缓存击穿,大量并发打到 MySQL,我一般用分布式单飞,保证只有一个请求回源并重建,其余并发等待;读多场景用逻辑过期+后台刷新,让用户短窗继续用旧值且不压 DB

方案一:单飞(SingleFlight)/分布式互斥重建

方案一:单飞(SingleFlight)/分布式互斥重建

同一个 key在任一时刻,只允许一个请求去数据库取数据并重建缓存;其它并发对同 key 的请求等待这个结果(或快速返回降级),这样整个系统对 DB 的压力从 “N 次并发查询” 收敛为 “1 次查询”。

所以,可以用分布式单飞确保一个请求回源、其余等待结果,配合二次检查避免重复加载。

String key = "user:123";

RLock lock = redisson.getLock("lock:"+ key);

booleanok = lock.tryLock(50, 500, TimeUnit.MILLISECONDS); // 等锁≤50ms,持锁≤500ms

try{

String v = redis.get(key);

if(v != null) returnv; // 二次检查,防并发重复加载

// 只有拿到锁的线程回源

Data data = db.query(id);

redis.setex(key, serialize(data), ttlWithJitter);

returnserialize(data);

} finally{

if(ok) lock.unlock;

}

注意要二次检查、锁超时、只锁热点 key、失败快速返回/降级。

方案二:逻辑过期 + 后台刷新

方案二:逻辑过期 + 后台刷新

删缓存后到重建前的空窗时段,如果让所有请求都回源,会压 DB;如果简单“阻塞等待重建”,高并发下用户延迟会抖动。

解决的思路,缓存里不只放数据,还放一个逻辑过期时间logicalExpireAt。

比如,缓存里放:{data, logicalExpireAt}。过期后:

一个请求拿“刷新令牌/锁”去后台回源并重建;

其他并发短窗继续返回旧值,不压 DB。

一个请求拿“刷新令牌/锁”去后台回源并重建;

其他并发短窗继续返回旧值,不压 DB。

if(cv != null&& now < cv.logicalExpireAt) returncv.data; // 还没过期

if(tryAcquireRefreshToken(key)) { // 只允许一个去刷新

async( -> {

Data fresh = db.queryById(123);

redis.set(key, newCacheVal(fresh, now+softTtl), hardTtlWithJitter);

});

}

returncv != null? cv.data : fallback; // 有旧值先兜底,没有再降级

适合场景读多写少、能容忍“短时略旧”的业务(页面/列表/推荐等)。

11. 事务几个特性?都怎么保证的?

事务的 acid 四大特性:

原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样,就好比买一件商品,购买成功时,则给商家付了钱,商品到手;购买失败时,则商品在商家手中,消费者的钱也没花出去。

一致性(Consistency):是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。比如,用户 A 和用户 B 在银行分别有 800 元和 600 元,总共 1400 元,用户 A 给用户 B 转账 200 元,分为两个步骤,从 A 的账户扣除 200 元和对 B 的账户增加 200 元。一致性就是要求上述步骤操作后,最后的结果是用户 A 还有 600 元,用户 B 有 800 元,总共 1400 元,而不会出现用户 A 扣除了 200 元,但用户 B 未增加的情况(该情况,用户 A 和 B 均为 600 元,总共 1200 元)。

隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据时,不会相互干扰,每个事务都有一个完整的数据空间,对其他并发事务是隔离的。也就是说,消费者购买商品这个事务,是不影响其他消费者购买的。

持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样,就好比买一件商品,购买成功时,则给商家付了钱,商品到手;购买失败时,则商品在商家手中,消费者的钱也没花出去。

一致性(Consistency):是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。比如,用户 A 和用户 B 在银行分别有 800 元和 600 元,总共 1400 元,用户 A 给用户 B 转账 200 元,分为两个步骤,从 A 的账户扣除 200 元和对 B 的账户增加 200 元。一致性就是要求上述步骤操作后,最后的结果是用户 A 还有 600 元,用户 B 有 800 元,总共 1400 元,而不会出现用户 A 扣除了 200 元,但用户 B 未增加的情况(该情况,用户 A 和 B 均为 600 元,总共 1200 元)。

隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据时,不会相互干扰,每个事务都有一个完整的数据空间,对其他并发事务是隔离的。也就是说,消费者购买商品这个事务,是不影响其他消费者购买的。

持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

MySQL InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?

持久性是通过 redo log (重做日志)来保证的;

原子性是通过 undo log(回滚日志) 来保证的;

隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;

一致性则是通过持久性+原子性+隔离性来保证;

持久性是通过 redo log (重做日志)来保证的;

原子性是通过 undo log(回滚日志) 来保证的;

隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;

一致性则是通过持久性+原子性+隔离性来保证;

对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View来实现的,它们的区别在于创建 Read View 的时机不同:

「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,所以同一事务里两次查询可能读到不一样的结果,出现不可重复读;

而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View,所以能保证同一条记录多次查询结果一致,实现可重复读。

「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,所以同一事务里两次查询可能读到不一样的结果,出现不可重复读;

而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View,所以能保证同一条记录多次查询结果一致,实现可重复读。

手撕是最长公共子序列哈尔滨股票配资

发布于:广东省