MySQL 转 PostgreSQL (holo)

语法区别

3. 关于日期、时间的提取

timestamp 类型的数据,把小时、分钟等提取出来。

--mysql
    SELECT hour(time) AS timeHour
    FROM   cart
    WHERE  ......;

--PostgreSQL (holo) 方法一
    SELECT DATE_PART('hour', time) as timeHour
    FROM   cart
    WHERE  ......;

--PostgreSQL (holo) 方法二
    SELECT EXTRACT(hour FROM time) AS timeHour
    FROM   cart
    WHERE  ......;

其中DATE_PART(field, source)extract() 两者等效,第一个参数可选以下内容:
* century 世纪
* decade 年份 ÷ 10
* year
* month 对于timestamp值,它是一年里的月份数(1-12);对于interval值, 它是月的数目,然后对 12 取模(0-11)
* day
* hour 小时
* minute 分钟
* second
* microseconds 秒域(包括小数部分)乘以 1,000,000 。请注意它包括全部的秒。
* milliseconds 秒域(包括小数部分)乘以 1,000 。请注意它包括完整的秒。
* dow 每周的星期几 , 星期日(0),星期六(1)
* doy 一年的第 x 天
* epoch
* isodow
* isoyear
* timezone
* timezone_hour
* timezone_minute

(参考资料 / 详细资料 连接)

GROUP BY

将MySQL中包含GROUP BY的代码,直接迁移到PGSQL中,有概率会遇到这个问题:
[42803] ERROR: column "table.colName" must appear in the GROUP BY clause or be used in an aggregate function

错误:”cart.id”列 必须出现在GROUP BY子句中或在聚合函数中使用

PHP的摸索学习

写在前面

  的确,这个博客之前一直都只在写golang的一些框架或者心得。但是从最近一段时间可能是需要换一种分隔了,毕竟新公司的后台开发主业务是用的PHP。
  不过好消息是,公司配给我的开发机上有go的相关环境,这意味着我可以不用太放弃这门语言,在写一些工具的时候还可以继续用到,之前学到的东西也没有白白浪费。


今日活动

  • 早上到公司办理入职,简单地了解了一下公司各部门的工作。公司的环境整体不错,安排了一台新的笔记本电脑 (吐槽:8GB内存,只开一个IDE就系统就已经占用了70%,用起来太难受了。)
  • 电脑是Windows的,因为开发环境是Linux,公司会给每个人安排一个Ubuntu的开发机,该系统会自带公司业务所有的开发环境,且不允许修改环境的配置(所以没有sudo权限)。
  • 除此之外,公司有自己的企业邮箱,使用自有的git仓库,有老员工留下来的wiki,尤其是wiki,里面的内容丰富,看完以后很醒目。
  • 上午差不多就做了这么多,下午大部分时间都在看PHP的基础,我已经对这个语言有了大致的了解,实际上它并没有我原来听说的那么不堪、那么难用。
  • 下午还抽空把代码拉取到自己的开发机上。

TODO

  • git的自动代码拉取的shell还没有设置。

并发、并行、进程、线程、协程

并发

  在一个CPU上,创建多个任务,在很短的时间内,让CPU来回切换任务执行它们。让用户看起来这些任务像是在同时执行,但是对于计算机来说,这些任务实际上还是一个接一个地顺序执行的。
  它强调一个CPU可以在同一时刻,接受很多个任务,但在执行的过程中还是一个一个任务地执行

并行

  在多个CPU上,创建多个任务,让每一个CPU都分到需要自己单独计算的任务。每个任务不被CPUs争抢,同时进行。用户看起来这些任务在同时进行,对于计算机而言,这些任务也是同时进行的。
  它强调好多个CPU可以在同一时刻,分别处理自己手上的任务,但在执行的过程中这些任务是同时计算的

进程

  cpu在切换程序的时候,需要保存上一个程序的状态(就是我们常说的context上下文),不能直接从程序A切换到程序B。进程就是用来划分不同的程序运行时所需要的资源
  进程是一个程序运行时候的所需要的基本资源单位。

线程

  cpu切换多个进程的时候,每个进程有不同的运行时所需要的资源,来回保存的过程中会花费不少的时间,进程一旦多起来,cpu调度会消耗一大堆资源。
  线程本身几乎不占资源,同一个进程中的每个线程之间共享资源,对内核来说,无需来回保存不同进程的上下文,节省了切换上下文时的CPU资源。
  对于同一个程序来说,如果需要并发计算,创建很多进程和创建了很多线程,后者可以节省切换上下文所需要使用的CPU资源。

协程

  协程是轻量级的线程,它们实现的功能差不多。两者最大的差别是进行切换操作的对象不同,线程、进程由操作系统负责切换;协程由用户程序自己进行切换(即Go routine负责切换)。在协程之间的切换不需要涉及任何系统调用或任何阻塞调用。

五行解决反转链表问题 以及其引伸

题目如下

  1. 反转链表

反转一个单链表。
示例:
  输入: 1->2->3->4->5->NULL
  输出: 5->4->3->2->1->NULL
进阶:
  你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

力扣206题,传送门

解法:

func reverseList(head *ListNode) *ListNode {
    cur := head        //当前节点
    var prev *ListNode //前一个节点(”第一个“前一个节点为nil)
    for cur != nil {
        cur.Next, prev, cur = prev, cur, cur.Next
    }
    return prev
}

没错,就是这么简单XD。

解法详解:

2、3行,定义变量就不用说了。
4行,当当前节点不为空的时候,进行循环。

重点 第5行:该行完成了翻转、指针移动的这两个关键步骤。

使用了连等式(多值赋值)。在这一行,看样子像是给三个变量进行更新赋值,实则它还隐藏着三个临时变量。
这里就需要先了解一下 多值赋值 的运算过程了:
  多值赋值会在当前变量所拥有的值的基础上,先计算等号右侧表达式的各个值,并在必要的情况下存储在临时变量中。再将右侧表达式的各个值,依次赋给等号左侧的变量(顺序为从左到右)。

详细分析多变量的赋值,请看这篇文章→《深入探讨Go多变量赋值》

  1. 先计算(备份)变量的值 到 临时变量中
    即:先备份当前的 prev, cur,记作 Temp_prev, Temp_cur
    计算当前的 cur.Next,存入 Temp_cur_Next中。
  2. 再对节点进行翻倒(赋值)
    cur.Next = Temp_prev  //将 当前节点的后继指针 指向 前一个节点。(完成翻转)
    prev = Temp_cur    //将 前一个节点 定义为 当前节点。(完成前一个节点指针的移动)
    cur = Temp_cur_Next  //将 当前节点 定义为 旧的“当前节点”的下一个节点。(完成当前节点指针的移动)

次重点 第7行:返回的结果为什么是prev(前一个节点),而不是cur(当前节点)?

在 最后一个节点 进行计算前,链表是这样的,1 <- 2 <- 3 <- 4 5 -> nil

  1. 计算后,赋值前的情况(计算了等号右侧的变量值,并存储在了临时变量里,还没有给等号左侧的变量修改值的时候):
    此时:prev = 4; cur = 5; Temp_prev = 4; Temp_cur = 5; Temp_cur_Next = nil;

  2. 计算后,开始对等号左侧的变量进行赋值(开始修改等号左侧变量的时候):
    备注:括号中的值代表的指针当前指向的元素
    cur.Next(5.Next) = prev(4)  //完成了最后两个元素的翻转
    prev = Temp_cur(5)     //即:翻转后得到的新链表的 head
    cur = Temp_cur_Next(nil)  //达到 for解除循环的临界条件:cur = nil

显然,cur = nilcur不能作为结果返回给被调用的函数


作者能力有限,若有哪些地方有误,请在评论区里指正。愿我们一同进步!

深入探讨 Go 多变量赋值

使用go语言时,会经常把一些变量放在同一行来声明、赋值或计算。那么,这种赋值方法到底是以什么样的实现方法来赋值的呢?

先说结论,就两步:
一、先计算等号右侧所有表达式的值,将结果存储临时变量中。
二、将临时变量的值赋给等式左侧的变量。

多个变量一同声明并赋值

a, b := 1, 5        //情况一
c, d := a+b, a-b    //情况二

对于情况一来说,很简单,直接按照从左到右的顺序,把1、2两个值赋值给a、b

  ; -----情况一  相关汇编代码  开始----------
  (.\main.go:4)     MOVQ    1, "".a+24(SP)             ; 将1赋值给a
  (.\main.go:4)     MOVQ5, "".b+16(SP)           ; 将5赋值给b
  ; -----情况一  相关汇编代码  结束----------

对于情况二来说,赋值的过程是这样的:
1. 计算a+b、a-b的值,并存储在两个临时变量中
2. 将运算结果从临时变量中调出来,从左到右依次赋给c, d两个变量。

  ; -----情况二  相关汇编代码  开始----------
  (.\main.go:5)     MOVQ    "".a+24(SP), AX           ; 将a赋值给AX寄存器
  (.\main.go:5)     ADDQ    $5, AX                    ; 将5和AX寄存器里的值相加(结果还保存在AX寄存器里)
  (.\main.go:5)     MOVQ    AX, ""..autotmp_4+56(SP)  ; 将AX寄存器里的值赋值给autotmp_4变量
  (.\main.go:5)     MOVQ    "".a+24(SP), AX           ; 将a赋值给AX寄存器
  (.\main.go:5)     SUBQ    "".b+16(SP), AX           ; 用AX里的值,减去b的值
  (.\main.go:5)     MOVQ    AX, ""..autotmp_5+48(SP)  ; 将AX里的计算结果赋值给autotmp_5变量
  (.\main.go:5)     MOVQ    ""..autotmp_4+56(SP), AX  ; 将autotmp_4变量放入AX寄存器中
  (.\main.go:5)     MOVQ    AX, "".c+8(SP)            ; 将AX寄存器中的值赋给c
  (.\main.go:5)     MOVQ    ""..autotmp_5+48(SP), AX  ; 将autotmp_5变量放入AX寄存器中
  (.\main.go:5)     MOVQ    AX, "".d(SP)              ; 将AX寄存器中的值赋给d
  ; -----情况二  相关汇编代码  结束----------

多个变量一同计算、互换变量值

func main() {
    a, b := 1, 5            //定义a, b
    c, d := a+b, a-b        //定义c ,d = 6, -4
    b, c, d = c, b, b+c     //探究单行多赋值计算
    _ = d                   //在本探究中没啥用的空行
}

对情况三来说,赋值的结果实际上是跟情况二是一样的:
1. 计算c、b、b+c的值(前两个无需计算),并存储在两个临时变量中(解释:为什么不是三个临时变量)
2. 将运算结果从临时变量中调出来,从左到右依次赋给b, c, d三个变量。

为什么不是三个临时变量?
对于前半部分 b, c = c, b ,想达成互换两个的值,只需要一个临时变量即可
temp := b
b = c
c = temp

  ; -----情况三  相关汇编代码  开始----------
  (.\main.go:6)     MOVQ    "".b+16(SP), AX           ; 将b的值,放入AX寄存器
  (.\main.go:6)     ADDQ    "".c+8(SP), AX            ; AX中的值加c的值(结果还保存在AX寄存器里)
  (.\main.go:6)     MOVQ    AX, ""..autotmp_6+40(SP)  ; 把上述计算结果赋值给autotmp_6变量
  (.\main.go:6)     MOVQ    "".b+16(SP), AX           ; 将b的值赋给AX
  (.\main.go:6)     MOVQ    AX, ""..autotmp_7+32(SP)  ; 把AX的值赋给autotmp_7变量
  (.\main.go:6)     MOVQ    "".c+8(SP), AX            ; 将c的值赋给AX
  (.\main.go:6)     MOVQ    AX, "".b+16(SP)           ; 把AX的值(c)赋给b
  (.\main.go:6)     MOVQ    ""..autotmp_7+32(SP), AX  ; 把autotmp_7变量的值赋给AX
  (.\main.go:6)     MOVQ    AX, "".c+8(SP)            ; 把AX的值(autotmp_7)赋给c
  (.\main.go:6)     MOVQ    ""..autotmp_6+40(SP), AX  ; 把autotmp_6变量的值赋给AX
  (.\main.go:6)     MOVQ    AX, "".d(SP)              ; 把AX的值(autotmp_6)赋给d
  ; -----情况三  相关汇编代码  结束----------

汇编代码详解:

实际应用(算法题)

《五行解决反转链表问题》


作者能力有限,若有哪些地方有误,请在评论区里指正。愿我们一同进步!

Go命令行程序(CLIs)框架——Cobra

什么是 CLIs

命令行界面(英语:Command-Line Interface,缩写:CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。也有人称之为字符用户界面(character user interface, CUI)。

虽然现在很多操作系统都提供了图形化的操作方式,但是却都保留了命令行界面,甚至有很多系统更加强了这部分功能。例如服务器端常用的Linux系统,目前就连以可视化桌面为主的Windows,也在强化自己命令行操作指令的功能和数量。
常见的CLI:
* bash、sh、ksh(Unix-like系统)
* COMMAND.COM(MS-DOS系统)
* cmd.exe(Windows命令提示符)
* Windows PowerShell(最新Windows NT系统搭载的CLI)

参考——《wikipedia——命令行界面》

常见的CLI程序
git、apt(Debian系系统的软件包管理器)等

CLI程序的结构

执行命令行程序的一般格式为:
APPNAME COMMAND ARG --FLAG
例如:
* git commit -m '简短的提交说明'
其中,git是程序名(APPNAME);commit是行为(COMMAND);-m是命令行选项,即对行为的改变(FLAG),'简短的提交说明'就是命令行的参数(ARG)。
* git clone URL --bare
其中,git是APPNAME;clone是COMMAND;URL是ARG;--bare是FLAG。


cobra

cobra github代码仓库

cobra是一个可以创建强大的CLI应用程序的一个库,cobra可以用来生成一个支持使用命令控制的程序。

cobra被用在了许多Go开发的程序,比如Kubernetes, Hugo 和 Github CLI 等程序。

cobra提供的功能:

  • 简单易用的基于子程序的CLI,比如app serverapp fetch等。
  • 完全兼容POSIX标志(包括短和长版标志)
  • 支持嵌套子命令
  • 支持全局标志、局部标志、和传递标志
  • 可以非常简单地生成应用程序(cobra init appname)和命令(cobra add cmdname
  • 可以智能纠错(输入错误的命令app servr,会提示“did you meanapp server?”)
  • 自动生成有关命令(COMMEND)和标志(FLAG)的帮助
  • 自动识别帮助标志,例如-h--help
  • 可以自动生成bash、zsh、fish、PowerShell的命令补全功能
  • 自动生成该程序的手册
  • 支持命令别名
  • 可以灵活地定义自己的帮助、用法等信息
  • Optional tight integration with viper for 12-factor apps

简要了解了cobra以及它可以提供的功能以后,我们现在安装cobra吧!

安装cobra(导入cobra包)

安装cobra包:

$ go get -u github.com/spf13/cobra/cobra

使用cobra生成应用程序框架

  1. 在要生成代码的目录下运行(要把程序生成在./appname/下,就要在./的目录下运行命令)
$ cobra init [appname] --pkg-name [pkgname]

此时我们就能在目录下看到由框架自动生成的程序啦!
不过在build这个程序之前,还需要初始化一下go mod,随后就能build了:

$ go mod init [moduleName]

如果要添加子命令,那么就在程序目录(./appname/)下运行:

$ cobra add [commandName]

上述命令执行完毕以后,我们再来看一下目录结构:

  ▾ appName/
    ▾ cmd/
        root.go
        your.go
        commands.go
      main.go

这就是一个cobra程序最基础的目录结构。

编写一个最基础的cobra程序!

我们再来看一下我们刚才通过cobra add [commandName]这条命令添加的新程序的代码:

为什么不看通过cobra init [appname] --pkg-name [pkgname]这条命令生成的./cmd/root.go这个代码?
通过init生成的root.go并不是最简单的cobra程序,其中包括了另一个函数initConfig(),以及一些有关于读取cfgFile的内容。这些内容我认为属于进阶的内容。

/*
    作者代码自带的版权信息:略
*/
package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
)

// miaoCmd represents the miao command
var miaoCmd = &cobra.Command{
    Use:   "miao",//这里需要写commandName,也就是响应这个操作的命令名。

    Short: "A brief description of your command【关于这条命令的简要说明】",
    Long: `A longer description that spans multiple lines and likely contains examples【一个长的说明,并且你可以使用跨越多行的文字,就像现在这个现在这个例子一样】
and usage of using your command. For example:【还有你这条命令的详细用法,例如:】
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.【巴拉巴拉巴拉】`,

    Run: func(cmd *cobra.Command, args []string) {//这里定义了如果执行了这条命令,所要运行的函数。
        fmt.Println("miao called")
    },
}

func init() {
    rootCmd.AddCommand(miaoCmd)

    // Here you will define your flags and configuration settings.
    //【在这里,你将定义你自己的flags和配置结构】

    // Cobra supports Persistent Flags which will work for this command
    //【cobra支持全局的flags,这个flag可以在这条命令,还有它的子命令中被使用】
    // and all subcommands, e.g.:
    miaoCmd.PersistentFlags().String("foo", "", "A help for foo")


    // Cobra supports local flags which will only run when this command
    //【cobra支持局部flags,这类flags只在你运行这条命令的时候被运行。且它会立刻被执行】
    // is called directly, e.g.:
    miaoCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

从生成代码的结构看,它有几这么几个变量和函数:

1. var miaoCmd = &cobra.Command

通过这个变量,定义了一个新的命令,这个命令里有四个

.
.
.
.
.
.
.

数据库的三范式

范式概念

在关系型数据库中,设计数据库时要考虑遵循一定的规则,这种规则就是范式。

实际上,数据库一共有六种范式,但一般设计数据库的时候,只需要遵守到3NF(第三范式)即可。
1NF 2NF 3NF BCNF 4NF 5NF

第一范式 1NF

第一范式主要强调原子性,即数据库的每一列都是一个不可再分割的基本数据项。

例:下表不满足第一范式

学号 姓名 家庭电话及住址
23333 马猴烧酒 166 xxxx xxxx,唐辛子星

修改后即可满足第一范式

学号 姓名 家庭电话 住址
23333 马猴烧酒 166 xxxx xxxx 唐辛子星

第二范式 2NF

第二范式主要强调唯一性,即数据表中每行都必须可以被唯一地区分,主要是为了防止数据重复,通常是使用一个主键来唯一标识一条记录。
满足第二范式就肯定满足第一范式。

  • 完全依赖、部分依赖
    因为主键可以由多列共同组成,
    当且仅当该行数据由主键中所有列共同确定时,我们说这条数据完全依赖于主键。
    当该行数据仅需要主键中的部分列即可确定时,我们说这条数据部分依赖于主键。

:完全依赖

学号(主键) 科目(主键) 分数
23333 数学 99

分数完全依赖于学号和科目,二者缺一不可。

:部分依赖

学号(主键) 姓名(主键) 科目(主键) 分数
23333 张三 数据库原理及应用 88

分数部分依赖于上述三个主键,原因:学号就可以确定出某位学生的姓名,分数依赖于科目和(学号姓名)在没有重名的情况下

第三范式 3NF

非主属性之间不能相互依赖,必须直接依赖候选关键字。
例:下表不满足第三范式

学号(主键) 姓名 班级 学生电话 班主任姓名 班主任电话
23333 张三 985211 168 xxxx xxxx 李四 189 xxxx xxxx
23334 王五 985211 199 xxxx xxxx 李四 189 xxxx xxxx

班主任的姓名、班主任的电话跟主键学号没有直接的关系,只有间接的关系(可以通过学生的班级了解到班主任的姓名电话)。故该表的非主属性班主任姓名、班主任电话依赖该表的非主属性班级,且间接依赖关键字学号。

修改后满足第三范式:

学号(主键) 姓名 班级 学生电话
23333 张三 985211 168 xxxx xxxx
23334 王五 985211 199 xxxx xxxx
班级 班主任姓名 班主任电话
985211 李四 189 xxxx xxxx
  • 直接依赖、传递依赖(函数依赖、间接依赖)
    a依赖b,b依赖c,则a肯定依赖c,这就是传递依赖。

学号 姓名 班级 班主任姓名
23333 张三 985211 李四

班主任姓名依赖于班级,班级依赖于学号,所以班主任姓名传递依赖学号,直接依赖班级。

Git基本操作

Git基本操作


点我下载(查看)原图 “Git基本操作.png”



Git仓库工作目录下的文件状态

已跟踪

  • Git已知的文件。工作一段时间以后,他们的状态可能是
    • 未修改
    • 已修改
    • 已放入暂存区

未跟踪

  • 除了上述文件以外的其他文件。

文件状态

  • 未跟踪 Untracked
  • 已跟踪
    • 暂存区 Staged
    • 非暂存区 Modified
    • 已提交 Unmodified


获取(新建)Git仓库

将一个本地目录转换为Git仓库(新建)

  • git init

从其他远程仓库中克隆一个仓库(获取)

  • git clone <url>
  • git clone <url> <自定义目录名>


忽略文件

在任意目录(子目录)下新建 .gitignore 文件

一个仓库可以只有一个 .gitignore 文件(一般情况都只有这一个文件),也可以在子目录下拥有额外的 .gitignore 文件

格式规范

  • 注释:所有空行或者以 # 开头的行都会被 Git 忽略。
  • 使用标准的 glob 模式匹配,(shell使用的简化版正则表达式)
    1. 星号(*)匹配零个或多个任意字符
    2. [abc] 匹配任何一个列在方括号中的字符 (这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c)
    3. 问号(?)只匹配一个任意字符
    4. 如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)
    5. 使用两个星号(**)表示匹配任意中间目录,比如 a/**/z 可以匹配 a/za/b/za/b/c/z
  • 匹配模式可以以(/)开头防止递归。
  • 匹配模式可以以(/)结尾指定目录。
  • 要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反。


记录每次更新到仓库

查看文件的状态

  • git status 输出内容:
    - changes to be committed 文件已改变,已暂存,准备提交
    - changes not staged for commit 已跟踪文件的内容发生了变化,但还没有放到暂存区。
    - untracked files 未跟踪的文件
  • git status -s(--short)简化输出

跟踪新文件(将一个文件添加到项目中)

  • git add <sth>

精准地将内容添加到下一次提交中

  • git add

git add有多重功能,主要有以下功能:

  1. 开始跟踪新的文件
  2. 把已跟踪的文件放到暂存区
  3. 合并时把有冲突的文件标记为已解决的状态


查看已暂存和未暂存的修改

查看未暂存的文件更新了哪些部分

  • git diff

查看已暂存的,将要添加到下次提交里的内容

  • git diff --staged
  • git diff --cached
    注:staged 和 cached 两个单词为近义词

使用diff插件来输出diff的分析结果

  • git difftool


提交更新

直接提交已暂存的内容

  • git commit

跳过使用暂存区域,直接将所有已经跟踪过的文件暂存起来一并提交

  • git commit -a
    小心,有时这个选项会将不需要的文件添加到提交中

直接提交已暂存内容,并使用简要提交信息

  • git commit -m "<提交信息>"

编辑提交信息

通过前两种方法(git commitgit commit -a)提交更新以后,git会调用系统中的文本编辑器(比如vim),用户需要在文本编辑器里编写提交信息,保存退出编辑器后git会正式提交信息。提交的信息不能为空!

提交过后的git返回的信息(提交后输出的内容)

  • 当前是在哪个分支提交的(master)
  • 本次提交的SHA-1校验和(bf530a8)
  • 本次提交中有多少文件修订过多少行添加或删改过


删除文件

从已跟踪的文件清单中移除(不删除文件)

  • git rm <file>
    运行上述命令,再运行git status 时,会在已暂存清单中看到该文件。
    下一次提交时,该文件就不再纳入版本管理了。

简单地从工作目录中手动删除文件

  • rm <file>
    运行上述命令,再运行git status时,会在未暂存清单中看到该文件。

删除之前已修改过,或删除已放到暂存区的文件

  • git rm -f <file> -f 即 force(强行)
    通过这种方法删除的数据不能被git恢复

把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然保留在当前工作目录中。(比如编译生成的文件或日志文件)

  • git rm --cached <file>

git rm 命令后面可以列出文件或者目录的名字,也可以使用 glob 模式。

  • 例:git rm log/\*.log
    glob模式在上文 忽略文件 处有所介绍


移动文件

命令

  • git mv <file_from> <file_to>

Git 并不显式跟踪文件移动操作。 如果在 Git 中重命名了某个文件,仓库中存储的元数据并不会体现出这是一次改名操作。
但是,此时如果查看状态信息(git status)实际上是能看到关于重命名操作的说明的

(输出)
renamed:    README.md -> README

实际上,运行git mv相当于运行了三条命令

mv README.md README
git rm README.md
git add README

MySQL之事务与隔离级别(概要篇)

事务

事务的概念

事务就是一组原子性的SQL查询,或者说是一个独立的工作单元。如果数据库引擎能够成功地对数据库应用该组查询的全部语句,那么久执行该组查询。如果其中有任何一条语句因为崩溃或者其他原因无法执行,那么所有的语句都将不会执行。
也就是说,事务内的语句,要么全部执行成功,要么全部执行失败。

经典例子:“银行应用”

假设要从用户A的银行卡账户转到用户B的银行卡账户200元钱,应该怎么做?

应该至少需要三个步骤:
1. 检查用户A的账户余额是否高于200元?
1. 从用户A的银行卡账户中减去200元。
1. 在用户B的银行卡账户中加上200元。

上述三个步骤的操作必须打包在一个事务中,任何一个步骤失败,则必须回滚所有的步骤。

可以用 START TRANSACTION 语句开始一个事务,然后要么使用 CONNIT 提交事务将修改的数据并持久保留,要么使用 ROLLBACK 撤销所有的修改。

事务的SQL样本如下:

START TRANSACTION;
SELECT balance FROM savings WHERE customer_ id = 111;
UPDATE savings balance = balance - 200. 00 WHERE customer_ id = 111;
UPDATE savings SET balance = balance + 200. 00 WHERE customer_ id = 111;
COMMIT;

试想一下,如果执行到第4条语句的时候,服务器奔溃了,会发生什么?
也许用户A因此损失了200元。也许执行到第3到第4条语句之间,服务器奔溃了,用户B因此白得到了200元。

除非系统通过严格的ACID测试,否则空谈事务的概念是远远不够的。

什么是ACID?

ACID表示 原子性(atomicity)一致性(consistency)隔离性(isolation)持久性(durability) 。一个运行良好的事务处理系统,必须具备这些标准的特征。

  • 原子性(atomicity):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一个部分操作,这就是事务的原子性。

  • 一致性(consistency):数据库总是从一个一致性的状态转换到另外一个一致性的状态。在前面的例子中,一致性确保了,即使在执行第三、四条语句之间时系统崩溃,A账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。

  • 隔离性(isolation):通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外一个关于A账户付款程序开始运行,则其看到的A账户的余额并没有被减去200美元。

  • 持久性(durability):一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。

在实际应用中,若想实现ACID特效非常地难,甚至可以说是一个不可能完成的任务。

就像各种锁一样,实现ACID的数据库相比没有实现的数据库,需要花费更多的CPU时间。

事务的隔离级别