简栈

拥抱AI,持续成长

CPU飚高分析

一般可以使用

  • ps -Lfp pid
  • ps -mp pid -o THREAD, tid, time
  • top -Hp pid
1
2
3
4
5
6
7
8
9
10
[root@redis webapps]# top -Hp 22272
top - 10:09:30 up 9 days, 22:10, 1 user, load average: 0.00, 0.00, 0.00
Tasks: 30 total, 0 running, 30 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 3923196k total, 3795588k used, 127608k free, 153056k buffers
Swap: 6160376k total, 0k used, 6160376k free, 3079244k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
22272 root 20 0 2286m 122m 11m S 0.0 3.2 0:00.00 java
22278 root 20 0 2286m 122m 11m S 0.0 3.2 0:00.00 java

TIME列就是各个Java线程耗费的CPU时间,CPU时间最长的是线程ID为22283 的线程。

printf "%x\n" 22283

1
2
[root@redis webapps]# printf ‘%x\n’ 22283
570b得到22283 的十六进制值为570b。

下一步轮到jstack上场了,它用来输出进程22272 的堆栈信息,然后根据线程ID的十六进制值grep,如下:

1
2
[root@redis webapps]# jstack 22272 | grep 570b
“SchedulerThread” prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait()

可以看到CPU消耗在SchedulerThread这个类的Object.wait(),定位到下面的代码:

阅读全文 »

背景

一直在使用Lombok以及MapStruct,但是对于它们能够在编译阶段直接生成实例代码却没有仔细了解过。最近刚好在部门内做了一次分享,也在这里对具体原理做一个详细阐述。

Lombok以及MapStruct实现大体思路

Lombok以及MapStruct都是通过在目标代码上标记注解,编译器能够根据注解生成对应的实现代码。比如Lombok在属性上标记@Getter,那么在这个Java Bean内就会生成对应属性的get方法。

本质上来说,不管是Lombok或者MapStruct,都是通过Java的一个标准API来实现的;这个API即为Pluggable Annotation Processing API,简称为JSR269

JSR269

借用JSR269官方原文定义(附带原文地址:https://jcp.org/en/jsr/detail?id=269):

J2SE 1.5 added a new Java language mechanism “annotations” that allows annotation types to be used to annotate classes, fields, and methods. These annotations are typically processed either by build-time tools or by run-time libraries to achieve new semantic effects. In order to support annotation processing at build-time, this JSR will define APIs to allow annotation processors to be created using a standard pluggable API. This will simplify the task of creating annotation processors and will also allow automation of the discovery of appropriate annotation processors for a given source file.
The specification will include at least two sections, a section of API modeling the Java programming language and a distinct section for declaring annotation processors and controlling how they are run. Since annotations are placed on program elements, an annotation processing framework needs to reflect program structure. Annotation processors will be able to specify what annotations they process and multiple processors will be able to run cooperatively.
The processors and program structure api can be accessed at build-time; i.e. this functionality supplements core reflection support for reading annotations.

译文如下:

J2SE 1.5 增加了一种新的Java语言机制”annotations“,它允许注解被用于类、字段以及方法上。这些注解由 build-time 工具以及 run-time 库处理,来达到新的语义效果。为了支持在 build-time 时处理注解,这个JSR定义了通用的插入式API用于创建标准的注解处理器。这将简化创建注解处理器的任务,并且还能够根据源文件自动匹配响应的注解处理器。
该规范将至少包含两个部分,一部分用于建模Java编程语言的API,另一部分用于声明注解处理器以及他们的运作机制。由于注解被用于程序元素上,一个注解处理框架需要反映程序结构。注解处理器将能指定哪些注解是它们可以处理的,以及多个注解处理器如何协同工作。
注解处理器以及程序框架api可以在 build-time 时访问;举个例子,此功能提供了核心反射支持用于读取注解(注:一般指从源码内读取注解或者只读取标注为source-only的注解)。

阅读全文 »

本地安装GDB

1
2
3
4
5
6
7
8
brew install gdb

➜ ~ gdb --version
GNU gdb (GDB) 9.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

除了这个,在Mac系统系统里面还要配置证书相关的操作。

按入下步骤创建代码签名的证书:

  1. 打开 Keychain Access 应用程序(/Applications/Utilities/Keychain Access.app)
  2. 执行菜单 钥匙串访问 -> 证书助理 -> 创建证书
  3. 填写如下信息:
    • 名称:gdb_codesign
    • 身份类型:自签名根证书
    • 证书类型:代码签名
    • 钩选:让我覆盖这些默认设置
      http://static.cyblogs.com/QQ20200524-221553@2x.jpg
  4. 一路确定,直到指定证书位置的步骤,选择系统
    http://static.cyblogs.com/QQ20200524-221644@2x.jpg
  5. 点击“创建”,会提示用输入系统登录密码,创建完成
  6. 钥匙串访问程序中,选择左侧栏的系统我的证书,找到你刚刚创建的gdb_codesign证书并双击打开证书信息窗口,展开信任项,设置使用此证书时:始终信任
  7. 关闭证书信息窗口,系统会再次要求输入系统登录密码。

因为我现在的系统是MacOS Catania,是在 Mojave (10.14) 之后的系统。所以还需要创建一个配置文件gdb-entitlement.xml,其内容如下:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>
</pre>

最后执行命令:

1
➜  Desktop  codesign --entitlements gdb-entitlement.xml -fs gdb_codesign $(which gdb)

终端中 gdb 断点进入源码调试 hotspot

阅读全文 »

1.什么是进程?

进程是系统中正在运行的一个程序,程序一旦运行就是进程。

进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。

2.什么是线程?

是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

3.线程的实现方式?

1.继承Thread类

2.实现Runnable接口

3.使用Callable和Future

4.Thread 类中的start() 和 run() 方法有什么区别?

阅读全文 »

Mac系统安装

1
2
3
4
利用brew search查找mercurial
➜ ~ brew search mercurial
安装
➜ ~ brew install mercuria

Linux系统安装

1
sudo apt install mercurial

安装openjdk8

添加代理

一般在下载代码的时候都会很慢,故先配置好代理。我这里是V2Ray。

1
2
3
4
5
6
vim /usr/local/etc/mercurial/hgrc

[http_proxy]
host=127.0.0.1:8001
[https_proxy]
host=127.0.0.1:8001
下载jdk8u的代码
1
2
3
4
5
6
hg clone https://hg.openjdk.java.net/jdk8u/jdk8u openjdk8
chmod u+x get_source.sh
chmod u+x configure
./get_source.sh # 下载全部源代码
./configure --with-freetype-include=/usr/local/include/freetype2 --with-freetype-lib=/usr/local/lib/ # configure 编译环境,若编译报错,需要添加 `--disable-warnings-as-errors`
make all
阅读全文 »

我们用Spring就是因为它能帮我们很好的管理Bean,如果我们能充分的理解Bean的生命周期,就能在想要的环节去做想做的事情。

周期只有四个!

是的,Spring Bean的生命周期只有这四个阶段。把这四个阶段和每个阶段对应的扩展点糅合在一起虽然没有问题,但是这样非常凌乱,难以记忆。要彻底搞清楚Spring的生命周期,首先要把这四个阶段牢牢记住。实例化和属性赋值对应构造方法和setter方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。在这四步之间穿插的各种扩展点,稍后会讲。

    1. 实例化 Instantiation
    1. 属性赋值 Populate
    1. 初始化 Initialization
    1. 销毁 Destruction

实例化 -> 属性赋值 -> 初始化 -> 销毁

主要逻辑都在doCreate()方法中,逻辑很清晰,就是顺序调用以下三个方法,这三个方法与三个生命周期阶段一一对应,非常重要,在后续扩展接口分析中也会涉及。

    1. createBeanInstance() -> 实例化
    1. populateBean() -> 属性赋值
    1. initializeBean() -> 初始化

源码如下,能证明实例化,属性赋值和初始化这三个生命周期的存在。关于本文的Spring源码都将忽略无关部分,便于理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 忽略了无关代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {

// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// 实例化阶段!
instanceWrapper = createBeanInstance(beanName, mbd, args);
}

// Initialize the bean instance.
Object exposedObject = bean;
try {
// 属性赋值阶段!
populateBean(beanName, mbd, instanceWrapper);
// 初始化阶段!
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}

至于销毁,是在容器关闭时调用的,详见ConfigurableApplicationContext#close()

阅读全文 »

数据库使用锁是为了支持更好的并发,提供数据的完整性和一致性。InnoDB是一个支持行锁的存储引擎,锁的类型有:

  • 共享锁(S)

  • 排他锁(X)

  • 意向共享(IS)

  • 意向排他(IX)

为了提供更好的并发,InnoDB提供了非锁定读:不需要等待访问行上的锁释放,读取行的一个快照。该方法是通过InnoDB的一个特性:MVCC来实现的。

InnoDB有三种行锁的算法:

1、Record Lock:单个行记录上的锁。

2、Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。

3、Next-Key Lock:1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。

http://static.cyblogs.com/QQ20200529-221058@2x.jpg

为什么section B上面的插入语句会出现锁等待的情况InnoDB是行锁,在section A里面锁住了a=8的行,其他应该不受影响。why?

因为InnoDB对于行的查询都是采用了Next-Key Lock的算法,锁定的不是单个值,而是一个范围(GAP)。上面索引值有1,3,5,8,11,其记录的GAP的区间如下:是一个左开右闭的空间(原因是默认主键的有序自增的特性,结合后面的例子说明)

阅读全文 »

简介

Elasticsearch是一个高度可扩展的开源的分布式Restful全文搜索和分析引擎。它允许用户快速的(近实时的)存储、搜索和分析海量数据。它通常用作底层引擎技术,为具有复杂搜索功能和要求的应用程序提供支持。
以下是ES可用于的一些场景:

  1. 电商网站提供搜索功能:可使用ES来存储产品的目录和库存,并为它们提供搜索和自动填充建议。
  2. 收集日志和交易数据,并进行分析:可使用Logstash来收集、聚合和解析数据, 然后让Logstash将此数据提供给ES。然后可在ES中搜索和聚合开发者感兴趣的信息。
  3. 需要快速调查、分析、可视化查询大量数据的特定问题:可以使用ES存储数据,然后使用Kibana构建自定义仪表板,来可视化展示数据。还可以使用ES的聚合功能针对这些数据进行复杂的商业分析。

我们要认识一个人Doug Cutting

为什么要提Doug Cutting,因为Elasticsearch的底层是Lucene,而Lucene就是Doug Cutting大神写的。

引用来自于: 鲜枣课堂

1998年9月4日,Google公司在美国硅谷成立。正如大家所知,它是一家做搜索引擎起家的公司。

http://static.cyblogs.com/640.jpg

无独有偶,一位名叫Doug Cutting的美国工程师,也迷上了搜索引擎。他做了一个用于文本搜索的函数库(姑且理解为软件的功能组件),命名为Lucene

http://static.cyblogs.com/pUm6Hxkd434Mk1VTAruKa8.jpg

左为Doug Cutting,右为Lucene的LOGO

Lucene是用JAVA写成的,目标是为各种中小型应用软件加入全文检索功能。因为好用而且开源(代码公开),非常受程序员们的欢迎。

早期的时候,这个项目被发布在Doug Cutting的个人网站和SourceForge(一个开源软件网站)。后来,2001年底,Lucene成为Apache软件基金会jakarta项目的一个子项目。

http://static.cyblogs.com/iaqYeVeXiaLwMxssVyfyV0f69tfVMod6.jpg

Apache软件基金会,搞IT的应该都认识

2004年,Doug Cutting再接再励,在Lucene的基础上,和Apache开源伙伴Mike Cafarella合作,开发了一款可以代替当时的主流搜索的开源搜索引擎,命名为Nutch

http://static.cyblogs.com/aqYeVeXiaLwMxssV.png

Nutch是一个建立在Lucene核心之上的网页搜索应用程序,可以下载下来直接使用。它在Lucene的基础上加了网络爬虫和一些网页相关的功能,目的就是从一个简单的站内检索推广到全球网络的搜索上,就像Google一样。

Nutch在业界的影响力比Lucene更大。

大批网站采用了Nutch平台,大大降低了技术门槛,使低成本的普通计算机取代高价的Web服务器成为可能。甚至有一段时间,在硅谷有了一股用Nutch低成本创业的潮流。

随着时间的推移,无论是Google还是Nutch,都面临搜索对象“体积”不断增大的问题。

尤其是Google,作为互联网搜索引擎,需要存储大量的网页,并不断优化自己的搜索算法,提升搜索效率。

http://static.cyblogs.com/TAruKa8WKbr3qDia9ba.jpg

Google搜索栏

在这个过程中,Google确实找到了不少好办法,并且无私地分享了出来。

2003年,Google发表了一篇技术学术论文,公开介绍了自己的谷歌文件系统GFS(Google File System)。这是Google公司为了存储海量搜索数据而设计的专用文件系统。

第二年,也就是2004年,Doug Cutting基于Google的GFS论文,实现了分布式文件存储系统,并将它命名为NDFS(Nutch Distributed File System)

http://static.cyblogs.com/google_gfs_ndfs.jpg

还是2004年,Google又发表了一篇技术学术论文,介绍自己的MapReduce编程模型。这个编程模型,用于大规模数据集(大于1TB)的并行分析运算。

第二年(2005年),Doug Cutting又基于MapReduce,在Nutch搜索引擎实现了该功能。

http://static.cyblogs.com/goole_mapreduce.jpg

2006年,当时依然很厉害的Yahoo(雅虎)公司,招安了Doug Cutting。

http://static.cyblogs.com/goole_mapreduce_002.jpg

这里要补充说明一下雅虎招安Doug的背景:2004年之前,作为互联网开拓者的雅虎,是使用Google搜索引擎作为自家搜索服务的。在2004年开始,雅虎放弃了Google,开始自己研发搜索引擎。所以。。。

加盟Yahoo之后,Doug Cutting将NDFS和MapReduce进行了升级改造,并重新命名为Hadoop(NDFS也改名为HDFS,Hadoop Distributed File System)。

这个,就是后来大名鼎鼎的大数据框架系统——Hadoop的由来。而Doug Cutting,则被人们称为Hadoop之父

http://static.cyblogs.com/goole_mapreduce_003.jpg

Hadoop这个名字,实际上是Doug Cutting他儿子的黄色玩具大象的名字。所以,Hadoop的Logo,就是一只奔跑的黄色大象。

http://static.cyblogs.com/goole_mapreduce_004.jpg

我们继续往下说。

还是2006年,Google又发论文了。

这次,它们介绍了自己的BigTable。这是一种分布式数据存储系统,一种用来处理海量数据的非关系型数据库。

Doug Cutting当然没有放过,在自己的hadoop系统里面,引入了BigTable,并命名为HBase

http://static.cyblogs.com/goole_mapreduce_005.jpg

好吧,反正就是紧跟Google时代步伐,你出什么,我学什么。

所以,Hadoop的核心部分,基本上都有Google的影子。

http://static.cyblogs.com/goole_mapreduce_006.png

其实从这里也能看到,站在巨人肩膀上或者仿照强者,也可以走出一条属于自己的道路。

安装Elasticsearch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
➜  Tools  brew search elasticsearch
==> Formulae
elasticsearch elasticsearch@2.4 elasticsearch@5.6

➜ Tools brew install elasticsearch@5.6
==> Downloading https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.16.tar.gz
######################################################################## 100.0%
Warning: elasticsearch@5.6 has been deprecated!
==> Caveats
Data: /usr/local/var/elasticsearch/elasticsearch_chenyuan/
Logs: /usr/local/var/log/elasticsearch/elasticsearch_chenyuan.log
Plugins: /usr/local/opt/elasticsearch@5.6/libexec/plugins/
Config: /usr/local/etc/elasticsearch/
plugin script: /usr/local/opt/elasticsearch@5.6/libexec/bin/elasticsearch-plugin

elasticsearch@5.6 is keg-only, which means it was not symlinked into /usr/local,
because this is an alternate version of another formula.

If you need to have elasticsearch@5.6 first in your PATH run:
echo 'export PATH="/usr/local/opt/elasticsearch@5.6/bin:$PATH"' >> ~/.zshrc


To have launchd start elasticsearch@5.6 now and restart at login:
brew services start elasticsearch@5.6
Or, if you don't want/need a background service you can just run:
/usr/local/opt/elasticsearch@5.6/bin/elasticsearch
==> Summary
/usr/local/Cellar/elasticsearch@5.6/5.6.16: 106 files, 36.0MB, built in 10 seconds
==> `brew cleanup` has not been run in 30 days, running now...
Removing: /Users/chenyuan/Library/Caches/Homebrew/erlang--22.1.2.mojave.bottle.tar.gz... (77.3MB)
Removing: /Users/chenyuan/Library/Caches/Homebrew/gettext--0.20.1.catalina.bottle.tar.gz... (8.3MB)
Removing: /Users/chenyuan/Library/Caches/Homebrew/icu4c--64.2.catalina.bottle.tar.gz... (26.1MB)
Removing: /Users/chenyuan/Library/Caches/Homebrew/jpeg--9c.mojave.bottle.tar.gz... (300.8KB)
Removing: /Users/chenyuan/Library/Caches/Homebrew/libpng--1.6.37.mojave.bottle.tar.gz... (442.2KB)
Removing: /Users/chenyuan/Library/Caches/Homebrew/libtiff--4.0.10_1.mojave.bottle.tar.gz... (1MB)
Removing: /Users/chenyuan/Library/Caches/Homebrew/node--12.11.1.catalina.bottle.tar.gz... (14.8MB)
Removing: /Users/chenyuan/Library/Caches/Homebrew/openssl@1.1--1.1.1d.mojave.bottle.tar.gz... (5.2MB)
Removing: /Users/chenyuan/Library/Caches/Homebrew/perl--5.30.0.catalina.bottle.tar.gz... (16.3MB)
Removing: /Users/chenyuan/Library/Caches/Homebrew/rabbitmq--3.8.0.tar.xz... (11MB)
Removing: /Users/chenyuan/Library/Caches/Homebrew/subversion--1.12.2_1.catalina.bottle.1.tar.gz... (10MB)
Removing: /Users/chenyuan/Library/Caches/Homebrew/utf8proc--2.4.0.catalina.bottle.tar.gz... (152.2KB)
Removing: /Users/chenyuan/Library/Caches/Homebrew/wxmac--3.0.4_2.mojave.bottle.tar.gz... (7.4MB)
Removing: /Users/chenyuan/Library/Logs/Homebrew/icu4c... (64B)
Removing: /Users/chenyuan/Library/Logs/Homebrew/node... (64B)
Pruned 0 symbolic links and 2 directories from /usr/local
阅读全文 »

看一个最简单的CGLIB的例子,感受一下AOP是如何做到的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* Created with vernon-test
* Description:
* User: chenyuan
* Date: 16/4/25
* Time: 上午9:25
*/
public class Target {
public String execute() {
String message = "----------test()----------";
System.out.println(message);
return message;
}
}

/**
* Created with vernon-test
* Description:
* User: chenyuan
* Date: 16/4/25
* Time: 上午9:25
*/
public class MyMethodInterceptor implements MethodInterceptor {

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(">>>MethodInterceptor start...");
Object result = proxy.invokeSuper(obj, args);
System.out.println(">>>MethodInterceptor ending...");
return "haha";
}
}

/**
* Created with vernon-test
* Description:
* User: chenyuan
* Date: 16/4/25
* Time: 上午9:28
*/
public class CglibProxyTest {

public Object createProxy(Class targetClass) {
// 第一步
Enhancer enhancer = new Enhancer();
// 第二步
enhancer.setSuperclass(targetClass);
// 第三步
enhancer.setCallback(new MyMethodInterceptor());
// 第四步
return enhancer.create();
}

public static void main(String rags[]) {
CglibProxyTest cglibProxyTest = new CglibProxyTest();
Target proxyTarget = (Target) cglibProxyTest.createProxy(Target.class);
String res = proxyTarget.execute();
System.out.println(res);
}

}

执行后的结果显示

1
2
3
4
5
6
Connected to the target VM, address: '127.0.0.1:55868', transport: 'socket'
>>>MethodInterceptor start...
----------test()----------
>>>MethodInterceptor ending...
haha
Disconnected from the target VM, address: '127.0.0.1:55868', transport: 'socket'

实际上在执行execute()的前后就各自做了自己想要的操作。其实这个就是Spring AOP对简单的一个原型。

@Transaction的工作原理

SpringTransactionInterceptorPlatformTransactionManager这两个类是整个事务模块的核心,TransactionInterceptor负责拦截方法执行,进行判断是否需要提交或者回滚事务。PlatformTransactionManager是Spring 中的事务管理接口,真正定义了事务如何回滚和提交。我们重点研究下这两个类的源码。

TransactionInterceptor类中的代码有很多,我简化一下逻辑,方便说明:

1
2
3
4
5
6
7
//以下代码省略部分内容
public Object invoke(MethodInvocation invocation) throws Throwable {
//获取事务调用的目标方法
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
//执行带事务调用
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

其实,这里也就是因为用到了动态代理。在事务回滚这个动作的前前后后可以做自己想要的东西。这是一个非常重要的设计思想。如果我们自己要写框架,这个模式可以作为你的第一参考。

阅读全文 »

1. 概述

一直在用SpringBoot中的@Transactional来做事务管理,但是很少没想过SpringBoot是如何实现事务管理的,今天从源码入手,看看@Transactional是如何实现事务的,最后我们结合源码的理解,自己动手写一个类似的注解来实现事务管理,帮助我们加深理解。

阅读说明:本文假设你具备Java基础,同时对事务有基本的了解和使用。

2. 事务的相关知识

开始看源码之前,我们先回顾下事务的相关知识。

2.1 事务的隔离级别

事务为什么需要隔离级别呢?这是因为在并发事务情况下,如果没有隔离级别会导致如下问题:

  • 脏读(Dirty Read) :当A事务对数据进行修改,但是这种修改还没有提交到数据库中,B事务同时在访问这个数据,由于没有隔离,B获取的数据有可能被A事务回滚,这就导致了数据不一致的问题。
  • 丢失修改(Lost To Modify): 当A事务访问数据100,并且修改为100-1=99,同时B事务读取数据也是100,修改数据100-1=99,最终两个事务的修改结果为99,但是实际是98。事务A修改的数据被丢失了。
  • 不可重复读(Unrepeatable Read):指A事务在读取数据X=100的时候,B事务把数据X=100修改为X=200,这个时候A事务第二次读取数据X的时候,发现X=200了,导致了在整个A事务期间,两次读取数据X不一致了,这就是不可重复读。
  • 幻读(Phantom Read):幻读和不可重复读类似。幻读表现在,当A事务读取表数据时候,只有3条数据,这个时候B事务插入了2条数据,当A事务再次读取的时候,发现有5条记录了,平白无故多了2条记录,就像幻觉一样。

不可重复读 VS 幻读

不可重复读的重点是修改 : 同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了,重点在更新操作。 幻读的重点在于新增或者删除:同样的条件 , 第 1 次和第 2 次读出来的记录数不一样,重点在增删操作。

阅读全文 »
0%