开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

go-zero实战:让微服务Go起来——3 服务拆分

发表于 2021-11-29

一个商城项目可拆分用户服务(user),订单服务(order),产品服务(product),支付服务(pay),售后服务(afterSale),……

每个服务都可以再分为 api 服务和 rpc 服务。api 服务对外,可提供给 app 调用。rpc 服务是对内的,可提供给内部 api 服务或者其他 rpc 服务调用。

3.1 用户服务(user)

api 服务 端口:8000 rpc 服务 端口:9000
login 用户登录接口 login 用户登录接口
register 用户注册接口 register 用户注册接口
userinfo 用户信息接口 userinfo 用户信息接口
……. ……. ……. …….

3.2 产品服务(product)

api 服务 端口:8001 rpc 服务 端口:9001
create 产品创建接口 create 产品创建接口
update 产品修改接口 update 产品修改接口
remove 产品删除接口 remove 产品删除接口
detail 产品详情接口 detail 产品详情接口
……. ……. ……. …….

3.3 订单服务(order)

api 服务 端口:8002 rpc 服务 端口:9002
create 订单创建接口 create 订单创建接口
update 订单修改接口 update 订单修改接口
remove 订单删除接口 remove 订单删除接口
detail 订单详情接口 detail 订单详情接口
list 订单列表接口 list 订单列表接口
paid 订单支付接口
……. ……. ……. …….

3.4 支付服务(pay)

api 服务 端口:8003 rpc 服务 端口:9003
create 支付创建接口 create 支付创建接口
detail 支付详情接口 detail 支付详情接口
callback 支付回调接口 callback 支付回调接口
……. ……. ……. …….

3.5 创建项目目录

  • 创建 mall 工程
1
2
bash复制代码$ mkdir mall && cd mall
$ go mod init mall
  • 创建 common 目录
1
bash复制代码$ mkdir common
  • 创建 service 目录
1
bash复制代码$ mkdir service && cd service
  • 创建 user api,user rpc,user model 目录
1
2
3
bash复制代码$ mkdir -p user/api
$ mkdir -p user/rpc
$ mkdir -p user/model
  • 创建 product api,product rpc,product model 目录
1
2
3
bash复制代码$ mkdir -p product/api
$ mkdir -p product/rpc
$ mkdir -p product/model
  • 创建 order api,order rpc,order model 目录
1
2
3
bash复制代码$ mkdir -p order/api
$ mkdir -p order/rpc
$ mkdir -p order/model
  • 创建 pay api,pay rpc,pay model 目录
1
2
3
bash复制代码$ mkdir -p pay/api
$ mkdir -p pay/rpc
$ mkdir -p pay/model
  • 最终项目目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bash复制代码├── common           # 通用库
├── service # 服务
│ ├── order
│ │ ├── api # order api 服务
│ │ ├── model # order 数据模型
│ │ └── rpc # order rpc 服务
│ ├── pay
│ │ ├── api # pay api 服务
│ │ ├── model # pay 数据模型
│ │ └── rpc # pay rpc 服务
│ ├── product
│ │ ├── api # product api 服务
│ │ ├── model # product 数据模型
│ │ └── rpc # product rpc 服务
│ └── user
│ ├── api # user api 服务
│ ├── model # user 数据模型
│ └── rpc # user rpc 服务
└── go.mod

项目地址:github

上一篇《go-zero实战:让微服务Go起来——2 环境搭建》

下一篇《go-zero实战:让微服务Go起来——4 用户服务(user)》

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【笔记】动态规划系列学习之 最长上升子序列(LIS)问题全解

发表于 2021-11-29

这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战.


lis - Longest Increasing Subsequence - 最长上升子序列
#1.LIS问题
给定一个序列,找出其中最长的上升子序列,输出长度
###O(nlogn)做法
维护一个集合,第i个元素表示长度为i的上升子序列末尾数字.
每次出现新的数字时,查找并替换它的lower_bound,如果没有则直接插入.
集合长度即为lis长度.
###例题: HDU 1025
排序后求lis,注意roads.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cpp复制代码pair<int,int> save[M];
int main(void)
{
int n,kase=0;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
save[i].first=read(),save[i].second=read();
sort(save,save+n);
set<int> st;
for(int i=0;i<n;i++)
{
int val = save[i].second;
auto it = st.lower_bound(val);
if(it != st.end()) st.erase(it);
st.insert(val);
}
printf("Case %d:\nMy king, at most %u road%s can be built.\n\n",
++kase,st.size(),st.size()==1?"":"s" );
}
return 0;
}

#2.LIS问题的变体
1.最初版本:最长上升子序列

1
2
3
4
5
6
7
8
cpp复制代码set<int> st;
for(int i=0;i<n;i++)
{
int val;
auto it = st.lower_bound(val);
if(it != st.end()) st.erase(it);
st.insert(val);
}

2.最长下降子序列
将原程序中的set<int>改为 set<int,greater<int>>
3.最长不下降子序列
将原程序中的set改为multiset,lower_bound改为upper_bound
4.最长不上升子序列
综合2与3.

例题: HDU 5532

直接求lis(3)与lis(4),判断长度是否都小于n-1

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
cpp复制代码int save[M];
int main(void)
{
int t=read();
while(t--)
{
int n=read();
for(int i=0;i<n;i++) save[i]=read();
multiset<int> st;
for(int i=0;i<n;i++)
{
int val = save[i];
auto it = st.upper_bound(val);
if(it!=st.end()) st.erase(it);
st.insert(val);
}
multiset<int,greater<int>> st2;
for(int i=0;i<n;i++)
{
int val = save[i];
auto it2 = st2.upper_bound(val);
if(it2!=st2.end()) st2.erase(it2);
st2.insert(val);
}
printf("%s\n",(int)st.size()>=n-1||(int)st2.size()>=n-1?"YES":"NO" );
}

return 0;
}

#3.Dilworth定理

Dilworth定理:对于一个有穷偏序集,最少链划分等于最长反链长度。
Dilworth对偶定理:对于一个有穷偏序集,其最少反链划分数等于其最长链的长度。

用人话说,在一个序列中下降子序列的最少个数就等于其最长不下降子序列长度,反过来同理,上升同理,即
lis(1)的长度=lis(4)的个数,
lis(4)的长度=lis(1)的个数,
lis(2)的长度=lis(3)的个数,
lis(3)的长度=lis(2)的个数,
可以简记为,”不”字可以将长度与个数转换.
###例题: luogu P1020 导弹拦截
求lis(4)的长度与个数,即求lis(4)和lis(1).

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
cpp复制代码int save[M];
int main(void)
{
int n=0;
while(scanf("%d",&save[n])!=EOF)
n++;
multiset<int,greater<int>> st4;
for(int i=0;i<n;i++)
{
int val = save[i];
auto it = st4.upper_bound(val);
if(it!=st4.end()) st4.erase(it);
st4.insert(val);
}
set<int> st1;
for(int i=0;i<n;i++)
{
int val = save[i];
auto it = st1.lower_bound(val);
if(it!=st1.end()) st1.erase(it);
st1.insert(val);
}
printf("%u\n%u\n",st4.size(),st1.size() );

return 0;
}

#4.输出答案
给定一个序列,输出LIS长度及LIS.
###O(nlogn)做法
集合中不再维护值,而是维护对应下标,比较规则仍按值比较.
对下标记录一个前驱数组,表示这个下标如果在lis中,它的上一个数是哪个下标.
集合中每插入一个新下标时,这个下标的前驱就是它在集合中上一位的下标.(或者-1)
输出时从集合最后一个元素开始按前驱倒序输出.

###例题: HDU 1160
按速度从大到小排序,求体重的lis.

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
cpp复制代码struct Item{
int w,s,id;
}save[M];
int cmp_sort(Item a,Item b){
return a.s==b.s?a.w<b.w:a.s>b.s;
}
struct cmp{
bool operator()(int a,int b){
return save[a].w<save[b].w;
}
};
int last[M];
void print(int a)
{
if(a==-1) return;
print(last[a]);
printf("%d\n",save[a].id );
}
int main(void)
{
int n=0;
while(scanf("%d%d",&save[n].w,&save[n].s)!=EOF)
{
save[n].id = n+1;
n++;
}
sort(save,save+n,cmp_sort);
set<int,cmp> st;
for(int i=0;i<n;i++)
{
auto it = st.lower_bound(i);
if(it != st.end()) st.erase(it);
it = st.insert(i).first;
last[i] = (it==st.begin()?-1:*(--it));
}
printf("%u\n",st.size() );
print(*st.rbegin());

return 0;
}

这是一个AC代码,但是可以被hack.
1 4 2 3 3 2 4 1 5 1

#5.记录信息
通过输出答案可以看出,跑一遍lis可以记录很多信息,比如以每个位置为结尾的lis长度.
本质上输出答案也是记录了信息:序列中上一个数的位置.
###Luogu P1091 合唱队形
给一个数字序列,求最少删去几个数能使得序列成为一个先升再降的序列.上升下降部分均可以没有,即lis(1)或lis(2).

跑一遍lis(1),记录以每个位置为结尾的lis长度,记作in.
再倒序跑一遍lis(1),记录以每个位置为结尾的倒序lis长度,记作out.
答案就是maxi=0n−1(n−in[i]−out[i]+1)max_{i=0}^{n-1}(n-in_{[i]}-out_{[i]}+1)maxi=0n−1​(n−in[i]​−out[i]​+1)
out中实际记录的是以每个位置为开头的lis(2)长度.

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
cpp复制代码int save[M];
int in[M],out[M];
struct cmp{
bool operator() (int a,int b){
return save[a]<save[b];
}
};
int main(void)
{
int n=read();
for(int i=0;i<n;i++)
save[i]=read();

set<int,cmp> st;
for(int i=0;i<n;i++)
{
auto it = st.lower_bound(i);
if(it!=st.end()) st.erase(it);
it = st.insert(i).first;
in[i] = it==st.begin()?1:in[*(--it)]+1;
}
st.clear();
for(int i=n-1;i>=0;i--)
{
auto it = st.lower_bound(i);
if(it!=st.end()) st.erase(it);
it = st.insert(i).first;
out[i] = it==st.begin()?1:out[*(--it)]+1;
}

int ans=n;
for(int i=0;i<n;i++)
ans = min(ans,n-in[i]-out[i]+1);
printf("%d\n",ans );
return 0;
}

#6. 习题

Luogu P1439

求两个1到n排列的LCS.

映射后求LIS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cpp复制代码int mp[M];
int main(void)
{
int n=read();
for(int i=1;i<=n;i++) mp[read()]=i;
set<int> st;
for(int i=1;i<=n;i++)
{
int val = mp[read()];
auto it = st.lower_bound(val);
if(it!=st.end()) st.erase(it);
st.insert(val);
}
printf("%u\n",st.size() );
return 0;
}

本文也发表于我的 csdn 博客中。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

LeetCode645 错误的集合

发表于 2021-11-29

这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战。

题目描述:

645. 错误的集合 - 力扣(LeetCode) (leetcode-cn.com)

集合 s 包含从 1 到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个数字复制了成了集合里面的另外一个数字的值,导致集合 丢失了一个数字 并且 有一个数字重复 。

给定一个数组 nums 代表了集合 S 发生错误后的结果。

请你找出重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。

示例一

1
2
ini复制代码输入: nums = [1,2,2,4]
输出: [2,3]

示例二

1
2
ini复制代码输入: nums = [1,1]
输出: [1,2]

提示:

  • 2 <= nums.length <= 10^4
  • 1 <= nums[i] <= 10^4

思路分析

排序

这个比较简单,排序之后,每每比较相邻元素,如果相等,说明为重复元素,至于丢失的元素也很好找,我们只要注意一下开头和结尾的边界情况即可。

哈希表

根据题意,这本是一个1到n的数组,现在丢失了一个数字,变成了一个重复的数字,结果一定是重复的数组是2个,丢失的数组是0个。

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Java复制代码class Solution {
public int[] findErrorNums(int[] nums) {
int[] errorNums = new int[2];
int n = nums.length;
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
for (int i = 1; i <= n; i++) {
int count = map.getOrDefault(i, 0);
if (count == 2) {
errorNums[0] = i;
} else if (count == 0) {
errorNums[1] = i;
}
}
return errorNums;
}
}

总结

虽然是一道简单的题,但是解法多多,除了上面的2个之外,官方还有位运算的解法,三叶姐也有多种其他解法,学海无涯。

参考

645. 错误的集合 题解 - 力扣(LeetCode) (leetcode-cn.com)

【宫水三叶】一题三解:「计数」&「数学」&「桶排序」 - 错误的集合 - 力扣(LeetCode) (leetcode-cn.com)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

力扣:473 火柴拼正方形 - 的思考过程

发表于 2021-11-29

473. 火柴拼正方形 - 力扣(LeetCode) (leetcode-cn.com)

题目:

是否能利用给定数组中的所有数拼出一个正方形。

思考过程:

看题目的数据范围就知道是用状态压缩。当然,这类题目很多时候都可以用dfs + 剪枝技巧 来做。包括我自己以前都是喜欢一股脑地上dfs,但是这次专门挑战下就用状态压缩dp。原因有两个:1)处于锻炼自身技能的目的 2)dfs经常要搭配一些剪枝技巧,有时候并不是很容易找出来剪枝技巧的思路。

这道题属于十分经典的状态压缩类的(分组)问题。典型的套路就是:将原问题拆解为子问题的解。还有一道比较类似的可以参考:1681. 最小不兼容性 - 力扣(LeetCode) (leetcode-cn.com)

在这里的情况就是将数组中所有数分到4组,看是否每一组的和都相等。所以我们可以提前计算出数组总和,然后除以4就能求出边长,也即每个分组的和

递推公式如下:

f[k][mask]∣=f[k−1][mask⊕subset],其中valid[subset]=true,表示数组中的某个子集subset代表的数之和是否等于正方形的边长f[k][mask] \quad |= f[k-1][mask \oplus subset],其中valid[subset] = true,表示数组中的某个
\子集subset代表的数之和是否等于正方形的边长f[k][mask]∣=f[k−1][mask⊕subset],其中valid[subset]=true,表示数组中的某个子集subset代表的数之和是否等于正方形的边长
顺便说一下两个位运算的经典作用:

1.求所有子集

1
2
3
4
5
6
7
8
9
ini复制代码int subset = mask;
while (subset > 0) {
subset = (subset - 1) & mask;
}

也可以:
for (int subset = mask; subset > 0; subset = (subset - 1) & mask) {

}

2.将选定的数置0

1
2
css复制代码mask = mask ^ subset;
将二进制表示的mask 中的subset 位置 置为0

我们可以先预处理valid数组,看哪些数字组合可以放到一组中,然后再利用上面的递推式枚举所有状态,最后答案是:

f[k][(1<<nums.length)−1]f[k][(1 << nums.length) - 1]f[k][(1<<nums.length)−1]
一开始的代码如下:

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
java复制代码   public boolean makesquare(int[] matchsticks) {
long sum = 0;
for (int i = 0; i < matchsticks.length; i++) {
sum += matchsticks[i];
}
if (sum % 4 != 0) {
return false;
}
int sidelen = (int) (sum / 4);
boolean[][] valid = new boolean[4][1 << matchsticks.length];
for (int i = 0; i < (1 << matchsticks.length); i++) {
long tmpSum = 0L;
for (int j = 0; j < matchsticks.length; j++) {
if (((1 << j) & i) != 0) {
tmpSum += matchsticks[j];
}
}
if (tmpSum == sidelen) {
valid[0][i] = true;
}

for (int i = 1; i < 4; i++) {
for (int j = 0; j < (1 << matchsticks.length); j++) {
for (int subset = j; subset > 0; subset = (subset - 1) & j) {

if (valid[0][subset]) {
valid[i][j] |= valid[i - 1][j ^ subset];
}
}
}
}

return valid[3][(1 << matchsticks.length) - 1];
}

image-20211129224342302.png

2300多ms通过你敢信。。。?

当然本着学习的态度,这显然是无法接受的。

于是我在原来的代码上加了很多输出,帮助自己寻找“重复”的计算,类似于:

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
ini复制代码   public boolean makesquare(int[] matchsticks) {
long sum = 0;
for (int i = 0; i < matchsticks.length; i++) {
sum += matchsticks[i];
}
if (sum % 4 != 0) {
return false;
}
int sidelen = (int) (sum / 4);
boolean[][] valid = new boolean[4][1 << matchsticks.length];
for (int i = 0; i < (1 << matchsticks.length); i++) {
long tmpSum = 0L;
for (int j = 0; j < matchsticks.length; j++) {
if (((1 << j) & i) != 0) {
tmpSum += matchsticks[j];
}
}
if (tmpSum == sidelen) {
valid[0][i] = true;
}
}

for (int i = 1; i < 4; i++) {
for (int j = 0; j < (1 << matchsticks.length); j++) {
for (int subset = j; subset > 0; subset = (subset - 1) & j) {

if (valid[0][subset]) {
valid[i][j] |= valid[i - 1][j ^ subset]

}
}
}
}

// for (int i = 0; i < 4; i++) {
// for (int j = 0; j < (1 << matchsticks.length); j++) {
//// StringJoiner stringJoiner = new StringJoiner(",");
//// for (int k = 0; k < matchsticks.length; k++) {
//// if (((1 << k) & j) != 0) {
//// stringJoiner.add(matchsticks[k] + "");
//// }
//// }
//// System.out.println(stringJoiner.toString() + " " + valid[i][j]);
// System.out.println(translate(matchsticks, j) + " " + valid[i][j]);
// }
// System.out.println("***************");
// }
return valid[3][(1 << matchsticks.length) - 1];
}

private String translate(int[] matchsticks, int chosen) {

StringJoiner stringJoiner = new StringJoiner(",");
for (int k = 0; k < matchsticks.length; k++) {
if (((1 << k) & chosen) != 0) {
stringJoiner.add(matchsticks[k] + "");
}
}
//System.out.println(stringJoiner.toString() + " ");
return stringJoiner.toString() + " ";
}

终于,在观察了半天打印出来的输出之后,我发现:其实我一直迷惑了自己,双层for循环其实是“不重不漏”的,没有地方重复计算了,每一步都是“合理”的,比如,当要求f[3][mask]的时候,就需要知道f[2][mask ^ subset]的各种情况。

真正耗时的是由很多计算其实是不需要的,所以只需要加上判断:

1
2
3
css复制代码if (sidesum[j] != (i + 1) * sidelen) {
continue;
}

就能够提减少运算。

它的含义是在当前k=i的情况下,只有那些sidesum[j] == (i + 1) * sidelen才有可能 为true

完整代码如下:

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
java复制代码public boolean makesquare(int[] matchsticks) {
long sum = 0;
for (int i = 0; i < matchsticks.length; i++) {
sum += matchsticks[i];
}
if (sum % 4 != 0) {
return false;
}
int sidelen = (int) (sum / 4);
boolean[][] valid = new boolean[4][1 << matchsticks.length];
long[] sidesum = new long[1 << matchsticks.length];

for (int i = 0; i < (1 << matchsticks.length); i++) {
long tmpSum = 0L;
for (int j = 0; j < matchsticks.length; j++) {
if (((1 << j) & i) != 0) {
tmpSum += matchsticks[j];
}
}
sidesum[i] = tmpSum;
if (tmpSum == sidelen) {
valid[0][i] = true;
}
}

//int count = 0;
for (int i = 1; i < 4; i++) {
for (int j = 0; j < (1 << matchsticks.length); j++) {
if (sidesum[j] != (i + 1) * sidelen) {
continue;
}
//count++;
for (int subset = j; subset > 0; subset = (subset - 1) & j) {
if (valid[0][subset]) {
valid[i][j] = valid[i - 1][j ^ subset];
}
if (valid[i][j]) {
break;
}
}
}
}

//System.out.println(count);
return valid[3][(1 << matchsticks.length) - 1];
}

另外,其实可以把双层循环改成一层就够了,如下:

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
java复制代码public boolean makesquare(int[] matchsticks) {
long sum = 0;
for (int i = 0; i < matchsticks.length; i++) {
sum += matchsticks[i];
}
if (sum % 4 != 0) {
return false;
}
int sidelen = (int) (sum / 4);
boolean[] valid = new boolean[1 << matchsticks.length];
long[] sidesum = new long[1 << matchsticks.length];

for (int i = 0; i < (1 << matchsticks.length); i++) {
long tmpSum = 0L;
for (int j = 0; j < matchsticks.length; j++) {
if (((1 << j) & i) != 0) {
tmpSum += matchsticks[j];
}
}
sidesum[i] = tmpSum;
if (tmpSum == sidelen) {
valid[i] = true;
}
}


//int count = 0;
for (int j = 0; j < (1 << matchsticks.length); j++) {
if (sidesum[j] % sidelen == 0) {
// if (sidesum[j] / sidelen > 1) {
// count++;
// }

for (int subset = j; subset > 0; subset = (subset - 1) & j) {

if (valid[subset]) {
valid[j] |= valid[j ^ subset];
}
if (valid[j]) {
break;
}
}
}
}
//System.out.println(count);
return valid[(1 << matchsticks.length) - 1];
}

上面的两个代码我加了count来对比是否计算量一致,最后发现是一致的。

最终提交的时间:

image-20211129225933699.png

其实跟dfs+剪枝比还是不快的,但是这种套路应用性好一点,毕竟有些剪枝技巧实在是不容易看出来。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

红帽linux之访问linux文件系统

发表于 2021-11-29

「这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战」

访问Linux文件系统

文件系统与挂载点:

1
2
3
4
csharp复制代码对于Linux文件系统层次结构,无需知道特定文件所在的存储设备,只需要知道该文件所在的目录即可。但需要知道存储设备与目录的关联关系,及空间使用情况。
文件系统可以挂载到一个目录上,该目录称为挂载点(mount point),挂载支持手动挂载和自动挂载。文件系统、存储与块设备:
Linux中,对存储设备的低级别访问是访问块设备(block device)文件。在挂载这些块设备前,必须先使用文件系统对其进行格式化。块设备存储在/dev目录中。
在RHEL中,第一个SATA/PATA、SAS、SCSI或USB设备称为/dev/sda,第二个被称 为/dev/sdb。

注意:

1
2
csharp复制代码1. 根据不同virtio驱动来命名不同的磁盘盘符。
2. virtio_blk内核模块:/dev/vdX;virtio_scsi内核模块:/dev/sdX
1
2
3
4
5
6
7
8
csharp复制代码 [root@VM-0-3-centos ~]# lsmod | grep virtio
virtio_balloon 18015 0
virtio_net 28085 0
net_failover 18147 1 virtio_net
virtio_blk 18472 2
virtio_pci 22985 0
virtio_ring 22991 4 virtio_blk,virtio_net,virtio_pci,virtio_balloon
virtio 14959 4 virtio_blk,virtio_net,virtio_pci,virtio_balloon

磁盘分区:

1
2
3
4
5
6
7
csharp复制代码存储设备通常划分为更小的区块,称为分区(partition)。
不同分区可以通过不同的文件系统进行格式化或用于不同的用途。
分区本身就是块设备。
第一磁盘上的第一个分区是/dev/sda1,第二磁盘上的第三个分区是/dev/sdb3,
vda分区类似。
NVMe SSD命名略有不同。
第一磁盘上的第一个分区是/dev/nvme0p1,第二磁盘上的第三个分区 是/dev/nvme1p3。

逻辑卷:

1
2
3
4
5
6
7
csharp复制代码逻辑卷管理(LVM)可用于整理磁盘和分区。
一个或多个块设备可以汇集为一个存储池,称为卷组(volume group)。
卷组中的磁盘空间被分配到一个或多个逻辑卷(logical volume),它们的功能等
同于物理磁盘上的分区。
LVM的目录结构类似/dev/myvg/mylv,其中myvg为卷组,mylv为逻辑卷。
/dev/mapper/myvg_mylv是另一种命名方式。
以上两种均为实际设备文件的软链接

检查文件系统:
df命令用于显示文件系统的空间状态与挂载点。

1
2
3
4
5
6
7
8
csharp复制代码[root@VM-0-3-centos ~]# df
Filesystem 1K-blocks Used Available Use% Mounted on
devtmpfs 929388 0 929388 0% /dev
tmpfs 941004 24 940980 1% /dev/shm
tmpfs 941004 508 940496 1% /run
tmpfs 941004 0 941004 0% /sys/fs/cgroup
/dev/vda1 51473868 4313780 44962844 9% /
tmpfs 188204 0 188204 0% /run/user/0
1
2
3
4
csharp复制代码为增加可读性,可以添加 -h 或 -H 选项, -h 单位是 KiB(2^10)、
MiB(2^20)
或 GiB(2^30),-H 单位是 SI 单位,即 KB(10^3)、MB(10^6)或
GB(10^9)。

du命令可以查看特定目录使用空间状况,同样可以使用 -h 和 -H 选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
csharp复制代码[root@VM-0-3-centos ~]# du /usr/share
20K /usr/share/X11/locale/vi_VN.viscii
2.1M /usr/share/X11/locale
2.1M /usr/share/X11
88K /usr/share/ucx/examples
28K /usr/share/ucx/perftest
120K /usr/share/ucx
140K /usr/share/bison/m4sugar
68K /usr/share/bison/xslt

[root@VM-0-3-centos ~]# du -h /var/log
4.0K /usr/share/locale/zh_CN/LC_TIME
3.6M /usr/share/locale/zh_CN
4.0K /usr/share/locale/del/LC_MESSAGES
8.0K /usr/share/locale/del
2.4M /usr/share/locale/sr/LC_MESSAGES

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

5940 从数组中移除最大值和最小值

发表于 2021-11-29

这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战

  1. 从数组中移除最大值和最小值

给你一个下标从 0 开始的数组 nums ,数组由若干 互不相同 的整数组成。

nums 中有一个值最小的元素和一个值最大的元素。分别称为 最小值 和 最大值 。你的目标是从数组中移除这两个元素。

一次 删除 操作定义为从数组的 前面 移除一个元素或从数组的 后面 移除一个元素。

返回将数组中最小值和最大值 都 移除需要的最小删除次数。

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
ini复制代码示例 1:

输入:nums = [2,10,7,5,4,1,8,6]
输出:5
解释:
数组中的最小元素是 nums[5] ,值为 1 。
数组中的最大元素是 nums[1] ,值为 10 。
将最大值和最小值都移除需要从数组前面移除 2 个元素,从数组后面移除 3 个元素。
结果是 2 + 3 = 5 ,这是所有可能情况中的最小删除次数。

示例 2:

输入:nums = [0,-4,19,1,8,-2,-3,5]
输出:3
解释:
数组中的最小元素是 nums[1] ,值为 -4 。
数组中的最大元素是 nums[2] ,值为 19 。
将最大值和最小值都移除需要从数组前面移除 3 个元素。
结果是 3 ,这是所有可能情况中的最小删除次数。

示例 3:

输入:nums = [101]
输出:1
解释:
数组中只有这一个元素,那么它既是数组中的最小值又是数组中的最大值。
移除它只需要 1 次删除操作。

提示:

  • 1 <= nums.length <= 10510^5105
  • −105-10^5−105 <= nums[i] <= 10510^5105
  • nums 中的整数 互不相同

解题思路

  1. 统计最大值和最小值所在的下标,因为在这题中,哪个是最大值,哪个是最小值对题目并不影响,因此我们只需要知道左边极值的下标和右边极值的下标即可。
  2. 共有三种删除方法
  • 从数组的 前面 移除元素,直到把右边的极值移除
  • 从数组的 后面 移除元素,直到把左边的极值移除
  • 从数组的两边一齐移除元素,直到把左边和右边的极值移除

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cpp复制代码class Solution {
public:
int minimumDeletions(vector<int>& nums) {

int max_i(0),min_i(0),n=nums.size();
for (int i = 1; i < nums.size(); ++i) {
if (nums[i]>nums[max_i])
max_i=i;
if (nums[i]<nums[min_i])
min_i=i;
}
int l=min(max_i,min_i),r=max(max_i,min_i);

return min(l+1+n-r,min(r+1,n-l));

}
};

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MySQL 架构体系和运行机制

发表于 2021-11-29

MySQL 架构

MySQL 的架构体系,如下图:

MySQL Server 架构自顶向下大致可以分为网络连接层、服务层、存储引擎层和系统文件层。

网络连接层

网络连接层要是客户端连接器(Client Connectors):它提供了与 MySQL 服务器建立连接的支持。目前支持几乎所有主流的服务端编程技术。例如常见的:Java/C/Python/.NET 等,它们通过各自的 API 技术与 MySQL 建立连接。

服务层(MySQL Server)

服务层是 MySQL Server 的核心,主要包含六个部分:

  • 系统管理
  • 控制工具
  • 连接池
  • SQL 接口
  • 解析器
  • 查询优化器
连接池

主要负责存储和管理客户端与数据库的连接,一个线程负责管理一个连接。

系统管理和控制工具

例如:备份恢复、安全管理、集群管理等。

SQL 接口(SQL Interface)

用于接受客户端发送的各种 SQL 命令,并且返回用户需要查询的结果集。比如:DML、DDL、存储过程、视图、解析器等。

解析器(Parser)

负责将请求的 SQL 解析生成一颗“解析树”。然后跟姐姐一些 MySQL 规则进一步检查解析树是否合法。

查询优化器(Optimizer)

当“解析树”通过解析器语法检查后,将交给优化器将其转化成执行计划,然后与存储引擎交互。

我们看个案例:

1
2
3
sql复制代码-- 查询的 SQL
select uid,name from user where gender=1;
-- 选取-》投影-》联接策略

上面是一个查询的 SQL,它是如何执行的?

  • select 先根据 where 语句进行选取,并不是查询出全部数据再过滤
  • select 查询根据 uid 和 name 进行属性的投影,并不是取出所有的字段
  • 将前面选取和投影联接起来,最终生成查询结果

缓存

缓存机制是由一系列小缓存组成的。比如:表缓存、记录缓存、权限缓存、引擎缓存等。如果查询缓存有命中的结果,查询语句就可以直接去查询缓存中的数据。

存储引擎层(Pluggable Storage Engines)

MySQL 的存储引擎是插件式的。主要负责 MySQL 中数据的存储与提取以及与底层系统文件进行交互。服务器中的查询执行引擎会通过接口与存储引擎进行通信(接口自动屏蔽了不同存储引擎之间的差异)。

系统文件层(File System)

该层负责将数据库的数据和日志存储在文件系统上,并完成与存储引擎的交互,该层是文件的物理存储层。主要包含:

  • 日志文件
  • 数据文件
  • 配置文件
  • pid 文件
  • socekt 文件等
日志文件

其中日志文件也分类别:

  • 错误日志(Error Log):默认是开启的,可以通过命令进行配置(show variables like ‘%log_error%’ )
  • 通用查询日志(General Query Log):记录一般的查询语句,可以通过命令进行配置(show variables like ‘%general%’ )
  • 二进制日志(Binary Log):该日志记录了对 MySQL 数据库执行的更改操作,并且记录了语句的发生时间、执行时长;但是它不记录 select、slow 等不修改数据库的 SQL。
    • 查看是否开启:show variables like ‘%log_bin%’
      • 查看参数:show variables like ‘%binlog%’
    • 查看日志文件:show binary logs
  • 慢查询日志(Slow Query Log):记录了所有执行时间超时的 SQL,默认是 10 秒
    • 查看是否开启:show variables like ‘%slow_query%’
      • 查看配置的超时的时间阈值:show variables like ‘long_query_time’

配置文件

存放 MySQL 所有的配置信息文件,比如:my.cf、my.ini 等。

数据文件
  • db.opt 文件:
    • 记录了这个库默认使用的字符集和校验规则。
  • frm 文件:
    • 存储与表相关的元数据(meta)信息。包括:表结构的定义信息等,每一张表度会有一个 frm 文件。
  • MYD 文件:
    • MyISAM 存储引擎专用的文件,存放表中的数据(data),每一张 MyISAM 表对应一个 .MYD 文件。
  • MYi 文件:
    • MyISAM 存储引擎专用的文件,主要存放表的索引相关的信息,每一张 MyISAM 表对应一个 .MYI 文件。
  • ibd 文件和 .ibdata 文件:
    • 存放 InnoDB 的数据文件(包括索引)。
      • InnoDB 有两种表空间方式:独享表空间、共享表空间
    • 独享表空间使用 .ibd 文件存放数据,每一张 InnoDB 表对应一个 .ibd 文件
      • 共享表空间使用 .ibdata 文件,所有表共同使用一个或多个 .ibdata 文件
  • ibdata1 文件:
    • 系统表空间中的数据文件,主要存储元数据、Undo 日志等信息
  • ib_logfile0、ib_logfile1 文件:
    • Redo log 日志文件
Pid 文件

pid 文件是 mysqld 应用程序在 Unix/Linux 环境下的一个进程文件,主要存放自己的进程 id。

Socket 文件

socket 文件也是在 Unix/Linux 环境下才有的,用户在 Unix/Linux 环境下的客户端连接可以不通过 TCP/IP 网络,而直接使用 Unix Socket 来连接 MySQL。

MySQL 的运行机制

建立连接

通过客户端/服务端的通信协议与 MySQL 建立连接。MySQL 客户端和服务端的通信方式是“半双工”。对于每一个 MySQL 的连接,时刻都有一个线程状态来标识这个连接正在做什么。

通讯机制
  • 全双工:能同时发送和接收数据(例如:平时打电话)
  • 半双工:在某一时刻,要么发送数据,要么接收数据,不能同时进行。例如:早起对讲机
  • 单工:只能发送数据或只能接收数据。例如:单行道

上面提到了我们可以根据线程状态来看到数据库连接在做什么,我们可以通过命令 show processlist 、show full processlist( 查看用户正在运行的线程信息,root 用户可以查看所有线程,其它用户只能查看自己命令 ) 进行查看,其相关的参数如下:

  • id:线程 ID
  • user:启动这个线程的用户
  • host:发送请求的客户端的 IP 和端口号
  • db:表示当前命令在哪个库执行
  • command:该线程正在执行的操作命令
    • Create DB 表示正在创建数据库
      • Drop DB 表示正在删除数据库
    • Execute 表示正在执行一个 Prepared Statement
      • Clost Stmt 表示正在关闭一个 Prepared Statement
    • Query 表示正在执行一个语句
      • Sleep 表示正在等待客户端发送语句
    • Quit 表示正在退出
      • Shutdown:表示正在关闭服务器
  • time:表示该线程处于当前状态的时间,单位是秒
  • state:表示的是线程状态
    • Updating:正在搜索匹配记录,进行修改
      • Sleeping:正在等待客户端发送新请求
    • Starting:正在执行请求处理
      • Checking table:正在检查数据表
    • Closing table:正在将表中数据刷新到磁盘中
      • Locked:被其他查询锁住了记录
    • Sending Data:正在处理 Select 查询,同时将结果返回给客户端
  • info:一般记录线程执行的语句,默认显示前 100 个字符。

查询缓存(Cache&Buffer)

如果开启了查询缓存且在查询缓存中查询到完全相同的 SQL 语句时,可以将查询到的结果直接返回给客户端。如果没有开启查询缓存或者没有查询到完全相同的 SQL 语句,则会通过解析器进行语法语义的解析,生成“解析树”:

  • 缓存 Select 查询的结果和 SQL 语句
  • 执行 Select 查询时,先查询缓存,判断是否存在可用的记录集,看匹配条件是否完全相同(包括:参数值),这样才会命中缓存数据。
  • 即使开启查询缓存,以下 SQL 也不能缓存
    • 查询语句中使用了 :SQL_NO_CACHE
      • 查询的结果大于 query_cache_limit 的阈值
    • 查询中有一些不确定参数,比如:now()

查看查询缓存是否启用(包含:是否启用、空间大小、阈值等)

  • show variables like ‘%query_cache%’

查看更详细的缓存参数(包含:可用缓存空间、缓存块、缓存了多少等)

  • show status like ‘QCache%’

解析器(Parser)

将客户端发送过来的 SQL 进行语法解析,生成“解析树”。然后,预处理器会根据 MySQL 规则进一步检查“解析树”是否合法,最后生成新的“解析树”。

例如:

  • 检查数据表和数据列是否存在
  • 数据列名称和别名,看看它们是否有歧义

查询优化器(Optimizer)

根据“解析树”生成最优的执行计划。MySQL 使用很多优化策略生成最优的执行计划,可以分为两类:

  • 静态优化(编译时优化)
  • 动态优化(运行时优化)

其中策略有如下几种:

等价变换策略
  • 5=5 and a>5 改成 a >5
  • a<b and a=5 改成 b>5 and a=5
  • 基于联合索引,调整查询条件的位置等
优化 count、min、max 等函数操作
  • InnoDB 引擎 min 函数 只需要查找索引的最左边(数据结构是树,树的特性左子树<root<右子树)
  • InnoDB 引擎 max 函数 只需要查找索引的最右边
  • MyISAM 引擎 count(),不需要计算,可以直接返回(MyISAM 引擎中有 存放了 count() 结果的位置,可以直接拿到)
提前终止查询
  • 使用了 limit 查询,获取 limit 所需的数据,就不在继续遍历后面的数据
in 的优化
  • MySQL 对 in 查询,会先进行排序,再采用二分查找法查找数据。比如:where id in(2,1,3),会变成 where id in(1,2,3)

查询执行引擎

执行引擎负责执行 SQL 语句。此时查询执行引擎会根据 SQL 语句中表的存储引擎类型,以及对应的 API 接口与底层存储引擎缓存或物理文件的交互,得到查询结果并返回给客户端。

如果开启了查询缓存,这个时候引擎会将 SQL 语句以及对应的结果完整的保存到查询缓存中,后面如果有相同的 SQL 语句执行则会直接返回结果(返回的结果过多时,采用增量模式返回)。

以上简单介绍了 MySQL 的架构体系和运行机制,你学废了没。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python的线程10 使用Event保证多线程同时执行 模

发表于 2021-11-29

「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战」

正式的Python专栏第48篇,同学站住,别错过这个从0开始的文章!

前面分享了threading.Event类,它维持了一个信号(True/False)状态。

像田径跑道上蹲在起点的运动员,不分先后,同时听到枪响就开跑,用这个类来做很适合。

模拟:发出一声枪响

当然不是真的有枪响,而且代码调用Event类的对象实例的set函数。

因为Event类的函数是线程安全的,所以我们可以把运动员看成一个一个的线程,并排在跑道起点。

所以这个思路代码就有了

1
2
3
4
5
python复制代码for _ in range(n):
def run():
wait for event ready
run
threading.Thread(target=run).start()

稍微写好一点,代码最终如下:

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
python复制代码#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/27 10:43 下午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : thread_event.py
# @Project : hello
import threading
import time

xuewei_event = threading.Event()

print("event:", xuewei_event)
print("is_set:", xuewei_event.is_set())


def run():
print(" %s ready" % threading.current_thread().name)
xuewei_event.wait()
print(" %s go" % threading.current_thread().name)
time.sleep(0.5)
print(" %s completed" % threading.current_thread().name)


threads = []
for i in range(4):
t_name = "t-" + str(i)
t = threading.Thread(name=t_name, target=run)
threads.append(t)
t.start()

# 学委提示:赛场鸣枪,运动员开跑
for i in [3, 2, 1]:
print("学委倒数 count %s" % i)
time.sleep(1)
xuewei_event.set()
print("is_set:", xuewei_event.is_set())

for t in threads:
t.join()

这是运行结果:

屏幕快照 2021-11-29 下午10.55.07.png

多线程还有点意思

看看上面的代码,学委还模拟了3/2/1倒数,再开枪。

如图所示,多个线程都听到这个枪响不约而同的喊出了‘go’,最后不同时间达到终点了。

编程还是挺好玩的。 喜欢Python的朋友,请关注学委的 Python基础专栏 or Python入门到精通大专栏

持续学习持续开发,我是雷学委!

编程很有趣,关键是把技术搞透彻讲明白。

欢迎关注微信,点赞支持收藏!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

dubboproperties属性优先级&常见配置

发表于 2021-11-29

「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战」

dubbo.properties属性优先级

覆盖关系

JVM 启动 -D 参数优先,这样可以使用户在部署和启动时进行参数重写,比如在启动时需改变协议的端口。

XML 次之,如果在 XML 中有配置,则 dubbo.properties 中的相应配置项无效。

Properties 最后,相当于缺省值,只有 XML 没有配置时,dubbo.properties 的相应配置项才会生效,通常用于共享公共配置,比如应用名。

application.properties配置20880

dubbo.properties配置20881

虚拟机参数20882

启动发现

常见配置

参考文章

启动时检查

Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check=”true”。

可以通过 check=”false” 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。

另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check=”false”,总是会返回引用,当服务恢复时,能自动连上。

通过 spring 配置文件

关闭某个服务的启动时检查 (没有提供者时报错):

1
ini复制代码<dubbo:reference interface="com.foo.BarService" check="false" />

关闭所有服务的启动时检查 (没有提供者时报错):

1
ini复制代码<dubbo:consumer check="false" />

关闭注册中心启动时检查 (注册订阅失败时报错):

1
ini复制代码<dubbo:registry check="false" />

超时时间&配置覆盖关系

由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间。

1、Dubbo消费端

全局超时配置<dubbo:consumer timeout=”5000” />指定接口以及特定方法超时配置<dubbo:reference interface=”com.foo.BarService” timeout=”2000”><dubbo:method name=”sayHello” timeout=”3000” />

2、Dubbo服务端

全局超时配置<dubbo:provider timeout=”5000” />指定接口以及特定方法超时配置<dubbo:provider interface=”com.foo.BarService” timeout=”2000”><dubbo:method name=”sayHello” timeout=”3000” />

3、配置原则

dubbo推荐在Provider上尽量多配置Consumer端属性:

1、作服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数,等等2、在Provider配置后,Consumer不配置则会使用Provider的配置值,即Provider配置可以作为Consumer的缺省值。否则,Consumer会使用Consumer端的全局设置,这对于Provider不可控的,并且往往是不合理的

配置的覆盖规则:

  1. 方法级配置别优于接口级别,即小Scope优先
  2. Consumer端配置 优于 Provider配置 优于 全局配置,
  3. 最后是Dubbo Hard Code的配置值(见配置文档)
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
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<context:component-scan base-package="com.atguigu.gmall.service.impl"></context:component-scan>


<dubbo:application name="order-service-consumer"></dubbo:application>

<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>

<!-- 配置本地存根-->

<!--声明需要调用的远程服务的接口;生成远程服务代理 -->
<!--
1)、精确优先 (方法级优先,接口级次之,全局配置再次之)
2)、消费者设置优先(如果级别一样,则消费方优先,提供方次之)
-->
<!-- timeout="0" 默认是1000ms-->
<!-- retries="":重试次数,不包含第一次调用,0代表不重试-->
<!-- 幂等(设置重试次数)【查询、删除、修改】、非幂等(不能设置重试次数)【新增】 -->
<dubbo:reference interface="com.atguigu.gmall.service.UserService"
id="userService" timeout="5000" retries="3" version="*">
<!-- <dubbo:method name="getUserAddressList" timeout="1000"></dubbo:method> -->
</dubbo:reference>

<!-- 配置当前消费者的统一规则:所有的服务都不检查 -->
<dubbo:consumer check="false" timeout="5000"></dubbo:consumer>

<dubbo:monitor protocol="registry"></dubbo:monitor>
<!-- <dubbo:monitor address="127.0.0.1:7070"></dubbo:monitor> -->

</beans>

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

mysql数据库基础知识点&事务详解

发表于 2021-11-29

「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战」

问题:mysql执行update语句时,如果不是主键where语句会报错

原因:MySql运行在safe-updates模式下,该模式会导致非主键条件下无法执行update或者delete命令

解决:

查看状态

1
sql复制代码SHOW VARIABLES LIKE 'SQL_SAFE_UPDATES';

image.png

开启

1
ini复制代码SET SQL_SAFE_UPDATES = 0;

image.png

安装binlog记录日志

官方链接:github.com/danfengcao/…

安装binlog2sql

1
2
3
bash复制代码git clone https://github.com/danfengcao/binlog2sql.git
cd binlog2sql
pip install --upgrade pip'

修改mysql的配置文件my.cnf

不知道my.cnf所在路径 可以用以下命令查询

1
bash复制代码sudo find / -name my.cnf

添加如下配置

1
2
3
4
5
6
bash复制代码[mysqld]
server_id = 1
log_bin = /var/log/mysql/mysql-bin.log
max_binlog_size = 1G
binlog_format = row
binlog_row_image = full

数据库事务

事务的特性 ACID

Atomacity 原子性

Consistency 一致性

Isolation 隔离性

Durability 持久性

事务的隔离级别

  • 脏读(Dirty Read)

当一个事务读取另一个事务尚未提交的修改时,产生脏读。

一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交。

  • 非重复度(Nonrepeatable Read)

一个事务对同一行数据重复读取两次,但是却得到了不同的结果。同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。

  • 幻想读(Phantom Reads)

事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。

Isolation Level 脏读(Dirty Read) 不可重复读(Non Repeatable Read) 幻读(Phantom Read)Read
Read Uncommitted Yes Yes Yes
Read Committed - Yes Yes
Repeatable Read - - Yes
Serializable - - -

Read Uncommitted

Read Uncommitted是隔离级别最低的一种事务级别。在这种隔离级别下,一个事务会读到另一个事务更新后但未提交的数据,如果另一个事务回滚,那么当前事务读到的数据就是脏数据,这就是脏读(Dirty Read)。

时刻 事务A 事务B
1 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
2 BEGIN; BEGIN;
3 UPDATE students SET name = ‘Bob’ WHERE id = 1;
4 SELECT * FROM students WHERE id = 1;
5 ROLLBACK;
6 SELECT * FROM students WHERE id = 1;
7 COMMIT;

当事务A执行完第3步时,它更新了id=1的记录,但并未提交,而事务B在第4步读取到的数据就是未提交的数据。

随后,事务A在第5步进行了回滚,事务B再次读取id=1的记录,发现和上一次读取到的数据不一致,这就是脏读。

可见,在Read Uncommitted隔离级别下,一个事务可能读取到另一个事务更新但未提交的数据,这个数据有可能是脏数据。

Read Committed

在Read Committed隔离级别下,一个事务可能会遇到不可重复读(Non Repeatable Read)的问题。

不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。

时刻 事务A 事务B
1 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
2 BEGIN; BEGIN;
3 SELECT * FROM students WHERE id = 1;
4 UPDATE students SET name = ‘Bob’ WHERE id = 1;
5 COMMIT;
6 SELECT * FROM students WHERE id = 1;
7 COMMIT;

当事务B第一次执行第3步的查询时,得到的结果是Alice,随后,由于事务A在第4步更新了这条记录并提交,所以,事务B在第6步再次执行同样的查询时,得到的结果就变成了Bob,因此,在Read Committed隔离级别下,事务不可重复读同一条记录,因为很可能读到的结果不一致。

Repeatable Read

在Repeatable Read隔离级别下,一个事务可能会遇到幻读(Phantom Read)的问题。

幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。

时刻 事务A 事务B
1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2 BEGIN; BEGIN;
3 SELECT * FROM students WHERE id = 99;
4 INSERT INTO students (id, name) VALUES (99, ‘Bob’);
5 COMMIT;
6 SELECT * FROM students WHERE id = 99;
7 UPDATE students SET name = ‘Alice’ WHERE id = 99;
8 SELECT * FROM students WHERE id = 99;
9 COMMIT;

事务B在第3步第一次读取id=99的记录时,读到的记录为空,说明不存在id=99的记录。随后,事务A在第4步插入了一条id=99的记录并提交。事务B在第6步再次读取id=99的记录时,读到的记录仍然为空,但是,事务B在第7步试图更新这条不存在的记录时,竟然成功了,并且,事务B在第8步再次读取id=99的记录时,记录出现了。

可见,幻读就是没有读到的记录,以为不存在,但其实是可以更新成功的,并且,更新成功后,再次读取,就出现了。

Serializable

是最严格的隔离级别。在Serializable隔离级别下,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。

虽然Serializable隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用Serializable隔离级别。

默认隔离级别

如果没有指定隔离级别,数据库就会使用默认的隔离级别。在MySQL中,如果使用InnoDB,默认的隔离级别是Repeatable Read。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…114115116…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%