无常是常

分享技术见解、学习心得和生活感悟

最新文章

常用构建脚本

镜像构建脚本

build-image-web.sh

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
#!/bin/bash

if [ "${BUILD_DIR}" == "" ];then
echo "env 'BUILD_DIR' is not set"
exit 1
fi

DOCKER_DIR=${BUILD_DIR}/${JOB_NAME}

if [ ! -d ${DOCKER_DIR} ];then
mkdir -p ${DOCKER_DIR}
fi

echo "docker workspace: ${DOCKER_DIR}"

JENKINS_DIR=${WORKSPACE}/${MODULE}

echo "jenkins workspace: ${JENKINS_DIR}"

PROJECT_DIR=${JENKINS_DIR}${JOB_NAME}-web

echo "project workspace: ${PROJECT_DIR}"


if [ ! -f ${PROJECT_DIR}/target/*.jar ];then
echo "target jar file not found ${PROJECT_DIR}/target/*.jar"
exit 1
fi

cd ${DOCKER_DIR}
rm -fr *.jar Dockerfile

DOCKER_FILE=/opt/script/Dockerfile

cp ${PROJECT_DIR}/target/*.jar .
cp ${DOCKER_FILE} .

sed -i "s,{{JOB_NAME}},${JOB_NAME},g" Dockerfile

VERSION=$(date +%Y%m%d%H%M%S)

IMAGE_NAME=harbor.merchant.com/kubernetes/${JOB_NAME}:${VERSION}

echo "${IMAGE_NAME}" > ${WORKSPACE}/IMAGE

echo "building image: ${IMAGE_NAME}"
docker build -t ${IMAGE_NAME} .

docker push ${IMAGE_NAME}

helm list -n default
helm upgrade ${JOB_NAME} --set name=${JOB_NAME} --set version=$VERSION --set httpPorts=8080 -n default /opt/charts/

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM openjdk:11

ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV TZ=Asia/Shanghai
ENV apollo.meta http://192.168.3.183:8080
ENV APOLLO_LABEL XianDevLabel
WORKDIR /

EXPOSE 8080

ADD {{JOB_NAME}}.jar /{{JOB_NAME}}.jar

CMD ["java","-Dapollo.label=XianDevLabel","-Djava.security.egd=file:/dev/./urandom","-jar","{{JOB_NAME}}.jar"]

镜像构建脚本

build-image-web.sh

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
#!/bin/bash

if [ "${BUILD_DIR}" == "" ];then
echo "env 'BUILD_DIR' is not set"
exit 1
fi

DOCKER_DIR=${BUILD_DIR}/${JOB_NAME}

if [ ! -d ${DOCKER_DIR} ];then
mkdir -p ${DOCKER_DIR}
fi

echo "docker workspace: ${DOCKER_DIR}"

JENKINS_DIR=${WORKSPACE}/${MODULE}

echo "jenkins workspace: ${JENKINS_DIR}"

PROJECT_DIR=${JENKINS_DIR}${JOB_NAME}-web

echo "project workspace: ${PROJECT_DIR}"


if [ ! -f ${PROJECT_DIR}/target/*.jar ];then
echo "target jar file not found ${PROJECT_DIR}/target/*.jar"
exit 1
fi

cd ${DOCKER_DIR}
rm -fr *.jar Dockerfile

DOCKER_FILE=/opt/script/Dockerfile

cp ${PROJECT_DIR}/target/*.jar .
cp ${DOCKER_FILE} .

sed -i "s,{{JOB_NAME}},${JOB_NAME},g" Dockerfile

VERSION=$(date +%Y%m%d%H%M%S)

IMAGE_NAME=harbor.merchant.com/kubernetes/${JOB_NAME}:${VERSION}

echo "${IMAGE_NAME}" > ${WORKSPACE}/IMAGE

echo "building image: ${IMAGE_NAME}"
docker build -t ${IMAGE_NAME} .

docker push ${IMAGE_NAME}

helm list -n default
helm upgrade ${JOB_NAME} --set name=${JOB_NAME} --set version=$VERSION --set httpPorts=8080 -n default /opt/charts/

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM openjdk:11

ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV TZ=Asia/Shanghai
ENV apollo.meta http://192.168.3.183:8080
ENV APOLLO_LABEL XianDevLabel
WORKDIR /

EXPOSE 8080

ADD {{JOB_NAME}}.jar /{{JOB_NAME}}.jar

CMD ["java","-Dapollo.label=XianDevLabel","-Djava.security.egd=file:/dev/./urandom","-jar","{{JOB_NAME}}.jar"]

mermaid

Mermaid 图表展示

这是一个展示Mermaid图表功能的文章,同时也演示了目录功能。

思维导图 (mindmap)

mindmap
  root((mindmap))
    Origins
      Long history
      ::icon(fa fa-book)
      Popularisation
        British popular psychology author Tony Buzan
    Research
      On effectiveness
and features On Automatic creation Uses Creative techniques Strategic planning Argument mapping Tools Pen and paper Mermaid

状态图 (stateDiagram)

stateDiagram-v2
    [*] --> Still
    Still --> [*]
    Still --> Moving
    Moving --> Still
    Moving --> Crash
    Crash --> [*]

类图 (classDiagram)

classDiagram
    Animal <|-- Duck
    Animal <|-- Fish
    Animal <|-- Zebra
    Animal : +int age
    Animal : +String gender
    Animal: +isMammal()
    Animal: +mate()
    class Duck{
      +String beakColor
      +swim()
      +quack()
    }
    class Fish{
      -int sizeInFeet
      -canEat()
    }
    class Zebra{
      +bool is_wild
      +run()
    }

象限图 (quadrantChart)

quadrantChart
    title Reach and engagement of campaigns
    x-axis Low Reach --> High Reach
    y-axis Low Engagement --> High Engagement
    quadrant-1 We should expand
    quadrant-2 Need to promote
    quadrant-3 Re-evaluate
    quadrant-4 May be improved
    Campaign A: [0.3, 0.6]
    Campaign B: [0.45, 0.23]
    Campaign C: [0.57, 0.69]
    Campaign D: [0.78, 0.34]
    Campaign E: [0.40, 0.34]
    Campaign F: [0.35, 0.78]

流程图示例

简单流程图

flowchart TD
    A[开始] --> B{是否有条件}
    B -->|是| C[执行操作A]
    B -->|否| D[执行操作B]
    C --> E[结束]
    D --> E

复杂流程图

flowchart LR
    A[用户请求] --> B[身份验证]
    B -->|成功| C[处理请求]
    B -->|失败| D[返回错误]
    C --> E[数据库查询]
    E --> F[数据处理]
    F --> G[返回结果]

甘特图示例

gantt
    section Section
    Completed :done,    des1, 2014-01-06,2014-01-08
    Active        :active,  des2, 2014-01-07, 3d
    Parallel 1   :         des3, after des1, 1d
    Parallel 2   :         des4, after des1, 1d
    Parallel 3   :         des5, after des3, 1d
    Parallel 4   :         des6, after des4, 1d

时序图示例

sequenceDiagram
Alice->>John: Hello John, how are you?
loop HealthCheck
    John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!

C4

C4Context
title System Context diagram for Internet Banking System

Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
Person(customerB, "Banking Customer B")
Person_Ext(customerC, "Banking Customer C")
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")

Person(customerD, "Banking Customer D", "A customer of the bank, 
with personal bank accounts.") Enterprise_Boundary(b1, "BankBoundary") { SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.") System_Boundary(b2, "BankBoundary2") { System(SystemA, "Banking System A") System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts.") } System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.") SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.") Boundary(b3, "BankBoundary3", "boundary") { SystemQueue(SystemF, "Banking System F Queue", "A system of the bank, with personal bank accounts.") SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.") } } BiRel(customerA, SystemAA, "Uses") BiRel(SystemAA, SystemE, "Uses") Rel(SystemAA, SystemC, "Sends e-mails", "SMTP") Rel(SystemC, customerA, "Sends e-mails to")

Journey

  journey
    title My working day
    section Go to work
      Make tea: 5: Me
      Go upstairs: 3: Me
      Do work: 1: Me, Cat
    section Go home
      Go downstairs: 5: Me
      Sit down: 3: Me

Bar Chart

gantt
    title Git Issues - days since last update
    dateFormat  X
    axisFormat %s

    section Issue19062
    71   : 0, 71
    section Issue19401
    36   : 0, 36
    section Issue193
    34   : 0, 34
    section Issue7441
    9    : 0, 9
    section Issue1300
    5    : 0, 5

Git Graph

gitGraph
  commit
  commit
  branch develop
  checkout develop
  commit
  commit
  checkout main
  merge develop
  commit
  commit

Pie

pie
"Dogs" : 386
"Cats" : 85.9
"Rats" : 15

总结

通过以上示例,我们可以看到Mermaid支持多种类型的图表:

  • 思维导图:用于整理思路和概念
  • 状态图:用于描述系统状态变化
  • 类图:用于展示类之间的关系
  • 象限图:用于分析和分类
  • 流程图:用于描述业务流程
  • 时序图:用于展示交互过程

这些图表都可以通过简单的文本语法来创建,非常适合技术文档和博客文章。

Mermaid 图表展示

这是一个展示Mermaid图表功能的文章,同时也演示了目录功能。

思维导图 (mindmap)

mindmap
  root((mindmap))
    Origins
      Long history
      ::icon(fa fa-book)
      Popularisation
        British popular psychology author Tony Buzan
    Research
      On effectiveness
and features On Automatic creation Uses Creative techniques Strategic planning Argument mapping Tools Pen and paper Mermaid

状态图 (stateDiagram)

stateDiagram-v2
    [*] --> Still
    Still --> [*]
    Still --> Moving
    Moving --> Still
    Moving --> Crash
    Crash --> [*]

类图 (classDiagram)

classDiagram
    Animal <|-- Duck
    Animal <|-- Fish
    Animal <|-- Zebra
    Animal : +int age
    Animal : +String gender
    Animal: +isMammal()
    Animal: +mate()
    class Duck{
      +String beakColor
      +swim()
      +quack()
    }
    class Fish{
      -int sizeInFeet
      -canEat()
    }
    class Zebra{
      +bool is_wild
      +run()
    }

象限图 (quadrantChart)

quadrantChart
    title Reach and engagement of campaigns
    x-axis Low Reach --> High Reach
    y-axis Low Engagement --> High Engagement
    quadrant-1 We should expand
    quadrant-2 Need to promote
    quadrant-3 Re-evaluate
    quadrant-4 May be improved
    Campaign A: [0.3, 0.6]
    Campaign B: [0.45, 0.23]
    Campaign C: [0.57, 0.69]
    Campaign D: [0.78, 0.34]
    Campaign E: [0.40, 0.34]
    Campaign F: [0.35, 0.78]

流程图示例

简单流程图

flowchart TD
    A[开始] --> B{是否有条件}
    B -->|是| C[执行操作A]
    B -->|否| D[执行操作B]
    C --> E[结束]
    D --> E

复杂流程图

flowchart LR
    A[用户请求] --> B[身份验证]
    B -->|成功| C[处理请求]
    B -->|失败| D[返回错误]
    C --> E[数据库查询]
    E --> F[数据处理]
    F --> G[返回结果]

甘特图示例

gantt
    section Section
    Completed :done,    des1, 2014-01-06,2014-01-08
    Active        :active,  des2, 2014-01-07, 3d
    Parallel 1   :         des3, after des1, 1d
    Parallel 2   :         des4, after des1, 1d
    Parallel 3   :         des5, after des3, 1d
    Parallel 4   :         des6, after des4, 1d

时序图示例

sequenceDiagram
Alice->>John: Hello John, how are you?
loop HealthCheck
    John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!

C4

C4Context
title System Context diagram for Internet Banking System

Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
Person(customerB, "Banking Customer B")
Person_Ext(customerC, "Banking Customer C")
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")

Person(customerD, "Banking Customer D", "A customer of the bank, 
with personal bank accounts.") Enterprise_Boundary(b1, "BankBoundary") { SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.") System_Boundary(b2, "BankBoundary2") { System(SystemA, "Banking System A") System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts.") } System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.") SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.") Boundary(b3, "BankBoundary3", "boundary") { SystemQueue(SystemF, "Banking System F Queue", "A system of the bank, with personal bank accounts.") SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.") } } BiRel(customerA, SystemAA, "Uses") BiRel(SystemAA, SystemE, "Uses") Rel(SystemAA, SystemC, "Sends e-mails", "SMTP") Rel(SystemC, customerA, "Sends e-mails to")

Journey

  journey
    title My working day
    section Go to work
      Make tea: 5: Me
      Go upstairs: 3: Me
      Do work: 1: Me, Cat
    section Go home
      Go downstairs: 5: Me
      Sit down: 3: Me

Bar Chart

gantt
    title Git Issues - days since last update
    dateFormat  X
    axisFormat %s

    section Issue19062
    71   : 0, 71
    section Issue19401
    36   : 0, 36
    section Issue193
    34   : 0, 34
    section Issue7441
    9    : 0, 9
    section Issue1300
    5    : 0, 5

Git Graph

gitGraph
  commit
  commit
  branch develop
  checkout develop
  commit
  commit
  checkout main
  merge develop
  commit
  commit

Pie

pie
"Dogs" : 386
"Cats" : 85.9
"Rats" : 15

总结

通过以上示例,我们可以看到Mermaid支持多种类型的图表:

  • 思维导图:用于整理思路和概念
  • 状态图:用于描述系统状态变化
  • 类图:用于展示类之间的关系
  • 象限图:用于分析和分类
  • 流程图:用于描述业务流程
  • 时序图:用于展示交互过程

这些图表都可以通过简单的文本语法来创建,非常适合技术文档和博客文章。

2025摘抄

2025/02/08

一念放下,天地皆宽。那些强求不放的事,都会成为自己的深渊

蒲松龄写过一个执迷象棋的书生。书生途经扬州时,见一位梁将军在林子里喝酒下棋,便徘徊棋桌旁,不愿离去。
梁将军邀请他下一盘,一连三局,书生都败下阵来。书生不服气,一直下到夜幕降临。
就在这时,忽然一阵阴风扫过,书生竟跪地痛哭,拉着梁将军的衣角,求他救救自己。
原来,书生早已做鬼。他生前执迷于下棋,荒废了学业。家里打也打了,骂也骂了,可书生就是割舍不下。
他把父亲活活气死,因“大不孝”的罪名被阎王索了性命,打入饿鬼狱。
因他颇有才华,阎王又派他给东岳大帝的凤楼写碑文,如果写好了,可放他还阳。
结果在前往凤楼的路上,书生巧遇梁将军的棋局,执念再起,耽误了行程。阎王知道后,任是谁来求情,都不再宽恕他。
这两个故事告诉我们,无论是对物质的贪恋,还是对成就的追求,一旦成为执念,就会变成心灵的枷锁。

2025/02/21

不纠结过河的筏子

不迷恋肉身。既不迷恋自己的肉身,也不迷恋身边人的肉
身。无论如何青春的肉体终会消逝,可以珍惜的时候就要好好珍惜,
能够展现的时候就要尽情展现,不要等到肉身衰老,禽兽无能,眼前
花盛开,鸟却飞不起来,才后悔莫及。 — 冯唐

2025/04/25

人生没有意义,它才自由,没意义不等于摆烂,什么都不做。

一朵花开在山里无人问津,另外一朵花被搬进奢华的房间,他们都还是花,一个活成了别人想要的样子,另一个无拘无束,活成了自己想要的样子 ——— 致人生的意义

空白才有无限可能!回归内心的空白,结果不是自我价值的终身判决。当你不需要意义证明自己时,反而变得更有力量,激发出更纯粹的创造力,不要沉迷于别人定义好的路,
每个人有自己的路,不需要你去拯救或者批判,做好自己就好

2025/07/02

“唐肃宗乾元年间,录事薛某高烧中梦见自己变成一条金色鲤鱼,几日不曾觅食,肚中甚是饥饿。此时,正遇一渔夫垂钓,他明知饵里有钩,却耐不住饵香扑鼻,张口吞饵,结果被渔翁钓了上来。” ————《醒世恒言》第26卷《薛录事鱼服证仙》

欲望是处处都是陷阱。

2025/02/08

一念放下,天地皆宽。那些强求不放的事,都会成为自己的深渊

蒲松龄写过一个执迷象棋的书生。书生途经扬州时,见一位梁将军在林子里喝酒下棋,便徘徊棋桌旁,不愿离去。
梁将军邀请他下一盘,一连三局,书生都败下阵来。书生不服气,一直下到夜幕降临。
就在这时,忽然一阵阴风扫过,书生竟跪地痛哭,拉着梁将军的衣角,求他救救自己。
原来,书生早已做鬼。他生前执迷于下棋,荒废了学业。家里打也打了,骂也骂了,可书生就是割舍不下。
他把父亲活活气死,因“大不孝”的罪名被阎王索了性命,打入饿鬼狱。
因他颇有才华,阎王又派他给东岳大帝的凤楼写碑文,如果写好了,可放他还阳。
结果在前往凤楼的路上,书生巧遇梁将军的棋局,执念再起,耽误了行程。阎王知道后,任是谁来求情,都不再宽恕他。
这两个故事告诉我们,无论是对物质的贪恋,还是对成就的追求,一旦成为执念,就会变成心灵的枷锁。

2025/02/21

不纠结过河的筏子

不迷恋肉身。既不迷恋自己的肉身,也不迷恋身边人的肉
身。无论如何青春的肉体终会消逝,可以珍惜的时候就要好好珍惜,
能够展现的时候就要尽情展现,不要等到肉身衰老,禽兽无能,眼前
花盛开,鸟却飞不起来,才后悔莫及。 — 冯唐

2025/04/25

人生没有意义,它才自由,没意义不等于摆烂,什么都不做。

一朵花开在山里无人问津,另外一朵花被搬进奢华的房间,他们都还是花,一个活成了别人想要的样子,另一个无拘无束,活成了自己想要的样子 ——— 致人生的意义

空白才有无限可能!回归内心的空白,结果不是自我价值的终身判决。当你不需要意义证明自己时,反而变得更有力量,激发出更纯粹的创造力,不要沉迷于别人定义好的路,
每个人有自己的路,不需要你去拯救或者批判,做好自己就好

2025/07/02

“唐肃宗乾元年间,录事薛某高烧中梦见自己变成一条金色鲤鱼,几日不曾觅食,肚中甚是饥饿。此时,正遇一渔夫垂钓,他明知饵里有钩,却耐不住饵香扑鼻,张口吞饵,结果被渔翁钓了上来。” ————《醒世恒言》第26卷《薛录事鱼服证仙》

欲望是处处都是陷阱。

2024摘抄

采访当今最伟大的数学家对AI的看法 陶哲轩:我们正踏入数学的全新领域 – XiaoHu.AI学院

我对重复人类已经擅长的事情并不感兴趣。这看起来效率低下。我认为在前沿领域,人类和AI将始终是必需的。它们有互补的优势。AI擅长将数十亿条数据转化为一个好的答案。人类擅长通过10次观察作出非常有创意的猜测。

2024/10/22

这是我们之间的故事,它代表的是一种特殊的意义(致不完美)

2024/10/25

王阳明曾说:“吾辈用功,只求日减,不求日增。减得一分人欲,便是复得一分天理,何等轻快洒脱,何等简易。

我们应该定期清理卫生,只留下必需品,久而久之你就会发现,原来很多东西只是一个摆设,并非我们所需。

我们应该定期审视人生,这样才会发现,原来那些占据自己精力的东西,实际上毫无价值。

**都说人生从来不是加法,而是减法。**只有不断地抛弃一些不必要的东西,内心才不会被遮蔽,光照进来,才能看清自己内心真正需要的是什么。

懂得知足,才能常乐

欲望越膨胀,人类越得不到满足,这便是痛苦的根源。只有懂得知足,才能真正获得财富。

着相了

**着相是一个‌佛教术语,意思是执着于外相、虚相或个体意识而偏离了本质。**

2024/10/29

事上练

只有日常中多注意在事上的磨练,在各种事情上锻炼自己的应变能力,让自己心境逐渐处在一种十分稳定的状态下,只有这样才能锻炼出遇事不慌不乱的素质。

“熟能生巧,巧能生化”。在社会浮躁风气渐盛的今天,笃行恰恰是我们最应该学习的。

笃行

王阳明有句话:“辨即明矣,思既慎矣,问既审矣,学既能矣,又从而不息其功焉,斯之谓笃行。”意思是,当我们已经分辨清楚,思考缜密,问得详细,已经学会了,还是持续不断地用功,这就叫笃行,这是获得成功的必要前提。

2024/10/30

君子得失淡然,所以有终身之乐;小人患得患失,所以无一日之乐。

2024/11/7

淡定看人生,宁静做自我。

所谓定力,实为定心,心静了,生活自然也就安稳了,人生自然也就安定了。

反之,无法保持内心的宁静,则容易被情绪所控制,稍不注意就可能误入歧途、贻误终生。

忍一时风平浪静,退一步海阔天空。想开了,自然微笑;看透了,肯定放下。这样才能在“风雨人生路上,逍遥任我行。”

2024/11/12

第一性原理(First Principles Thinking)是一种思维方法,指的是把问题分解到最基本的真理上,从最基本的层面去思考问题,而不是基于类比或现有经验。

让我用一个简单的编程例子来说明:假设我们需要计算一个购物车的总价格。

传统思维方式

:直接参考其他电商网站的实现方式,复制他们的解决方案。

第一性原理思维

  • 首先分析问题的本质:
  • 购物车是什么? → 商品的集合
  • 总价格是什么? → 所有商品价格的总和
  • 每个商品的价格如何计算? → 单价 × 数量
  1. 基于这些基本事实来设计解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CartItem:
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity

def get_total(self):
return self.price * self.quantity

class ShoppingCart:
def __init__(self):
self.items = []

def add_item(self, item):
self.items.append(item)

def calculate_total(self):
return sum(item.get_total() for item in self.items)

通过第一性原理思维,我们:

  • 没有直接抄袭现有解决方案
  • 从最基本的概念出发(商品、数量、价格)
  • 构建出了清晰、可维护的解决方案

这种思维方式让我们能够:

  • 更深入地理解问题
  • 创造出更优的解决方案
  • 避免被既有思维定式限制

马斯克就经常使用第一性原理思维。比如在设计特斯拉电池时,他没有直接采用现有的电池方案,而是从最基本的材料成本和物理原理出发,重新思考了电池的设计,最终降低了成本并提高了性能。

2024/11/17

非淡泊无以明志,非宁静无以致远 —诸葛亮《诫子书》

2024/12/10

立志、勤学、改过、责善

为什么立志如此重要?王阳明举例说,一个人如果没有志向,那么就好像船没有舵木、马没有衔环,于是只能随波逐流,人生就如一盘散沙。

非学无以广才,非志无以成学

如果不通过学习,就无法增长才干;如果没有志向,就无法成就学问。

2024/12/26

如何才能做到在工作和生活中诚意满满?
第一,搞清楚你的工作性质:这份工作到底是在做什么?它能给别人带来什么益处?
第二,紧盯着那个“益处”,并且专心致志地放大它。
第三,把工作和生活紧密结合,真心实意地对待你的工作和生活。

2024/12/27

每天前进三十公里 ——南极探险的故事

2024/12/29

人生在世,不是每日往复,应该想想来到这个世间的道理? 寻求自己的答案

《穷爸爸也富爸爸》普通人只能被日复一日推着走

被金钱支配的恐惧和欲望

金钱的本质是流动

不要为钱打工,要让钱为你打工

首先要思考上班的意思是什么?上班是为了赚钱?那赚钱的方式只有上班吗?上班时最优解吗?还是说上班只是当下临时方案?

2024/12/31

人活一世,到底为了什么?是为了功名利禄、权势尊位,还是传宗接代、子孙满堂?

王阳明说,是为了心安。而心安的唯一来源便是人与生俱来的良知。除却良知,一切都是梦幻泡影。

一个杯子,里面有半杯水。有人描述这杯中还有半杯水,而有人会说这里只有半杯水了。前者是乐观主义者,后者是悲观主义者。
有句话叫“悲观者往往正确,乐观者往往成功”。大意是说,悲观者意识到一件事的成功率不高时,就会拒绝行动,从概率角度来看,这是正确的。但乐观者往往会关注事物积极的一方面,即使意识到一件事的成功率不高,只要回报足够,也倾向于迎难而上;不管多难的事,只要有所实践,离成功便更近一步,再加上点儿运气及坚持到底的乐观心态,往往就成功了

此心安处是吾乡,心安才是良知!

采访当今最伟大的数学家对AI的看法 陶哲轩:我们正踏入数学的全新领域 – XiaoHu.AI学院

我对重复人类已经擅长的事情并不感兴趣。这看起来效率低下。我认为在前沿领域,人类和AI将始终是必需的。它们有互补的优势。AI擅长将数十亿条数据转化为一个好的答案。人类擅长通过10次观察作出非常有创意的猜测。

2024/10/22

这是我们之间的故事,它代表的是一种特殊的意义(致不完美)

2024/10/25

王阳明曾说:“吾辈用功,只求日减,不求日增。减得一分人欲,便是复得一分天理,何等轻快洒脱,何等简易。

我们应该定期清理卫生,只留下必需品,久而久之你就会发现,原来很多东西只是一个摆设,并非我们所需。

我们应该定期审视人生,这样才会发现,原来那些占据自己精力的东西,实际上毫无价值。

**都说人生从来不是加法,而是减法。**只有不断地抛弃一些不必要的东西,内心才不会被遮蔽,光照进来,才能看清自己内心真正需要的是什么。

懂得知足,才能常乐

欲望越膨胀,人类越得不到满足,这便是痛苦的根源。只有懂得知足,才能真正获得财富。

着相了

**着相是一个‌佛教术语,意思是执着于外相、虚相或个体意识而偏离了本质。**

2024/10/29

事上练

只有日常中多注意在事上的磨练,在各种事情上锻炼自己的应变能力,让自己心境逐渐处在一种十分稳定的状态下,只有这样才能锻炼出遇事不慌不乱的素质。

“熟能生巧,巧能生化”。在社会浮躁风气渐盛的今天,笃行恰恰是我们最应该学习的。

笃行

王阳明有句话:“辨即明矣,思既慎矣,问既审矣,学既能矣,又从而不息其功焉,斯之谓笃行。”意思是,当我们已经分辨清楚,思考缜密,问得详细,已经学会了,还是持续不断地用功,这就叫笃行,这是获得成功的必要前提。

2024/10/30

君子得失淡然,所以有终身之乐;小人患得患失,所以无一日之乐。

2024/11/7

淡定看人生,宁静做自我。

所谓定力,实为定心,心静了,生活自然也就安稳了,人生自然也就安定了。

反之,无法保持内心的宁静,则容易被情绪所控制,稍不注意就可能误入歧途、贻误终生。

忍一时风平浪静,退一步海阔天空。想开了,自然微笑;看透了,肯定放下。这样才能在“风雨人生路上,逍遥任我行。”

2024/11/12

第一性原理(First Principles Thinking)是一种思维方法,指的是把问题分解到最基本的真理上,从最基本的层面去思考问题,而不是基于类比或现有经验。

让我用一个简单的编程例子来说明:假设我们需要计算一个购物车的总价格。

传统思维方式

:直接参考其他电商网站的实现方式,复制他们的解决方案。

第一性原理思维

  • 首先分析问题的本质:
  • 购物车是什么? → 商品的集合
  • 总价格是什么? → 所有商品价格的总和
  • 每个商品的价格如何计算? → 单价 × 数量
  1. 基于这些基本事实来设计解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CartItem:
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity

def get_total(self):
return self.price * self.quantity

class ShoppingCart:
def __init__(self):
self.items = []

def add_item(self, item):
self.items.append(item)

def calculate_total(self):
return sum(item.get_total() for item in self.items)

通过第一性原理思维,我们:

  • 没有直接抄袭现有解决方案
  • 从最基本的概念出发(商品、数量、价格)
  • 构建出了清晰、可维护的解决方案

这种思维方式让我们能够:

  • 更深入地理解问题
  • 创造出更优的解决方案
  • 避免被既有思维定式限制

马斯克就经常使用第一性原理思维。比如在设计特斯拉电池时,他没有直接采用现有的电池方案,而是从最基本的材料成本和物理原理出发,重新思考了电池的设计,最终降低了成本并提高了性能。

2024/11/17

非淡泊无以明志,非宁静无以致远 —诸葛亮《诫子书》

2024/12/10

立志、勤学、改过、责善

为什么立志如此重要?王阳明举例说,一个人如果没有志向,那么就好像船没有舵木、马没有衔环,于是只能随波逐流,人生就如一盘散沙。

非学无以广才,非志无以成学

如果不通过学习,就无法增长才干;如果没有志向,就无法成就学问。

2024/12/26

如何才能做到在工作和生活中诚意满满?
第一,搞清楚你的工作性质:这份工作到底是在做什么?它能给别人带来什么益处?
第二,紧盯着那个“益处”,并且专心致志地放大它。
第三,把工作和生活紧密结合,真心实意地对待你的工作和生活。

2024/12/27

每天前进三十公里 ——南极探险的故事

2024/12/29

人生在世,不是每日往复,应该想想来到这个世间的道理? 寻求自己的答案

《穷爸爸也富爸爸》普通人只能被日复一日推着走

被金钱支配的恐惧和欲望

金钱的本质是流动

不要为钱打工,要让钱为你打工

首先要思考上班的意思是什么?上班是为了赚钱?那赚钱的方式只有上班吗?上班时最优解吗?还是说上班只是当下临时方案?

2024/12/31

人活一世,到底为了什么?是为了功名利禄、权势尊位,还是传宗接代、子孙满堂?

王阳明说,是为了心安。而心安的唯一来源便是人与生俱来的良知。除却良知,一切都是梦幻泡影。

一个杯子,里面有半杯水。有人描述这杯中还有半杯水,而有人会说这里只有半杯水了。前者是乐观主义者,后者是悲观主义者。
有句话叫“悲观者往往正确,乐观者往往成功”。大意是说,悲观者意识到一件事的成功率不高时,就会拒绝行动,从概率角度来看,这是正确的。但乐观者往往会关注事物积极的一方面,即使意识到一件事的成功率不高,只要回报足够,也倾向于迎难而上;不管多难的事,只要有所实践,离成功便更近一步,再加上点儿运气及坚持到底的乐观心态,往往就成功了

此心安处是吾乡,心安才是良知!

事务到底是隔离的还是不隔离的?

begin/start transaction命令并不是一个事务的起点,在执行到它们之后的第一个操作InnoDB表的语句,事务才真正启动。使用start transation with consistent snapshot 这个命令可以马上启动一个事务。

在MySQL中有两个视图的概念:

  • 一个是view。它是一个用于查询语句定义的虚拟表,它的语法是:create view ...

  • 另一个是InnoDB 在实现MVCC时用到的一致性读视图,即 consistent read view,用于支持RC(Read Commit 读提交)和RR(Repeatable 可重复读)隔离级别实现的。

“快照”在 MVCC 里是怎么工作的?

在可重复读隔离级别下,事务在启动的时候就“拍个快照”,这个快照时基于整库的。

如果库有100G,那么启动一个事务就需要拷贝100G数据,这样实现是不现实的。

快照是怎么实现的?

InnoDB 里面每个事务都有一个唯一的事务ID,叫做transaction id,它是在事务开始的时候向InnoDB 的事务系统申请的,是按照顺序严格递增的。

而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。

也就是说,数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row trx_id。

如图下所示,就是一个记录被多个事务连续更新后的状态。

图中的三个虚线箭头,就是undo log;而 V1、V2、V3 并不是物理上真实存在的,而是每次需要的时候根据当前版本和 undo log 计算出来的。比如,需要 V2 的时候,就是通过 V4 依次执行 U3、U2 算出来。

事务启动的时候,以启动的时刻为准,如果一个数据版本低于在事务启动之前生成的,那就可见,如果在启动之后才生成的,那就不可见。

在实现上,InnoDB为每个事务构造了一个数据,用来保存这个事务的启动瞬间,当前正在“活跃”的所有事务ID,“活跃”指的是,启动了但还没提交。

数组里面事务 ID 的最小值记为低水位,当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位。

这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。

这样,对于当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能:

  1. 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;

  2. 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;

  3. 如果落在黄色部分,那就包括两种情况
    a. 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见;
    b. 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见。

** InnoDB 利用了“所有数据都有多个版本”的这个特性,实现了“秒级创建快照”的能力。**

更新逻辑

更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。

当前读,读取的是最新版本,并且需要先获取对应记录的锁,如以下这些 SQL 类型:

1
2
3
4
5
select ... lock in share mode

select ... for update

update 、delete 、insert

例如,要 update 一条记录,在事务执行过程中,如果不加锁,那么另一个事务可以 delete 这条数据并且能成功 commit ,就会产生冲突了。所以 update 的时候肯定要是当前读,得到最新的信息并且锁定相应的记录。

事务的可重复读的能力是怎么实现的?

可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。

而读提交的逻辑和可重复读的逻辑类似,它们最主要的区别是:

  • 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;

  • 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。

begin/start transaction命令并不是一个事务的起点,在执行到它们之后的第一个操作InnoDB表的语句,事务才真正启动。使用start transation with consistent snapshot 这个命令可以马上启动一个事务。

在MySQL中有两个视图的概念:

  • 一个是view。它是一个用于查询语句定义的虚拟表,它的语法是:create view ...

  • 另一个是InnoDB 在实现MVCC时用到的一致性读视图,即 consistent read view,用于支持RC(Read Commit 读提交)和RR(Repeatable 可重复读)隔离级别实现的。

“快照”在 MVCC 里是怎么工作的?

在可重复读隔离级别下,事务在启动的时候就“拍个快照”,这个快照时基于整库的。

如果库有100G,那么启动一个事务就需要拷贝100G数据,这样实现是不现实的。

快照是怎么实现的?

InnoDB 里面每个事务都有一个唯一的事务ID,叫做transaction id,它是在事务开始的时候向InnoDB 的事务系统申请的,是按照顺序严格递增的。

而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。

也就是说,数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row trx_id。

如图下所示,就是一个记录被多个事务连续更新后的状态。

图中的三个虚线箭头,就是undo log;而 V1、V2、V3 并不是物理上真实存在的,而是每次需要的时候根据当前版本和 undo log 计算出来的。比如,需要 V2 的时候,就是通过 V4 依次执行 U3、U2 算出来。

事务启动的时候,以启动的时刻为准,如果一个数据版本低于在事务启动之前生成的,那就可见,如果在启动之后才生成的,那就不可见。

在实现上,InnoDB为每个事务构造了一个数据,用来保存这个事务的启动瞬间,当前正在“活跃”的所有事务ID,“活跃”指的是,启动了但还没提交。

数组里面事务 ID 的最小值记为低水位,当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位。

这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。

这样,对于当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能:

  1. 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;

  2. 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;

  3. 如果落在黄色部分,那就包括两种情况
    a. 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见;
    b. 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见。

** InnoDB 利用了“所有数据都有多个版本”的这个特性,实现了“秒级创建快照”的能力。**

更新逻辑

更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。

当前读,读取的是最新版本,并且需要先获取对应记录的锁,如以下这些 SQL 类型:

1
2
3
4
5
select ... lock in share mode

select ... for update

update 、delete 、insert

例如,要 update 一条记录,在事务执行过程中,如果不加锁,那么另一个事务可以 delete 这条数据并且能成功 commit ,就会产生冲突了。所以 update 的时候肯定要是当前读,得到最新的信息并且锁定相应的记录。

事务的可重复读的能力是怎么实现的?

可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。

而读提交的逻辑和可重复读的逻辑类似,它们最主要的区别是:

  • 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;

  • 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。

行锁功过:怎么减少行锁对性能的影响?

从两阶段锁说起

实际上事务B的update语句会被阻塞,直至事务A执行commit之后,事务B才能继续执行。

在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是等到事务结束才释放,这就是两阶段锁协议。

如果你的事务中需要锁多个行,要把最可能造成冲突、最可能影响并发的锁尽量往后放。

死锁和死锁检测

当并发系统出现资源循环依赖,就会导致这几个线程处于无限等待状态,称为死锁

两种解决死锁的策略:

  • 一种策略是直接进入等待,直至超时。这个超时时间可以通过参数innodb_lock_wait_timeout来设置。

  • 另外一种策略是发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数innodb_deadlock_detect设置为on,表示开启这个逻辑。

使用show variables like 'innodb_deadlock_detect';可以查看系统参数。

问题

如果你要删除一个表里面的前 10000 行数据,有以下三种方法可以做到:

  • 第一种,直接执行 delete from T limit 10000;

  • 第二种,在一个连接中循环执行 20 次 delete from T limit 500;

  • 第三种,在 20 个连接中同时执行 delete from T limit 500。

一般会选择第二种。

第一种方式单个语句占用时间长,锁的时间也比较长;而且大事务还会导致主从延迟。

第三种方式会人为造成锁冲突。

从两阶段锁说起

实际上事务B的update语句会被阻塞,直至事务A执行commit之后,事务B才能继续执行。

在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是等到事务结束才释放,这就是两阶段锁协议。

如果你的事务中需要锁多个行,要把最可能造成冲突、最可能影响并发的锁尽量往后放。

死锁和死锁检测

当并发系统出现资源循环依赖,就会导致这几个线程处于无限等待状态,称为死锁

两种解决死锁的策略:

  • 一种策略是直接进入等待,直至超时。这个超时时间可以通过参数innodb_lock_wait_timeout来设置。

  • 另外一种策略是发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数innodb_deadlock_detect设置为on,表示开启这个逻辑。

使用show variables like 'innodb_deadlock_detect';可以查看系统参数。

问题

如果你要删除一个表里面的前 10000 行数据,有以下三种方法可以做到:

  • 第一种,直接执行 delete from T limit 10000;

  • 第二种,在一个连接中循环执行 20 次 delete from T limit 500;

  • 第三种,在 20 个连接中同时执行 delete from T limit 500。

一般会选择第二种。

第一种方式单个语句占用时间长,锁的时间也比较长;而且大事务还会导致主从延迟。

第三种方式会人为造成锁冲突。

深入浅出索引(下)

覆盖索引

如果执行语句如:select ID from T where k between 3 and 5,这是只需要查ID的值,而ID的值已经在k索引树上了,因此可以直接提供查询结果,不需要回表,这种情况我们称为覆盖索引

由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

最左前缀原则


可以看到,索引是按照索引定义里面出现的字段排序的。

只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。

在建立联合索引的时候,如何安排索引内的字段顺序?

第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。

索引下推

MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。

例如以下查询语句:

1
mysql> select * from tuser where name like '张 %' and age=10 and ismale=1;
无索引下推

有索引下推

可以明显的看出,有索引下推的情况下减少了回表的次数。

覆盖索引

如果执行语句如:select ID from T where k between 3 and 5,这是只需要查ID的值,而ID的值已经在k索引树上了,因此可以直接提供查询结果,不需要回表,这种情况我们称为覆盖索引

由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

最左前缀原则


可以看到,索引是按照索引定义里面出现的字段排序的。

只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。

在建立联合索引的时候,如何安排索引内的字段顺序?

第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。

索引下推

MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。

例如以下查询语句:

1
mysql> select * from tuser where name like '张 %' and age=10 and ismale=1;
无索引下推

有索引下推

可以明显的看出,有索引下推的情况下减少了回表的次数。

深入浅出索引(上)

索引的出现其实就是为了提高数据查询的效率,就像书的目录一样。

索引常见模型

哈希表

哈希表是一种以键值对存储的数据结构,只要输入key,就可以根据key找到对应的vaule。不可避免的是会存在hash冲突,处理这种情况方法是拉出一个链表。(类比Java中HashMap结构)。

优点:

  • 查找速度快,新增速度也快。

缺点:

  • 因为不是有序的,如果需要范围查询,速度是很慢的。

哈希表这种结构适用于只有等值查询的场景,比如 Memcached 及其他一些 NoSQL 引擎。

有序数组

有序数组查询时,可以使用二分法进行搜索,时间复杂度是O(log(N)),并且有序数据还支持范围查询。但是需要插入的时候,就需要进行数据挪动(因为要保证顺序),成本是非常高的。

因此,有序数组索引只适用于静态存储引擎

搜索树

二叉树的特点是:每个节点的左儿子小于父节点,父节点又小于右儿子。这样如果你要查 ID_card_n2 的话,按照图中的搜索顺序就是按照 UserA -> UserC -> UserF -> User2 这个路径得到。这个时间复杂度是 O(log(N))。

当然为了维持 O(log(N)) 的查询复杂度,你就需要保持这棵树是平衡二叉树。为了做这个保证,更新的时间复杂度也是 O(log(N))。

二叉树的主要缺点是:当数据量很大的时候,树高会非常高,假如树高为20。一次查询可能需要访问 20 个数据块。在机械硬盘时代,从磁盘随机读一个数据块需要 10 ms 左右的寻址时间。也就是说,对于一个 100 万行的表,如果使用二叉树来存储,单独访问一个行可能需要 20 个 10 ms 的时间,这个查询可真够慢的。

InnoDB 的索引模型

在InnoDB中,表都是根据主键顺序以索引的形式存放的,这种存储方式的表称为索引组织表。InnoDB使用的是B+树索引模型,所以数据都是存储在B+树中的。

每一个索引在InnoDB中对应一颗B+树。

从上图来看,索引类型分为:主键索引和非主键索引。

主键索引在InnoDB中也成为聚簇索引(clustered index),非主键索引在InnoDB中成为二级索引(secondary index)。

基于主键索引和非主键索引搜索的区别:

  • 基于主键索引查询只需要搜索对应的这颗B+树。

  • 基于非主键索引首先先查到对应值的ID(假设ID为主键),再到ID索引树中搜索一次,这个过程称为回表。

索引的维护

页分裂

B+ 树为了维护索引的有序性,在插入新值的时候需要做必要的维护,如果插入的数据需要在页的中间,那么就需要进行数据的挪动,空出位置,如果插入的页刚好满了,就会触发页分裂,页分裂除了会影响性能,而且会使整体空间利用率降低50%(因为之前一个页,分裂成了两个)。

当然有分裂就有合并。当相邻两个页由于删除了数据,利用率很低之后,会将数据页做合并。合并的过程,可以认为是分裂过程的逆过程。

基于业务字段做主键,往往不能保证插入的有序性,更容易造成页分裂,基于自增ID做主键,每插入一条记录都是追加操作,一般不会触发叶子结点分裂。由于每个非主键索引叶子结点都是主键的值,如果用整型做主键,只需要4个字节,如果是长整型,则是8个字节。

显然,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。

索引的出现其实就是为了提高数据查询的效率,就像书的目录一样。

索引常见模型

哈希表

哈希表是一种以键值对存储的数据结构,只要输入key,就可以根据key找到对应的vaule。不可避免的是会存在hash冲突,处理这种情况方法是拉出一个链表。(类比Java中HashMap结构)。

优点:

  • 查找速度快,新增速度也快。

缺点:

  • 因为不是有序的,如果需要范围查询,速度是很慢的。

哈希表这种结构适用于只有等值查询的场景,比如 Memcached 及其他一些 NoSQL 引擎。

有序数组

有序数组查询时,可以使用二分法进行搜索,时间复杂度是O(log(N)),并且有序数据还支持范围查询。但是需要插入的时候,就需要进行数据挪动(因为要保证顺序),成本是非常高的。

因此,有序数组索引只适用于静态存储引擎

搜索树

二叉树的特点是:每个节点的左儿子小于父节点,父节点又小于右儿子。这样如果你要查 ID_card_n2 的话,按照图中的搜索顺序就是按照 UserA -> UserC -> UserF -> User2 这个路径得到。这个时间复杂度是 O(log(N))。

当然为了维持 O(log(N)) 的查询复杂度,你就需要保持这棵树是平衡二叉树。为了做这个保证,更新的时间复杂度也是 O(log(N))。

二叉树的主要缺点是:当数据量很大的时候,树高会非常高,假如树高为20。一次查询可能需要访问 20 个数据块。在机械硬盘时代,从磁盘随机读一个数据块需要 10 ms 左右的寻址时间。也就是说,对于一个 100 万行的表,如果使用二叉树来存储,单独访问一个行可能需要 20 个 10 ms 的时间,这个查询可真够慢的。

InnoDB 的索引模型

在InnoDB中,表都是根据主键顺序以索引的形式存放的,这种存储方式的表称为索引组织表。InnoDB使用的是B+树索引模型,所以数据都是存储在B+树中的。

每一个索引在InnoDB中对应一颗B+树。

从上图来看,索引类型分为:主键索引和非主键索引。

主键索引在InnoDB中也成为聚簇索引(clustered index),非主键索引在InnoDB中成为二级索引(secondary index)。

基于主键索引和非主键索引搜索的区别:

  • 基于主键索引查询只需要搜索对应的这颗B+树。

  • 基于非主键索引首先先查到对应值的ID(假设ID为主键),再到ID索引树中搜索一次,这个过程称为回表。

索引的维护

页分裂

B+ 树为了维护索引的有序性,在插入新值的时候需要做必要的维护,如果插入的数据需要在页的中间,那么就需要进行数据的挪动,空出位置,如果插入的页刚好满了,就会触发页分裂,页分裂除了会影响性能,而且会使整体空间利用率降低50%(因为之前一个页,分裂成了两个)。

当然有分裂就有合并。当相邻两个页由于删除了数据,利用率很低之后,会将数据页做合并。合并的过程,可以认为是分裂过程的逆过程。

基于业务字段做主键,往往不能保证插入的有序性,更容易造成页分裂,基于自增ID做主键,每插入一条记录都是追加操作,一般不会触发叶子结点分裂。由于每个非主键索引叶子结点都是主键的值,如果用整型做主键,只需要4个字节,如果是长整型,则是8个字节。

显然,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。

事务隔离:为什么你改了我还看不见?

ACID (Atomicity、Consistency、Isolation、Durability)即原子性、一致性、隔离性、持久性。

当数据库上有多个事务同时执行的时候,就可能会出现脏读(dirty read)、不可重复读(nonrepeatable read)、幻读(phantom read)的问题。

隔离级别

SQL标准的隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复度(repeatable read)和串行化(serializable)。

  • 读未提交:一个事务还没有提交时,它做的变更就能被别的事务看到。

  • 读提交:一个事务提交之后,它做的变更才会被其他事务看到。

  • 可重复读:一个事务执行过程中看到的数据,总是跟这个事务启动的时候看到的数据一致。当然在可重复读的隔离级别下,未提交的变更对其他事务也是不可见的。

  • 串行化:对同一条记录“写”会加“写锁”,“读”会加“读锁”。当出现读写冲突的时候,后访问的事务必须等待前一个事务执行完成,才能继续执行。

事务隔离的实现

数据库隔离的实现上会创建一个视图,访问的时候以视图的逻辑结果为准。

“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都会用这个视图。

“读提交”隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的

**“读未提交”**隔离级别下直接返回记录上的最新值,没有视图的概念

“串行化”的隔离级别下直接使用加锁的方式避免并行访问

在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。

当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。

同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。

回滚日志系统会判断,当系统中没有比这个回滚日志更早的read-view的时候,日志会被删除。

建议不要使用长事务

长事务意味着系统里面会存在很老的事务视图,由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。

长事务还会占用锁资源,也可能拖垮整个库。

通过以下语句可以查询持续时间超过60s的事务。

1
2
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60

事务的启动方式

  1. 显式启动,begin或start transaction。配套的提交语句是commit,回滚语句是commit

  2. set autocommit=0,这个命令会将这个线程自动提交关闭。意味着如果你只执行select语句,这个事务就启动了,而且不会自动提交。这个事务持续存在直到你主动执行commit或rollback语句,或者断开连接。

建议使用set autocommit=1,通过显式语句的方式启动事务。

在 autocommit 为 1 的情况下,用 begin 显式启动的事务,如果执行 commit 则提交事务。如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行 begin 语句的开销。同时带来的好处是从程序开发的角度明确地知道每个语句是否处于事务中。

ACID (Atomicity、Consistency、Isolation、Durability)即原子性、一致性、隔离性、持久性。

当数据库上有多个事务同时执行的时候,就可能会出现脏读(dirty read)、不可重复读(nonrepeatable read)、幻读(phantom read)的问题。

隔离级别

SQL标准的隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复度(repeatable read)和串行化(serializable)。

  • 读未提交:一个事务还没有提交时,它做的变更就能被别的事务看到。

  • 读提交:一个事务提交之后,它做的变更才会被其他事务看到。

  • 可重复读:一个事务执行过程中看到的数据,总是跟这个事务启动的时候看到的数据一致。当然在可重复读的隔离级别下,未提交的变更对其他事务也是不可见的。

  • 串行化:对同一条记录“写”会加“写锁”,“读”会加“读锁”。当出现读写冲突的时候,后访问的事务必须等待前一个事务执行完成,才能继续执行。

事务隔离的实现

数据库隔离的实现上会创建一个视图,访问的时候以视图的逻辑结果为准。

“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都会用这个视图。

“读提交”隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的

**“读未提交”**隔离级别下直接返回记录上的最新值,没有视图的概念

“串行化”的隔离级别下直接使用加锁的方式避免并行访问

在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。

当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。

同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。

回滚日志系统会判断,当系统中没有比这个回滚日志更早的read-view的时候,日志会被删除。

建议不要使用长事务

长事务意味着系统里面会存在很老的事务视图,由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。

长事务还会占用锁资源,也可能拖垮整个库。

通过以下语句可以查询持续时间超过60s的事务。

1
2
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60

事务的启动方式

  1. 显式启动,begin或start transaction。配套的提交语句是commit,回滚语句是commit

  2. set autocommit=0,这个命令会将这个线程自动提交关闭。意味着如果你只执行select语句,这个事务就启动了,而且不会自动提交。这个事务持续存在直到你主动执行commit或rollback语句,或者断开连接。

建议使用set autocommit=1,通过显式语句的方式启动事务。

在 autocommit 为 1 的情况下,用 begin 显式启动的事务,如果执行 commit 则提交事务。如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行 begin 语句的开销。同时带来的好处是从程序开发的角度明确地知道每个语句是否处于事务中。

日志系统:一条SQL更新语句是如何执行的?

redo log(重做日志)

InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。

write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。

有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。

binlog(归档日志)

binlog没有crash-safe能力,只能用于归档。

这两种日志的不同点:

  • redo log是InnoDB引擎特有的,binlog是MySQL的server层实现的,所有引擎都可以使用。

  • redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑。

  • redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

两阶段提交

1
mysql> update T set c=c+1 where ID=2;

执行器和 InnoDB 引擎在执行这个简单的 update 语句时的内部流程:

  1. 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。

  2. 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。

  3. 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。

  4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。

  5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。

流程图如下所示:

如何让数据库恢复到半个月任意一秒?

  • 首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库;
  • 然后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表之前的那个时刻。

总结

建议innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘,这样可以保证 MySQL 异常重启之后数据不丢失。

建议sync_binlog 这个参数设置成 1 ,表示每次事务的 binlog 都持久化到磁盘,这样可以保证 MySQL 异常重启之后 binlog 不丢失。

redo log(重做日志)

InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。

write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。

有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。

binlog(归档日志)

binlog没有crash-safe能力,只能用于归档。

这两种日志的不同点:

  • redo log是InnoDB引擎特有的,binlog是MySQL的server层实现的,所有引擎都可以使用。

  • redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑。

  • redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

两阶段提交

1
mysql> update T set c=c+1 where ID=2;

执行器和 InnoDB 引擎在执行这个简单的 update 语句时的内部流程:

  1. 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。

  2. 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。

  3. 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。

  4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。

  5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。

流程图如下所示:

如何让数据库恢复到半个月任意一秒?

  • 首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库;
  • 然后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表之前的那个时刻。

总结

建议innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘,这样可以保证 MySQL 异常重启之后数据不丢失。

建议sync_binlog 这个参数设置成 1 ,表示每次事务的 binlog 都持久化到磁盘,这样可以保证 MySQL 异常重启之后 binlog 不丢失。