jasonje / notes Goto Github PK
View Code? Open in Web Editor NEW:books: 所看所学所记,Python,Go,后端/架构技术,数据分析,机器学习。持续学习中
Home Page: https://www.yuque.com/jasonje/notes
:books: 所看所学所记,Python,Go,后端/架构技术,数据分析,机器学习。持续学习中
Home Page: https://www.yuque.com/jasonje/notes
__new__
至少要有一个参数cls
,代表当前类,此参数在实例化时由Python
解释器自动识别;
__new__
必须要有返回值,返回实例化出来的实例,这点在自己实现__new__
时要特别注意,可以return
父类(通过super
(当前类名,cls
))__new__
出来的实例,或者直接是object
的__new__
出来的实例;
__init__
有一个参数self
,就是这个__new__
返回的实例,__init__
在__new__
的基础上可以完成一些其它初始化的动作,__init__
不需要返回值;
如果__new__
创建的是当前类的实例,会自动调用__init__
函数,通过return
语句里面调用的__new__
函数的第一个参数是cls
来保证是当前类实例,如果是其他类的类名,那么实际创建返回的就是其他类的实例,其实就不会调用当前类的__init__
函数,也不会调用其他类的__init__
函数。
浅复制仅仅复制了容器中元素的地址;深复制,完全拷贝了一个副本,容器内部元素地址都不一样。
复制不可变数据类型,不管copy()
还是deepcopy()
,都是同一个地址,和=
效果一样,对象的id
值与浅复制原来的值相同;
复制的值是可变对象(列表和字典)
copy()
有两种情况:第一种情况:复制的对象中无复杂 子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id
值与浅复制原来的值不同。
第二种情况:复制的对象中有复杂 子对象 (例如列表中的一个子元素是一个列表), 改变原来的值中的复杂子对象的值 ,会影响浅复制的值。
deepcopy()
:完全复制独立,包括内层列表和字典函数参数为interface{}
时候可以接收任何类型的值。
永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。
type Fragment interface {
Exec(transInfo *TransInfo) error
}
type GetPodAction struct {
}
func (g GetPodAction) Exec(transInfo *TransInfo) error {
...
return nil
}
正确的赋值方式有:
var fragment Fragment = new(GetPodAction)
var fragment Fragment = &GetPodAction{}
var fragment Fragment = GetPodAction{}
panic
package main
func main() {
var x interface{}
var y interface{} = []int{3, 5}
_ = x == x
_ = x == y
_ = y == y
}
解析:_ = y == y
行。因为两个比较值的动态类型为同一个不可比较类型。
http {
...
proxy_cache_path /path/to/cache
levels=1:2
keys_zone=nginx_cache:1024m
max_size=10g
inactive=7d
use_temp_path=off; # 最好写在同一行
server {
...
proxy_cache_key $uri;
proxy_cache_methods GET POST;
proxy_cache nginx_cache;
proxy_cache_valid 200 206 304 301 302 7d;
set $nocache 0;
# 这里可以写相关的匹配规则,来对不同的请求进行缓存或不缓存
proxy_no_cache $nocache;
proxy_cache_bypass $nocache;
}
}
参数说明:
proxy_cache_path
:缓存存放路径;
levels
:设置缓存文件目录层次;如果所有的缓存放入一个文件夹,则影响效率。这里设置的是levels=1:2 ,表示两级目录;
keys_zone
:在共享内存中设置一块存储区域来存放缓存的 key
和 metadata
,这样 nginx
可以快速判断一个 request
是否命中或者未命中缓存,1m
可以存储 8000
个 key
,10m
可以存储80000个key
;
max_size
:最大的缓存空间,如果不指定,会使用掉所有的硬盘空间,当达到配额后,会删除最少使用的 cache
文件;
inactive
:删除指定时间内未被访问的缓存文件;
use_temp_path
:如果为 off
,则 nginx
会将缓存文件直接写入指定的 cache
文件中,而不是使用 temp_path
存储,建议为 off
,避免文件在不同文件系统中不必要的拷贝;
proxy_cache
:启用 proxy cache
,指定声明好的缓存区域 keys_zone
;
proxy_cache_valid 200 206 304 301 302 7d
:为对 http
状态为 200 206 304 301 302
等的缓存结果缓存 7
天;
proxy_cache_key $uri
:定义缓存唯一 key
,通过唯一 key
来进行 hash
存取;
proxy_no_cache
和 proxy_cache_bypass
:当为 0
时表示缓存,不为 0
时表示不缓存。
注意:
$upstream_cache_status
变量可以查看缓存命中的状态,相关的日志中可以设置。uwsgi
进行代理,上面的全部参数只要将 proxy
换成 uwsgi
即可,类似 uwsgi_cache_path
。redo log
的大小是固定的,在MySQL
中可以通过修改配置参数innodb_log_files_in_group
和 innodb_log_file_size
配置日志文件数量和每个日志文件大小,redolog
采用循环写的方式记录,当写到结尾时,会回到开头循环写日志。
write pos
表示日志当前记录的位置,一边写一边后移;
当ib_logfile_3
写满后,会从ib_logfile_0
从头开始记录;
checkpoint
表示将日志记录的修改写入磁盘,完成写入后,checkpoint
会将日志上的相关记录擦除掉;
write pos->checkpoint
之间的部分是 redo log
空着的部分,用于记录新的记录;
checkpoint->write pos
之间是redo log
待落盘的数据修改记录;
当write pos
追上checkpoint
时,得先停下记录,先推动checkpoint
向前移动,空出位置记录新的日志。
主键索引的叶子节点存的是整行数据。在 InnoDB
里,主键索引也被称为聚簇索引 (clustered index
);
非主键索引的叶子节点内容是主键的值。在 InnoDB
里,非主键索引也被称为二级索引 (secondary index
)。
create table T(
ID int primary key,
k int not null,
name varchar(16),
index (k))engine=InnoDB;
如果语句是 select * from T where ID=500
,即主键查询方式,则只需要搜索 ID
这棵 B+
树;
如果语句是 select * from T where k=5
,即普通索引查询方式,则需要先搜索 k
索引 树,得到 ID
的值为 500
,再到 ID
索引树搜索一次。这个过程称为回表。
2019.10.11
INSERT INTO
表示插入数据,数据库会检查主键,如果出现重复会报错;
REPLACE INTO
表示插入替换数据,需求表中有PrimaryKey
,或者unique
索引,如果数据库已经存在数据,则用新数据替换,如果没有数据效果则和insert into
一样;
INSERT IGNORE INTO
表示,如果中已经存在相同的记录,则忽略当前新数据。
SQLAlchemy
表对象模型生成器——sqlacodegen
2019.10.14
安装:
pip install sqlacodegen
使用:
sqlacodegen postgresql:///some_local_db
sqlacodegen mysql+oursql://user:password@localhost/dbname
sqlacodegen sqlite:///database.db
经典类中MRO
搜索采用简单的从左到右的深度优先顺序,而新式类是广度优先。
dict.setdefault(key, default)
接收两个参数,第一个参数是健的名称,第二个参数是默认值。
假如字典中不存在给定的键,则返回参数中提供的默认值;反之,则返回字典中保存的值。
collections.defaultdict
from collections import defaultdict
mydict = defaultdict(list)
上面的初始化函数接受一个类型作为参数,当所访问的键不存在的时候,可以实例化一个值作为默认值。
其只有在通过dict[key]
或者dict.__getitem__(key)
访问的时候才有效,例如使用mydict.pop('something')
就无效。
该类除了接受类型名称作为初始化函数的参数之外,还可以使用任何不带参数的可调用函数,到时该函数的返回结果作为默认值。
collections.defaultdict
的实现定义了__missing__()
方法,当使用__getitem__()
访问一个不存在的键的时,会调用__missing__()
方法获取默认值,并将该键添加到字典中去。
dict
中并不存在这个__missing__()
方法,需要在派生的子类中自行实现。
var(
size := 1024
max_size = size*2
)
func main() {
fmt.Println(size,max_size)
}
解析:变量声明的简短形式中,需要使用显示初始化,编译器会自动推导数据类型,所以不需要提供数据类型,只能在函数内部使用简短形式。
iota
的使用const (
x = iota
_
y
z = "zz"
k
p = iota
)
func main() {
fmt.Println(x,y,z,k,p)
}
输出:
0 2 zz zz 5
解析:
iota
只在常量表达式中使用;
每次 const
出现,都会让const
初始化为0
;
自增常量经常包含一个自定义的枚举类型,允许依靠编译器完成自增设置;
使用 _
跳过不需要的值;
定义在一行的情况:
const (
Apple, Banana = iota +1, iota + 2
Cherimoya, Durian
Elderberry, Fig
)
上面变量的结果如下:
Apple: 1
Banana: 2
Cherimoya: 2
Durian: 3
Elderberry: 3
Fig: 4
const (
i = iota
j = 3.14
k = iota
l
)
上面各个变量对应的结果就是0, 3.14, 2, 3
。
var x error = nil
解析:nil
只能赋值给指针、chan
、func
、interface
、map
或slice
。
error
的定义如下:
type error interface {
Error() string
}
func hello() []string {
return nil
}
func main() {
h := hello
if h == nil {
fmt.Println("nil")
} else {
fmt.Println("not nil")
}
}
输出:not nil
解析:h
是hello()
,所以其不为空,能通过编译。
iota
和 const
关键字const (
a = iota
b = iota
)
const (
name = "name"
c = iota
d = iota
)
func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}
解析:输出:0 1 1 2
。iota
只能在常量的表达式中使用。iota
在 const
关键字出现时将被重置为 0
,const
中每新增一行常量声明将使 iota
计数一次。
type Direction int
const (
North Direction = iota
East
South
West
)
func (d Direction) String() string {
return [...]string{"North", "East", "South", "West"}[d]
}
func main() {
fmt.Println(South)
}
输出:South
解析:根据 iota
的用法推断出 South
的值是 2
;另外,如果类型定义了 String()
方法,当使用 fmt.Printf()
、fmt.Print()
和 fmt.Println()
会自动使用 String()
方法,实现字符串的打印。
var p *int
func foo() (*int, error) {
var i int = 5
return &i, nil
}
func bar() {
//use p
fmt.Println(*p)
}
func main() {
p, err := foo()
if err != nil {
fmt.Println(err)
return
}
bar()
fmt.Println(*p)
}
解析:问题出在操作符:=
,对于使用:=
定义的变量,如果新变量与同名已定义的变量不在同一个作用域中,那么 Go
会新定义这个变量。对于本例来说,main()
函数里的 p
是新定义的变量,会遮住全局变量 p
,导致执行到 bar()
时程序,全局变量 p
依然还是 nil
,程序随即 Crash
。
func main() {
i := 1
s := []string{"A", "B", "C"}
i, s[i-1] = 2, "Z"
fmt.Printf("s: %v \n", s)
}
输出:[Z, B, C]
解析:多重赋值时候,上例中,先计算等号左边的索引表达式和取值表达式,接着解读等号右边的表达式,然后进行赋值。所以赋值运算等同于 i, s[0] = 2, "Z"
。
i++
和i--
在Go
中是语句,不是表达式,所以不能进行赋值操作。
下面代码有什么问题
const i = 100
var j = 123
func main() {
fmt.Println(&j, j)
fmt.Println(&i, i)
}
结果:编译报错cannot take the address of i
解析:常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,所以常量无法寻址。
func GetValue(m map[int]string, id int) (string, bool) {
if _, exist := m[id]; exist {
return "exist", true
}
return nil, false
}
func main() {
intmap := map[int]string{
1: "a",
2: "b",
3: "c",
}
v, err := GetValue(intmap, 3)
fmt.Println(v, err)
}
结果:不能通过编译。
解析:nil
可以用作 interface
、function
、pointer
、map
、slice
和 channel
的空值。但是如果不特别指定的话,Go
语言不能识别类型,所以会报错: cannot use nil as type string in return argument.
。
func Foo(x interface{}) {
if x == nil {
fmt.Println("empty interface")
return
}
fmt.Println("non-empty interface")
}
func main() {
var x *int = nil
Foo(x)
}
输出:non-empty interface
解析:接口除了有静态类型,还有动态类型和动态值,当且仅当动态值和动态类型都为 nil
时,接口类型值才为 nil
。这里的 x
的动态类型是 *int
,所以 x
不为 nil
。
func main() {
var s []int
s = append(s,1)
var m map[string]int
m["one"] = 1
}
解析:不能对 nil
的 map
直接赋值,需要使用 make()
初始化。但可以使用 append()
函数对为 nil
的 slice
增加元素。
通过指针变量 p
访问其成员变量 name
,有两种方式:p.name
和(*p).name
。
下面的代码有什么问题
func main() {
var x = nil
_ = x
}
解析:nil
用于表示 interface
、函数、maps
、slices
和 channels
的零值。如果不指定变量的类型,编译器猜不出变量的具体类型,导致编译错误。
修复代码:
func main() {
var x interface{} = nil
_ = x
}
func test(x byte) {
fmt.Println(x)
}
func main() {
var a byte = 0x11
var b uint8 = a
var c uint8 = a + b
test(c)
}
解析:34
。与 rune
是 int32
的别名一样,byte
是 uint8
的别名,别名类型无须转换,可直接计算。
func main() {
const x = 123
const y = 1.23
fmt.Println(x)
}
解析:编译可以通过。常量是一个简单值的标识符,在程序运行时,不会被修改的量。不像变量,常量未使用是能编译通过的。
const (
x uint16 = 120
y
s = "abc"
z
)
func main() {
fmt.Printf("%T %v\n", y, y)
fmt.Printf("%T %v\n", z, z)
}
输出:
uint16 120
string abc
解析:常量组中如不指定类型和初始化值,则与上一行非空常量右值相同。
func (n N) value(){
n++
fmt.Printf("v:%p,%v\n",&n,n)
}
func (n *N) pointer(){
*n++
fmt.Printf("v:%p,%v\n",n,*n)
}
func main() {
var a N = 25
p := &a
p1 := &p
p1.value()
p1.pointer()
}
结果:编译错误。
calling method value with receiver p1 (type **N) requires explicit dereference
calling method pointer with receiver p1 (type **N) requires explicit dereference
解析:不能使用多级指针调用方法。
session
保存在服务器端,cookie
保存在客户端;session
的运行依赖 session id
,而 session id
是存在 cookie
中的,也就是说,如果浏览器禁用了 cookie
,同时 session
也会失效(但是可以通过其它方式实现,比如在 url
中传递 session_id
);cookie
可以跟踪会话,也可以保存用户喜好或者保存用户名密码;session
用来跟踪会话。func funcMui(x,y int)(sum int,error){
return x+y,nil
}
结果:第二个返回值没有命名。
解析:
如果函数存在多个返回值,只要一个返回值存在命名,则另一个其他的返回值也必须命名;
如果有多个返回值,必须加上括号;如果只有一个带有命名的返回值,也必须带上括号。
init()
函数有几个需要注意的地方:init()
函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等;
一个包可以出现多个 init()
函数,一个源文件也可以包含多个 init()
函数;
同一个包中多个 init()
函数的执行顺序没有明确定义,但是不同包的init
函数是根据包导入的依赖关系决定的;;
init()
函数在代码中不能被显示调用、不能被引用(赋值给函数变量),否则出现编译错误;
一个包被引用多次,如 A import B,C import B,A import C
,B
被引用多次,但 B
包只会初始化一次;
引入包,不可出现死循环。即 A import B,B import A
,这种情况编译失败。
cap()
函数适用于array, slice, channel
类型。
可变函数的定义与调用方式
func add(args ...int) int {
sum := 0
for _, arg := range args {
sum += arg
}
return sum
}
定义:func f(elems ...Type)
调用:f(a, b, c);f([]Type{a, b, c}...)
func main() {
var fn1 = func() {}
var fn2 = func() {}
if fn1 != fn2 {
println("fn1 not equal fn2")
}
}
解析:编译错误,函数只能与 nil
比较。
type N int
func (n N) test(){
fmt.Println(n)
}
func main() {
var n N = 10
fmt.Println(n)
n++
f1 := N.test
f1(n)
n++
f2 := (*N).test
f2(&n)
}
输出:10 11 12
解析:通过类型引用的方法表达式会被还原成普通函数样式,接收者是第一个参数,调用时显示传参。类型可以是 T
或 *T
,只要目标方法存在于该类型的方法集中就可以。
还可以直接使用方法表达式调用:
func main() {
var n N = 10
fmt.Println(n)
n++
N.test(n)
n++
(*N).test(&n)
}
type N int
func (n N) test(){
fmt.Println(n)
}
func main() {
var n N = 10
p := &n
n++
f1 := n.test
n++
f2 := p.test
n++
fmt.Println(n)
f1()
f2()
}
输出:13 11 12
解析:基于实例或指针引用的 method value
,参数签名不会改变,依旧按正常方式调用。但当 method value
被赋值给变量或作为参数传递时,会立即计算并赋值该方法执行所需的 receiver
对象与其绑定,以便在稍后执行,能隐式传入 receiver
参数。
JSON
:键值对,轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。XML
: 在WebService
中应用最为广泛,但是相比于JSON
,它的数据更加冗余,因为需要成对的闭合标签。Protobuf
:谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为Protobuf
是二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。Protobuf
优势:
JSON
和XML
很小,适合网络传输;JSON
的处理速度。// 1.
func main() {
s := make([]int, 5)
s = append(s, 1, 2, 3)
fmt.Println(s)
}
// 2.
func main() {
s := make([]int, 0)
s = append(s,1,2,3,4)
fmt.Println(s)
}
输出:
[0 0 0 0 0 1 2 3]
[1 2 3 4]
new()
与make()
的区别new(T)
和 make(T, args)
是 Go
语言内建函数,用来分配内存,但适用的类型不同。
new(T)
会为 T
类型的新值分配已置零的内存空间,并返回地址(指针),即类型为 *T
的值。换句话说就是,返回一个指针,该指针指向新分配类型为 T
的零值。适用于值类型,如数组、结构体等。
make(T, args)
返回初始化之后的 T
类型的值,这个值并不是 T
类型的零值,也不是指针 *T
,是经过初始化之后的 T
的引用。make()
只适用于 slice
、map
和 channel
。
func main() {
list := new([]int)
list = append(list, 1)
fmt.Println(list)
}
结果:不能通过编译。
解析:new([]int)
返回一个*[]int
类型的指针,不能进行append
的操作。正确的方式是使用make()
或者字面量进行初始化。
count(*)
、count(1)
都会对全表进行扫描,统计所有记录的条数,包括那些为null
的记录。
count(col)
会统计该字段不为null的记录条数。
count(1)
比count(*)
快;count(主键)
效率最高;count(*)
效率较高。实现反射加法操作。
正常运算时,比如:a + b
,是以a
为主导的,这里调用了魔法方法__add__()
。但是如果表达式中的a
没有__add__()
方法时,就会调用b
的反运算方法__radd__()
。
>>> class Nint(int):
... def __radd__(self, other):
... return int.__sub__(self, other) # 剪法运算
...
>>> a = Nint(6)
>>> b = Nint(4)
>>> a + b
10
>>> 1 + b # 1 没有 __add__()方法,就调用 b 中的反运算方法 __radd__(),返回 int.__sub__(4, 1)
3
>>>
2019.03.28
守护进程(daemon
)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。
#!/bin/sh
#添加本地执行路径
export LD_LIBRARY_PATH=./
while true; do
# 启动一个循环,定时检查进程是否存在
server=`ps aux | grep myServer | grep -v grep`
if [ ! "$server" ]; then
# 如果不存在就重新启动
nohup ./myServer -c 1 &
# 启动后沉睡10s
sleep 10
fi
# 每次循环沉睡1s
sleep 5
done
CPU
的亲和性,就是进程要在指定的CPU
上尽量长时间地运行而不被迁移到其他处理器,也称为CPU
关联性。
即需要将进程或线程绑定到相应的CPU
上;在多核运行的机器上,每个CPU
本身自己会有缓存,缓存着进程使用的信息,而进程可能会被OS
调度到其他CPU
上,如此,CPU cache
命中率就低了,当绑定CPU
后,程序就会一直在指定的CPU
跑,不会由操作系统调度到其他CPU
上,性能有一定的提高。
软亲和性:就是进程要在指定的CPU
上尽量长时间地运行而不被迁移到其他处理器,Linux
内核进程调度器天生就具有被称为软亲和性的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。
硬亲和性:简单来说就是利用Linux
内核提供给用户的API
,强行将进程或者线程绑定到某一个指定的CPU
核运行。
解释:在Linux
内核中,所有的进程都有一个相关的数据结构,称为task_struct
。这个结构中与亲和性相关度最高的是cpus_allowed
位掩码。这个位掩码由n
位组成,与系统中的n
个逻辑处理器一一对应。具有4
个物理CPU
的系统可以有4
位。如果这些CPU
都启用了超线程,那么这个系统就有一个8
位的位掩码。如果为给定的进程设置了给定的位,那么这个进程就可以在相关的CPU
上运行。因此,如果一个进程可以在任何CPU
上运行,并且能够根据需要在处理器之间进行迁移,那么位掩码就全是1
,这就是Linux
中进程的缺省值。
goroutine
在CPU
中的迁移package main
import (
"fmt"
"math/rand"
"runtime"
"time"
)
/*
#define _GNU_SOURCE
#include <sched.h>
*/
import "C"
func randSleep() {
time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)
}
func worker(id int) {
for {
fmt.Printf("worker: %d, CPU: %d\n", id, C.sched_getcpu())
randSleep()
}
}
func main() {
for i := 0; i < runtime.NumCPU(); i++ {
go worker(i)
}
time.Sleep(2 * time.Second)
}
goroutine
到指定CPU
package main
import (
"fmt"
"math/rand"
"os"
"runtime"
"time"
)
/*
#define _GNU_SOURCE
#include <sched.h>
#include <pthread.h>
void lock_thread(int cpuid) {
pthread_t tid;
cpu_set_t cpuset;
tid = pthread_self();
CPU_ZERO(&cpuset);
CPU_SET(cpuid, &cpuset);
pthread_setaffinity_np(tid, sizeof(cpu_set_t), &cpuset);
}
*/
import "C"
func randSleep() {
time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)
}
func setAffinity(cpuID int) {
runtime.LockOSThread()
C.lock_thread(C.int(cpuID))
}
func worker(id int, lock bool) {
if lock {
setAffinity(id)
}
for {
fmt.Printf("worker: %d, CPU: %d\n", id, C.sched_getcpu())
randSleep()
}
}
func main() {
lock := len(os.Getenv("LOCK")) > 0
for i := 0; i < runtime.NumCPU(); i++ {
go worker(i, lock)
}
time.Sleep(2 * time.Second)
}
defer
和panic
的执行顺序package main
import (
"fmt"
)
func main() {
defer_call()
}
func defer_call() {
defer func() { fmt.Println("打印前") }()
defer func() { fmt.Println("打印中") }()
defer func() { fmt.Println("打印后") }()
panic("触发异常")
}
输出:
打印后
打印中
打印前
panic: 触发异常
解析:defer
的执行顺序是后进先出。当出现 panic
语句的时候,会先按照 defer
的后进先出的顺序执行,最后才会执行 panic
。
func increaseA() int {
var i int
defer func() {
i++
}()
return i
}
func increaseB() (r int) {
defer func() {
r++
}()
return r
}
func main() {
fmt.Println(increaseA())
fmt.Println(increaseB())
}
解析:increaseA()
的返回参数是匿名,increaseB()
是具名。所以结果是0, 1
。
func f1() (r int) {
defer func() {
r++
}()
return 0
}
func f2() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}
func f3() (r int) {
defer func(r int) {
r = r + 5
}(r)
return 1
}
解析:
f1()
结果为1
,拆解过程:func f1() (r int) {
// 1.赋值
r = 0
// 2.闭包引用,返回值被修改
defer func() {
r++
}()
// 3.空的 return
return
}
f2()
结果为5
,拆解过程:func f2() (r int) {
t := 5
// 1.赋值
r = t
// 2.闭包引用,但是没有修改返回值 r
defer func() {
t = t + 5
}()
// 3.空的 return
return
}
f3()
结果为1
,拆解过程:func f3() (r int) {
// 1.赋值
r = 1
// 2.r 作为函数参数,不会修改要返回的那个 r 值
defer func(r int) {
r = r + 5
}(r)
// 3.空的 return
return
}
第2
步,r
是作为函数参数使用,是一份复制,defer
语句里面的 r
和 外面的 r
其实是两个变量,里面变量的改变不会影响外层变量 r
,所以不是返回 6
,而是返回 1
。
type Person struct {
age int
}
func main() {
person := &Person{28}
// 1.
defer fmt.Println(person.age)
// 2.
defer func(p *Person) {
fmt.Println(p.age)
}(person)
// 3.
defer func() {
fmt.Println(person.age)
}()
person.age = 29
}
解析:
1
中将 28 当做 defer
函数的参数,会把 28
缓存在栈中,等到最后执行该 defer
语句的时候取出,即输出 28
;
2
缓存的是结构体 Person{28}
的地址,最终 Person{28}
的 age
被重新赋值为 29
,所以 defer
语句最后执行的时候,依靠缓存的地址取出的 age
便是 29,即输出 29
;
3
中闭包引用,输出 29
。
func hello(i int) {
fmt.Println(i)
}
func main() {
i := 5
defer hello(i)
i = i + 10
}
解析:hello()
函数的参数在执行 defer
语句的时候会保存一份副本在实际调用 hello()
函数时用,所以是 5
。
return
之后的 defer
语句会执行吗,下面这段代码输出什么?var a bool = true
func main() {
defer func(){
fmt.Println("1")
}()
if a == true {
fmt.Println("2")
return
}
defer func(){
fmt.Println("3")
}()
}
解析:defer
关键字后面的函数或者方法想要执行必须先注册,return
之后的defer
是不能注册的, 也就不能执行后面的函数或方法。
func f(n int) (r int) {
defer func() {
r += n
recover()
}()
var f func()
defer f()
f = func() {
r += 2
}
return n + 1
}
func main() {
fmt.Println(f(3))
}
输出:7
解析:第一步执行r = n + 1
,接着执行第二个 defer
,由于此时 f()
未定义,引发异常,随即执行第一个 defer,异常被
recover(),程序正常执行,最后
return`。
func F(n int) func() int {
return func() int {
n++
return n
}
}
func main() {
f := F(5)
defer func() {
fmt.Println(f())
}()
defer fmt.Println(f())
i := f()
fmt.Println(i)
}
输出:7 6 8
解析:defer
后面的函数如果带参数,会优先计算参数,并将结果存储在栈中,到真正执行 defer
的时候取出。
recover
的调用func main() {
defer func() {
recover()
}()
panic(1)
}
recover()
必须在 defer()
函数中直接调用才有效。
那么下面代码输出什么
func main() {
defer func() {
fmt.Print(recover())
}()
defer func() {
defer fmt.Print(recover())
panic(1)
}()
defer recover()
panic(2)
}
defer recover()
行代码捕获是无效的,在调用 defer()
时,便会计算函数的参数并压入栈中,所以执行 defer fmt.Print(recover())
行代码时,此时便会捕获 panic(2)
。此后的 panic(1)
,会被上一层的 recover()
捕获,最终输出 2 1
。
is
:比较的是两个对象的id
值是否相等,也就是比较俩对象是否为同一个实例对象,是否指向同一个内存地址;
==
:比较的两个对象的内容/值是否相等,默认会调用对象的eq()
方法。
传输层UDP
是不可靠的连接,如果要实现可靠传输,就要在传输层的上一层(或者直接在应用层)实现类似于 TCP
协议的实现:超时重传(定时器)、有序接受 (添加包序号)、应答确认 (Seq/Ack
应答机制)、滑动窗口流量控制等机制 (滑动窗口协议)等。
目前已经有一些实现UDP
可靠传输的机制,比如UDT
。
a. 在Python
里凡是继承了object
的类,都是新式类;
b. Python3
里只有新式类;
c. Python2
里面继承object
的是新式类,没有写父类的是经典类;
d. 经典类目前在Python
里基本没有应用;
e. 保持class
与type
的统一对新式类的实例执行a.__class__
与type(a)
的结果是一致的,对于旧式类来说就不一样了;
f.对于多重继承的属性搜索顺序不一样新式类是采用广度优先搜索,旧式类采用深度优先搜索。
MySQL
之间数据复制的基础是二进制日志文件(binary log file
)。
一台MySQL
数据库一旦启用二进制日志后,其作为master
,它的数据库中所有操作都会以事件
的方式记录在二进制日志中,其他数据库作为slave
通过一个I/O
线程与主服务器保持通信,并监控master
的二进制日志文件的变化,如果发现master
二进制日志文件发生变化,则会把变化复制到自己的中继日志中,然后slave
的一个SQL线程
会把相关的事件
执行到自己的数据库中,以此实现从数据库和主数据库的一致性,也就实现了主从复制。
__new__
class SingleTon(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = object.__new__(cls)
return cls._instance
class MyClass(SingleTon):
def __init__(self, val):
self.val = val
if __name__ == '__main__':
a = MyClass(1)
b = MyClass(2)
print(a is b)
print(id(a), id(b))
print(type(a))
print(type(b))
from functools import wraps
def singleton(cls):
_instance = {}
@wraps(cls)
def single(*args, **kwargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
return single
@singleton
class SingleTon(object):
def __init__(self, val):
self.val = val
if __name__ == '__main__':
a = SingleTon(1)
b = SingleTon(2)
print(a is b)
print(id(a), id(b))
print(type(a))
print(type(b))
__metaclass__
class SingletonType(type):
_instance = None
def __call__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(SingletonType, cls).__call__(*args, **kwargs)
return cls._instance
class MyClass(metaclass = SingletonType): # 注意类名不能为 Singleton
def __init__(self, val):
self.val = val
if __name__ == '__main__':
a = MyClass(1)
b = MyClass(2)
print(a is b)
print(id(a), id(b))
print(type(a))
print(type(b))
package main
import "fmt"
type MyInt1 int
type MyInt2 = int
func main() {
var i int =0
var i1 MyInt1 = i
var i2 MyInt2 = i
fmt.Println(i1,i2)
}
解析:type MyInt1 int
是基于int
类型创建了新类型,而type MyInt2 = int
是创建了 int
类型的别名MyInt2
。
var i1 MyInt1 = i
是将int
类型的变量赋值给MyInt1
,Go
是强类型的语言,编译不能通过,正确的做法是进行强制类型转换,即var i1 MyInt1 = MyInt1(i)
;而MyInt2
是int
的别名,能够进行赋值。
func GetValue() int {
return 1
}
func main() {
i := GetValue()
switch i.(type) {
case int:
println("int")
case string:
println("string")
case interface{}:
println("interface")
default:
println("unknown")
}
}
结果:不能通过编译。
解析:只有接口类型才能进行类型选择。
func main() {
a := 5
b := 8.1
fmt.Println(a + b)
}
解析:编译出错,Go
语言是强类型语言,不同类型之间不能直接相加。
func (i int) PrintInt () {
fmt.Println(i)
}
func main() {
var i int = 1
i.PrintInt()
}
解析:编译错误。基于类型创建的方法必须定义在同一个包内;解决的办法可以定义一种新的类型。即:
type Myint int
func (i Myint) PrintInt () {
fmt.Println(i)
}
func main() {
var i Myint = 1
i.PrintInt()
}
type People interface {
Speak(string) string
}
type Student struct{}
func (stu *Student) Speak(think string) (talk string) {
if think == "speak" {
talk = "speak"
} else {
talk = "hi"
}
return
}
func main() {
var peo People = Student{}
think := "speak"
fmt.Println(peo.Speak(think))
}
解析:编译错误。值类型 Student
没有实现接口的 Speak()
方法,而是指针类型 *Student
实现该方法。
func main() {
sn1 := struct {
age int
name string
}{age: 11, name: "qq"}
sn2 := struct {
age int
name string
}{age: 11, name: "qq"}
if sn1 == sn2 {
fmt.Println("sn1 == sn2")
}
sm1 := struct {
age int
m map[string]string
}{age: 11, m: map[string]string{"a": "1"}}
sm2 := struct {
age int
m map[string]string
}{age: 11, m: map[string]string{"a": "1"}}
if sm1 == sm2 {
fmt.Println("sm1 == sm2")
}
}
结构体只能比较是否相等,但是不能比较大小。
相同类型的结构体才能够进行比较,结构体是否相同不但与属性类型有关,还与属性顺序相关。下面的sn3
与 sn1
就是不同的结构体:
sn3:= struct {
name string
age int
}{age:11,name:"qq"}
struct
的所有成员都可以比较,则该 struct
就可以通过 ==
或 !=
进行比较是否相等,比较时逐个项进行比较,如果每一项都相等,则两个结构体才相等,否则不相等。type People interface {
Show()
}
type Student struct{}
func (stu *Student) Show() {
}
func main() {
var s *Student
if s == nil {
fmt.Println("s is nil")
} else {
fmt.Println("s is not nil")
}
var p People = s
if p == nil {
fmt.Println("p is nil")
} else {
fmt.Println("p is not nil")
}
}
解析:输出:s is nil
和 p is not nil
。当且仅当动态值和动态类型都为 nil
时,接口类型值才为 nil
。上面的代码,给变量 p
赋值之后,p
的动态值是 nil
,但是动态类型却是 *Student
,是一个 nil
指针,所以相等条件不成立。
func main() {
x := interface{}(nil)
y := (*int)(nil)
a := y == x
b := y == nil
_, c := x.(interface{})
println(a, b, c)
}
输出:false true false
解析:类型断言语法:i.(Type)
,其中 i
是接口,Type
是类型或接口。编译时会自动检测 i
的动态类型与 Type
是否一致。但是,如果动态类型不存在,则断言总是失败。
type info struct {
result int
}
func work() (int,error) {
return 13,nil
}
func main() {
var data info
data.result, err := work()
fmt.Printf("info: %+v\n",data)
}
解析:编译失败。non-name data.result on left side of :=
。不能使用短变量声明设置结构体字段值。
修复代码:
func main() {
var data info
var err error
data.result, err = work() //ok
if err != nil {
fmt.Println(err)
return
}
fmt.Println(data)
}
type People struct {
name string `json:"name"`
}
func main() {
js := `{
"name":"seekload"
}`
var p People
err := json.Unmarshal([]byte(js), &p)
if err != nil {
fmt.Println("err: ", err)
return
}e x
fmt.Println(p)
}
输出:struct field name has json tag but is not exported
解析:结构体访问控制,因为 name
首字母是小写,导致其他包不能访问。
type T struct {
ls []int
}
func foo(t T) {
t.ls[0] = 100
}
func main() {
var t = T{
ls: []int{1, 2, 3},
}
foo(t)
fmt.Println(t.ls[0])
}
输出:100
解析:调用 foo()
函数时虽然是传值,但 foo()
函数中,字段 ls
依旧可以看成是指向底层数组的指针。
type X struct {}
func (x *X) test() {
println(x)
}
func main() {
var a *X
a.test()
X{}.test()
}
解析:X{}
是不可寻址的,不能直接调用方法。在方法中,指针类型的接收者必须是合法指针(包括 nil
),或能获取实例地址。
type T struct {
x int
y *int
}
func main() {
i := 20
t := T{10,&i}
p := &t.x // 先计算 t.x ,然后得到 t.x 的地址
*p++ // 先获取 p 的值,然后累加
*p-- // 同上,做递减
t.y = p
fmt.Println(*t.y) // 取值
}
输出:10
解析:递增运算符 ++
和递减运算符 --
的优先级低于解引用运算符 *
和取址运算符 &
,解引用运算符和取址运算符的优先级低于选择器 .
中的属性选择操作符。
type N int
func (n *N) test(){
fmt.Println(*n)
}
func main() {
var n N = 10
p := &n
n++
f1 := n.test
n++
f2 := p.test
n++
fmt.Println(n)
f1()
f2()
}
输出:13 13 13
解析:当目标方法的接收者是指针类型时,那么被复制的就是指针。
panic
package main
type T struct{}
func (*T) foo() {
}
func (T) bar() {
}
type S struct {
*T
}
func main() {
s := S{}
_ = s.foo
s.foo()
_ = s.bar
}
解析:_ = s.bar
行。因为 s.bar
将被展开为 (*s.T).bar
,而 s.T
是个空指针,解引用会 panic
。
panic
func main() {
nil := 123
fmt.Println(nil)
var _ map[string]int = nil
}
解析:var _ map[string]int = nil
行。当前作用域中,预定义的 nil
被覆盖,此时 nil
是 int
类型值,不能赋值给 map
类型。
17.1
type T struct {
n int
}
func main() {
ts := [2]T{}
for i, t := range ts {
switch i {
case 0:
t.n = 3
ts[1].n = 9
case 1:
fmt.Print(t.n, " ")
}
}
fmt.Print(ts)
}
输出:0 [{0} {9}]
解析:此时 for-range
使用的是数组 ts
的副本,所以 t.n = 3
的赋值操作不会影响原数组。
17.2
type T struct {
n int
}
func main() {
ts := [2]T{}
for i, t := range &ts {
switch i {
case 0:
t.n = 3
ts[1].n = 9
case 1:
fmt.Print(t.n, " ")
}
}
fmt.Print(ts)
}
输出:9 [{0} {9}]
解析:for-range
循环中的循环变量 t
是原数组元素的副本。如果数组元素是结构体值,则副本的字段和原数组字段是两个不同的值。
17.3
type T struct {
n int
}
func main() {
ts := [2]T{}
for i := range ts[:] {
switch i {
case 0:
ts[1].n = 9
case 1:
fmt.Print(ts[i].n, " ")
}
}
fmt.Print(ts)
}
输出:9 [{0} {9}]
解析:for-range
切片时使用的是切片的副本,但不会复制底层数组,所以此副本切片与原数组共享底层数组。
2019.10.16
ngx_accept_disabled = ngx_cycle->connection_n / 8
- ngx_cycle->free_connection_n;
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS;
} else {
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
nginx
使用一个叫ngx_accept_disabled
的变量来控制是否去竞争accept_mutex
锁。在第一段代码中,计算ngx_accept_disabled
的值,这个值是nginx
单进程的所有连接总数的八分之一,减去剩下的空闲连接数量,得到的这个ngx_accept_disabled
有一个规律,当剩余连接数小于总连接数的八分之一时,其值才大于0
,而且剩余的连接数越小,这个值越大。
再看第二段代码,当ngx_accept_disabled
大于0
时,不会去尝试获取accept_mutex
锁,并且将ngx_accept_disabled
减1,于是,每次执行到此处时,都会去减1
,直到小于0
。不去获取accept_mutex
锁,就是等于让出获取连接的机会,很显然可以看出,当空余连接越少时,ngx_accept_disable
越大,于是让出的机会就越多,这样其它进程获取锁的机会也就越大。不去accept
,自己的连接就控制下来了,其它进程的连接池就会得到利用,这样,nginx
就控制了多进程间连接的平衡了。
CLOSED 表示socket连接没被使用。
LISTENING 表示正在监听进入的连接。
SYN_SENT 表示正在试着建立连接。
SYN_RECEIVED 进行连接初始同步。
ESTABLISHED 表示连接已被建立。
CLOSE_WAIT 表示远程计算器关闭连接,正在等待socket连接的关闭。
FIN_WAIT_1 表示socket连接关闭,正在关闭连接。
CLOSING 先关闭本地socket连接,然后关闭远程socket连接,最后等待确认信息。
LAST_ACK 远程计算器关闭后,等待确认信号。
FIN_WAIT_2 socket连接关闭后,等待来自远程计算器的关闭信号。
TIME_WAIT 连接关闭后,等待远程计算器关闭重发。
Python2
返回列表;
Python3
返回迭代器,节省内存
func main() {
i := -5
j := +5
fmt.Printf("%+d %+d", i, j)
}
解析:%d
表示输出十进制数字,+
表示输出数值的符号,不表示取反。
func main() {
str := "hello"
str[0] = 'x'
fmt.Println(str)
}
解析:编译出错,Go
语言中的字符串是只读的。
func main() {
i := 65
fmt.Println(string(i))
}
解析:UTF-8
编码中,十进制数字 65
对应的符号是 A
。
type ConfigOne struct {
Daemon string
}
func (c *ConfigOne) String() string {
return fmt.Sprintf("print: %v", c)
}
func main() {
c := &ConfigOne{}
c.String()
}
解析:出现运行时错误。如果类型实现 String()
方法,当格式化输出时会自动使用 String()
方法。
上面这段代码是在该类型的 String()
方法内使用格式化输出,导致递归调用,最后抛错。
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
if hasattr(Parent, 'x'):
print(getattr(Parent, 'x'))
setattr(Parent, 'x',3)
print(getattr(Parent,'x'))
hasattr(object,name)
函数:判断一个对象里面是否有name
属性或者name
方法,返回bool
值,有name
属性(方法)返回True
,否则返回False
。
getattr(object, name[,default])
函数:获取对象object
的属性或者方法,如果存在则打印出来,如果不存在,打印默认值,默认值可选。
注意:如果返回的是对象的方法,则打印结果是:方法的内存地址,如果需要运行这个方法,可以在后面添加括号()
。
setattr(object, name, values)
函数:给对象的属性赋值,若属性不存在,先创建再赋值。
tcpdump
默认只截取前 96
字节的内容。
要想截取所有的报文内容,可以使用-s number
,number
就是要截取的报文字节数.
如果是0
的话,表示截取报文全部内容。
-n
表示不要解析域名,直接显示 ip
;
-nn
不要解析域名和端口;
-X
同时用 hex
和 ascii
显示报文的内容;
-XX
同 -X,但同时显示以太网头部;
-S
显示绝对的序列号(sequence number
),而不是相对编号;
-i any
监听所有的网卡;
-v, -vv, -vvv
显示更多的详细信息;
-c number
截取 number
个报文,然后结束;
-A
只使用 ascii
打印报文的全部数据,不要和 -X
一起使用。截取 http
请求的时候可以用 sudo tcpdump -nSA port 80
host
: 过滤某个主机的数据报文tcpdump host 1.2.3.4
src, dst
: 过滤源地址和目的地址tcpdump src 2.3.4.5
tcpdump dst 3.4.5.6
net
: 过滤某个网段的数据,CIDR
模式tcpdump net 1.2.3.0/24
proto
: 过滤某个协议的数据,支持 tcp
, udp
和 icmp
。使用的时候可以省略 proto
关键字。tcpdump icmp
port
: 过滤通过某个端口的数据报tcpdump port 3389
src/dst, port, protocol
: 结合三者tcpdump src port 1025 and tcp
tcpdump udp and src port 53
port
范围tcpdump portrange 21-23
tcpdump less 32
tcpdump greater 128
tcpdump > 32
tcpdump <= 128
常用:AND, &&, OR, ||, not, !
10.5.2.3
,目的端口是 3389
的数据报tcpdump -nnvS src 10.5.2.3 and dst port 3389
192.168
网段到 10
或者 172.16
网段的数据报tcpdump -nvX src net 192.168.0.0/16 and dat net 10.0.0.0/8 or 172.16.0.0/16
Mars
或者 Pluto
发出的数据报,并且目的端口不是 22
tcpdump -vv src mars or pluto and not dat port 22
tcpdump -w capture_file.pcap port 80
tcpdump -nXr capture_file.pcap host web30
21:27:06.995846 IP (tos 0x0, ttl 64, id 45646, offset 0, flags [DF], proto TCP (6), length 64)
192.168.1.106.56166 > 124.192.132.54.80: Flags [S], cksum 0xa730 (correct), seq 992042666, win 65535, options [mss 1460,nop,wscale 4,nop,nop,TS val 663433143 ecr 0,sackOK,eol], length 0
21:27:07.030487 IP (tos 0x0, ttl 51, id 0, offset 0, flags [DF], proto TCP (6), length 44)
124.192.132.54.80 > 192.168.1.106.56166: Flags [S.], cksum 0xedc0 (correct), seq 2147006684, ack 992042667, win 14600, options [mss 1440], length 0
21:27:07.030527 IP (tos 0x0, ttl 64, id 59119, offset 0, flags [DF], proto TCP (6), length 40)
192.168.1.106.56166 > 124.192.132.54.80: Flags [.], cksum 0x3e72 (correct), ack 2147006685, win 65535, length 0
192.168.1.106.56166 > 124.192.132.54.80
:源地址 ip
是 192.168.1.106
,源端口是 56166
,目的地址是 124.192.132.54
,目的端口是 80
。 >
符号代表数据的方向。
常见的 TCP
报文的 Flags
:
[S]
: SYN
(开始连接)[.]
: 没有 Flag
[P]
: PSH
(推送数据)[F]
: FIN
(结束连接)[R]
: RST
(重置连接)[S.]
: SYN-ACK
(SYN
报文的应答报文)2019.07.13
含有defer
的函数,都会在该函数返回前,调用defer
函数,即便外部函数出现panic
异常,也能执行。
defer DeferredFunc() // 这类的函数称为延迟调用函数(Deferred Function)
func example() {
defer func() {
fmt.Println("later")
}
fmt.Println("first")
}
先打印first
,然后在example()
返回前,打印later
。
defer
的作用域只局限于包围它的函数中。
defer
的调用是存在一个栈中,按照后进先出的顺序执行。
func NewFile(path string) (c *Config, err error) {
c = &Config{}
file, err := os.Open(path)
if err != nil {
return
}
defer file.Close()
err = json.NewDecoder(file).Decode(c)
if err != nil {
// err != nil时,执行file.Close()
return
}
// 执行file.Close()
return
}
不管NewFile()
是否返回错误,在return
前都会执行file.Close()
这个延迟函数。
panic
中恢复defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
panic("oops!")
在defer
中通过recover
捕获异常,然后对异常进行处理。
函数不会因为异常而终止。
func example() {
num := 42
defer func() {
fmt.Println(num)
}()
num = 13
}
延迟调用函数能够获取外围函数中局部变量的最终状态,所以上述的结果是13
。
func count(i int) (n int) {
defer func(i int) {
n = n + i // (3)
}(i) // (1)
i = i * 2 // (2)
n = i // (2)
return n
}
调用count(10)
时候,上面函数的结果是30
,其中值的变化顺序是:
(1) i = 10
--> (2) i = 20; n = i
--> (3) n = 20 + 10
--> (4) return n
延迟调用函数在i
传入之前就记录了i
的值10
,在return
前执行了延迟调用函数,其结果是n = 20; i = 10
的加和。
(1) 没有指针作为接收者
type Car struct {
model string
}
func (c Car) PrintModel() {
fmt.Println(c.model)
}
func main() {
c := Car{model: "DeLorean DMC-12"}
defer c.PrintModel()
c.model = "Chevrolet Impala"
}
结果为DeLorean DMC-12
。
(2) 指针作为接收者
type Car struct {
model string
}
func (c *Car) PrintModel() {
fmt.Println(c.model)
}
func main() {
c := Car{model: "DeLorean DMC-12"}
defer c.PrintModel()
c.model = "Chevrolet Impala"
}
结果为Chevrolet Impala
。
(1)中结果是因为接收者在声明时候拷贝了值,其执行defer c.PrintModel()
会使用拷贝的值;
(2)中的结果是因为接收者是指针对象,虽然会产生新的指针变量,但是指向的地址仍然是原地址,即作用于同一对象中。
说明:不要以为唯一索引影响了insert
速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
join
。需要join
的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。说明:即使双表join
也要注意表索引、SQL
性能。
varchar
字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20
的索引,区分度会高达90%
以上,可以使用count(distinct left(列名, 索引长度))/count(*)
的区分度来确定。
说明:索引文件具有B Tree
的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。
order by
的场景,请注意利用索引的有序性。order by
最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort
的情况,影响查询性能。正例:where a=? and b=? order by c
; 索引:a_b_c
反例:索引中有范围查找,那么索引有序性无法利用,如:where a>10 order by b
; 索引a_b
无法排序。
说明:如果一本书需要知道第11章是什么标题,会翻开第11章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。
正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用explain
的结果,extra
列会出现:using index
。
说明:MySQL
并不是跳过offset
行,而是取offset+N
行,然后返回放弃前offset
行,返回N
行,那当offset
特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL
改写。
正例:先快速定位需要获取的id
段,然后再关联: SELECT a.* FROM 表1 a, (select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
SQL
性能优化的目标:至少要达到 range
级别,要求是 ref
级别,如果可以是 consts
最好。 说明:consts
单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
ref
指的是使用普通的索引(normal index
)。
range
对索引进行范围检索。
反例:explain
表的结果,type=index
,索引物理文件全扫描,速度非常慢,这个index
级别比较range
还低,与全表扫描是小巫见大巫。
说明:存在非等号和等号混合时,在建索引时,请把等号条件的列前置。如:where c>? and d=?
那么即使c
的区分度更高,也必须把d
放在索引的最前列,即索引idx_d_c
。
正例:如果where a=? and b=?
,如果a
列的几乎接近于唯一值,那么只需要单建idx_a
索引即可。
【推荐】防止因字段类型不同造成的隐式转换,导致索引失效。
【参考】创建索引时避免有如下极端误解:
宁滥勿缺。认为一个查询就需要建一个索引。
宁缺勿滥。认为索引会消耗空间、严重拖慢更新和新增速度。
抵制惟一索引。认为业务的惟一性一律需要在应用层通过“先查后插”方式解决。
func main() {
runtime.GOMAXPROCS(1)
int_chan := make(chan int, 1)
string_chan := make(chan string, 1)
int_chan <- 1
string_chan <- "hello"
select {
case value := <-int_chan:
fmt.Println(value)
case value := <-string_chan:
panic(value)
}
}
解析:可能触发异常,也可能不会触发异常。
channel
特性说明给一个 nil channel
发送数据,造成永远阻塞;
从一个 nil channel
接收数据,造成永远阻塞;
给一个已经关闭的 channel
发送数据,引起 panic
;
从一个已经关闭的 channel
接收数据,如果缓冲区中为空,则返回一个零值,缓冲区不为空,返回已缓冲数据;
无缓冲的channel
是同步的,而有缓冲的channel
是非同步的。
func main() {
ch := make(chan int, 100)
// A
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
}()
// B
go func() {
for {
a, ok := <-ch
if !ok {
fmt.Println("close")
return
}
fmt.Println("a: ", a)
}
}()
close(ch)
fmt.Println("ok")
time.Sleep(time.Second * 10)
}
解析:当 A
协程还没起时,主协程已经将 channel
关闭了,当 A
协程往关闭的 channel
发送数据时会 panic
,panic: send on closed channel
。
func Stop(stop <-chan bool) {
close(stop)
}
解析:有方向的 channel
不可以被关闭。
func main() {
var ch chan int
select {
case v, ok := <-ch:
println(v, ok)
default:
println("default")
}
}
输出:default
。
解析:ch
为 nil
,读写都会阻塞。
var o = fmt.Print
func main() {
c := make(chan int, 1)
for range [3]struct{}{} {
select {
default:
o(1)
case <-c:
o(2)
c = nil
case c <- 1:
o(3)
}
}
}
输出:321
解析:第一次循环,写操作已经准备好,执行 o(3)
,输出 3
;第二次,读操作准备好,执行 o(2)
,输出 2
并将 c
赋值为 nil
;第三次,由于 c
为 nil
,走的是 default
分支,输出 1
。
type data struct {
sync.Mutex
}
func (d data) test(s string) {
d.Lock()
defer d.Unlock()
for i:=0;i<5 ;i++ {
fmt.Println(s,i)
time.Sleep(time.Second)
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
var d data
go func() {
defer wg.Done()
d.test("read")
}()
go func() {
defer wg.Done()
d.test("write")
}()
wg.Wait()
}
解析:锁失效。将 Mutex
作为匿名字段时,相关的方法必须使用指针接收者,否则会导致锁机制失效。
修复代码:
type data struct {
*sync.Mutex // 嵌入 *Mutex 来避免复制的问题,但是需要初始化
}
func (d data) test(s string) { // 值方法
d.Lock()
defer d.Unlock()
for i := 0; i < 5; i++ {
fmt.Println(s, i)
time.Sleep(time.Second)
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
d := data{new(sync.Mutex)} // 初始化
go func() {
defer wg.Done()
d.test("read")
}()
go func() {
defer wg.Done()
d.test("write")
}()
wg.Wait()
}
for range
和数组的取值方式func main() {
slice := []int{0,1,2,3}
m := make(map[int]*int)
for key,val := range slice {
m[key] = &val
}
for k,v := range m {
fmt.Println(k,"->",*v)
}
}
输出:
0 -> 3
1 -> 3
2 -> 3
3 -> 3
解析:for range
循环时候,会创建每个元素的副本,而不是每个元素的引用,所以m[key] = &val
取得的是变量val
的地址,所以最后map
中的所有元素的值都是变量val
的地址,因为最后val
被赋值为3
,所以输出都是3
。
正确的写法:
func main() {
slice := []int{0,1,2,3}
m := make(map[int]*int)
for key,val := range slice {
value := val
m[key] = &value
}
for k,v := range m {
fmt.Println(k,"===>",*v)
}
}
func main() {
m := map[int]string{0:"zero",1:"one"}
for k,v := range m {
fmt.Println(k,v)
}
}
输出:
0 zero
1 one
// 或者
1 one
0 zero
解析:map
的输出是无序的。
func main() {
v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}
}
解析:不会出现死循环,能正常结束。循环次数在循环开始前就已经确定,循环内改变切片的长度,不影响循环次数。
func main() {
var m = [...]int{1, 2, 3}
for i, v := range m {
go func() {
fmt.Println(i, v)
}()
}
time.Sleep(time.Second * 3)
}
输出:
2 3
2 3
2 3
解析:for range
使用短变量声明 (:=)
的形式迭代变量,需要注意的是,变量 i
、v
在每次循环体中都会被重用,而不是重新声明。
各个 goroutine
中输出的 i、v 值都是 for range
循环结束后的 i
、v
最终值,而不是各个 goroutine
启动时的i
、v
值。可以理解为闭包引用,使用的是上下文环境的值。
两种可行的修正方法:
a. 使用函数传递
for i, v := range m {
go func(i,v int) {
fmt.Println(i, v)
}(i,v)
}
b. 使用临时变量保留当前值
for i, v := range m {
i := i // 这里的 := 会重新声明变量,而不是重用
v := v
go func() {
fmt.Println(i, v)
}()
}
func main() {
var a = [5]int{1, 2, 3, 4, 5}
var r [5]int
for i, v := range a {
if i == 0 {
a[1] = 12
a[2] = 13
}
r[i] = v
}
fmt.Println("r = ", r)
fmt.Println("a = ", a)
}
输出:
r = [1 2 3 4 5]
a = [1 12 13 4 5]
解析:range
表达式是副本参与循环,就是说例子中参与循环的是 a
的副本,而不是真正的 a
。
修复写法:
func main() {
var a = [5]int{1, 2, 3, 4, 5}
var r [5]int
for i, v := range &a {
if i == 0 {
a[1] = 12
a[2] = 13
}
r[i] = v
}
fmt.Println("r = ", r)
fmt.Println("a = ", a)
}
输出:
r = [1 12 13 4 5]
a = [1 12 13 4 5]
func main() {
var a = []int{1, 2, 3, 4, 5}
var r [5]int
for i, v := range a {
if i == 0 {
a[1] = 12
a[2] = 13
}
r[i] = v
}
fmt.Println("r = ", r)
fmt.Println("a = ", a)
}
输出:
r = [1 12 13 4 5]
a = [1 12 13 4 5]
解析:这的 a
是一个切片,切片在 Go
的内部结构有一个指向底层数组的指针,当 range
表达式发生复制时,副本的指针依旧指向原底层数组,所以对切片的修改都会反应到底层数组上,所以通过 v
可以获得修改后的数组元素。
type Foo struct {
bar string
}
func main() {
s1 := []Foo{
{"A"},
{"B"},
{"C"},
}
s2 := make([]*Foo, len(s1))
for i, value := range s1 {
s2[i] = &value
}
fmt.Println(s1[0], s1[1], s1[2])
fmt.Println(s2[0], s2[1], s2[2])
}
输出:
{A} {B} {C}
&{C} &{C} &{C}
解析:for range
使用短变量声明(:=
)的形式迭代变量时,变量 i
、value
在每次循环体中都会被重用,而不是重新声明。所以 s2
每次填充的都是临时变量 value
的地址,而在最后一次循环中,value 被赋值为
{c}。因此,
s2输出的时候显示出了三个
&{c}`。
counter
的输出值func main() {
var m = map[string]int{
"A": 21,
"B": 22,
"C": 23,
}
counter := 0
for k, v := range m {
if counter == 0 {
delete(m, "A")
}
counter++
fmt.Println(k, v)
}
fmt.Println("counter is ", counter)
}
输出:2
或3
解析:for range map
是无序的。
func main() {
x := []string{"a", "b", "c"}
for v := range x {
fmt.Print(v)
}
}
输出:0 1 2
func main() {
var k = 9
for k = range []int{} {}
fmt.Println(k)
for k = 0; k < 3; k++ {
}
fmt.Println(k)
for k = range (*[3]int)(nil) {
}
fmt.Println(k)
}
输出:9 3 2
// a
type person struct {
name string
}
func main() {
var m map[person]int
p := person{"mike"}
fmt.Println(m[p])
}
// b
func main() {
s := make(map[string]int)
delete(s, "h")
fmt.Println(s["h"])
}
输出:都是0
解析:打印一个 map
中不存在的值时,返回元素类型的零值。删除 map
不存在的键值对时,不会报错。
A, B
两处应该怎么修改才能顺利编译func main() {
var m map[string]int //A
m["a"] = 1
if v := m["b"]; v != nil { //B
fmt.Println(v)
}
}
解析:A
处只进行了map
声明,不能直接赋值,需要使用make()
或者字面量的方式直接初始化map
,所以应该改为m := make(map[string]int)
;B
处改为v,k := m["b"]
当 key
为 b
的元素不存在的时候,v
会返回值类型对应的零值,k
返回 false
。
type Math struct {
x, y int
}
var m = map[string]Math{
"foo": Math{2, 3},
}
func main() {
m["foo"].x = 4
fmt.Println(m["foo"].x)
}
解析:编译错误。对于类似 X = Y
的赋值操作,必须知道 X
的地址,才能够将 Y
的值赋给 X
,但 go
中的 map
的 value
本身是不可寻址的。
有两个解决方法:
type Math struct {
x, y int
}
var m = map[string]Math{
"foo": Math{2, 3},
}
func main() {
tmp := m["foo"]
tmp.x = 4
m["foo"] = tmp
fmt.Println(m["foo"].x)
}
type Math struct {
x, y int
}
var m = map[string]*Math{
"foo": &Math{2, 3},
}
func main() {
m["foo"].x = 4
fmt.Println(m["foo"].x)
fmt.Printf("%#v", m["foo"]) // %#v 格式化输出详细信息
}
type Param map[string]interface{}
type Show struct {
*Param
}
func main() {
s := new(Show)
s.Param["day"] = 2
}
解析:map
需要初始化才能使用;指针不支持索引。
修复代码如下:
func main() {
s := new(Show)
p := make(Param)
p["day"] = 2
s.Param = &p
tmp := *s.Param
fmt.Println(tmp["day"])
}
func main() {
m := make(map[string]int,2)
cap(m)
}
解析:使用 cap()
获取 map
的容量。
使用 make
创建 map
变量时可以指定第二个参数,不过会被忽略;
cap()
函数适用于数组、数组指针、slice
和 channel
,不适用于 map
,可以使用 len()
返回 map
的元素个数。
type T struct {
n int
}
func main() {
m := make(map[int]T)
m[0].n = 1
fmt.Println(m[0].n)
}
解析:编译错误。map[key]struct
中 struct
是不可寻址的,所以无法直接赋值。
(.*)
是贪婪匹配,会把满足正则的尽可能多的往后匹配;
(.*?)
是非贪婪匹配,会把满足正则的尽可能少匹配。
HTTP Keep-Alive模式,客户端如何判断服务器的数据已经发生完成
使用消息首部字段Conent-Length
:Conent-Length
表示实体内容长度,客户端(服务器)可以根据这个值来判断数据是否接收完成。
使用消息首部字段Transfer-Encoding
:当客户端向服务器请求一个静态页面或者一张图片时,服务器可以很清楚的知道内容大小,然后通过Content-length
消息首部字段告诉客户端需要接收多少数据。
但是如果是动态页面等时,服务器是不可能预先知道内容大小,这时就可以使用Transfer-Encoding
:chunk
模式来传输数据了。即如果要一边产生数据,一边发给客户端,服务器就需要使用Transfer-Encoding: chunked
这样的方式来代替Content-Length
。
chunk
编码将数据分成一块一块的发生。Chunked
编码将使用若干个Chunk
串连而成,由一个标明长度为0
的chunk
标示结束。每个Chunk
分为头部和正文两部分,头部内容指定正文的字符总数(十六进制的数字)和数量单位(一般不写),正文部分就是指定长度的实际内容,两部分之间用回车换行(CRLF
)隔开。在最后一个长度为0
的Chunk
中的内容是称为footer
的内容,是一些附加的Header
信息(通常可以直接忽略)。
import collections
class MyDict(collections.UserDict):
def __missing__(self, key):
if isinstance(key, str):
raise KeyError(key)
return self[str(key)]
def __contains__(self, key):
return str(key) in self.data
def __setitem__(self, key, item):
self.data[str(key)] = item
def __getattr__(self, key):
return self.data[str(key)]
旧:
a = [1, 2, 3, 4, 5]
n = len(a)
if n > 4:
print(n)
错误:
a = [1, 2, 3, 4, 5]
if (n = len(a)) > 4: # SyntaxError: invalid syntax
print(n)
无法在if
语句中进行赋值比较。
新:
新语法通过:=
让表达式内部进行变量赋值变成可能。
a = [1, 2, 3, 4, 5]
if (n := len(a)) > 4:
print(n)
上下文即程序运行的环境状态。
上下文管理器的常用于一些资源的操作,需要在资源的获取与释放相关的操作。
Python
的上下文管理协议则是__enter__()
和__exit__()
any()
:只要迭代器中有一个元素为真就为真;
all()
:迭代器中所有的判断项返回都是真,结果才为真。
def super(cls, inst):
mro = inst.__class__.mro() # inst负责生成MRO的list
return mro[mro.index(cls) + 1] # 定位到当前cls在MRO中的位置,然后后移一位,返回此处的类
EXPLAIN
使用MySQL
提供了一个 EXPLAIN
命令,它可以对 SELECT
语句进行分析,并输出 SELECT
执行的详细信息,以供开发人员针对性优化。
mysql> explain select * from user_info where id = 2\G
*************************** 1. row ***************************
id: 1 # SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符.
select_type: SIMPLE # SELECT 查询的类型.
table: user_info # 查询的是哪个表
partitions: NULL # 匹配的分区
type: const # join 类型
possible_keys: PRIMARY # 此次查询中可能选用的索引
key: PRIMARY # 此次查询中确切使用到的索引
key_len: 8
ref: const # 哪个字段或常数与 key 一起被使用
rows: 1 # 显示此查询一共扫描了多少行,这个是一个估计值
filtered: 100.00 # 表示此查询条件所过滤的数据的百分比
Extra: NULL # 额外的信息
1 row in set, 1 warning (0.00 sec)
比较重要的字段:
select_type
表示了查询的类型, 它的常用取值有:SIMPLE
:表示此查询不包含 UNION
查询或子查询;
PRIMARY
:表示此查询是最外层的查询;
UNION
:表示此查询是 UNION
的第二或随后的查询;
DEPENDENT UNION
:UNION
中的第二个或后面的查询语句, 取决于外面的查询;
UNION RESULT
:UNION
的结果;
SUBQUERY
:子查询中的第一个 SELECT
;
DEPENDENT SUBQUERY
:子查询中的第一个 SELECT
, 取决于外面的查询. 即子查询依赖于外层查询的结果。
table
,表示查询涉及的表或衍生表。
type
字段,判断此次查询是全表扫描还是索引扫描等。常用取值有:
system
:表中只有一条数据,这个类型是特殊的 const
类型;
const
:针对主键或唯一索引的等值查询扫描,最多只返回一行数据。const
查询速度非常快, 因为它仅仅读取一次即可。
eq_ref
:此类型通常出现在多表的 join
查询,表示对于前表的每一个结果,都只能匹配到后表的一行结果。并且查询的比较操作通常是 =
, 查询效率较高。
ref
:此类型通常出现在多表的 join
查询,针对于非唯一或非主键索引,或者是使用了最左前缀规则索引的查询。
range
:表示使用索引范围查询,通过索引字段范围获取表中部分数据记录。这个类型通常出现在 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN()
操作中。
当 type
是 range
时, 那么 EXPLAIN
输出的 ref
字段为 NULL
,并且 key_len
字段是此次查询中使用到的索引的最长的那个。
index
:表示全索引扫描(full index scan
),和 ALL
类型类似,只不过 ALL
类型是全表扫描,而 index
类型则仅仅扫描所有的索引,而不扫描数据。
index
类型通常出现在:所要查询的数据直接在索引树中就可以获取到,而不需要扫描数据。当是这种情况时,Extra
字段 会显示 Using index
。
ALL
:表示全表扫描, 这个类型的查询是性能最差的查询之一。
不同的 type
类型的性能关系如下:ALL < index < range ~ index_merge < ref < eq_ref < const < system
key
是 MySQL
在当前查询时所真正使用到的索引。
rows
是 MySQL
查询优化器根据统计信息,估算 SQL
要查找到结果集需要扫描读取的数据行数。
在访问数据库时,应该只请求需要的行和列。请求多余的行和列会消耗 MySQL
服务器的CPU
和内存资源,并增加网络开销。
例如在处理分页时,应该使用 LIMIT
限制 MySQL
只返回一页的数据,而不是向应用程序返回全部数据后,再由应用程序过滤不需要的行。
当一行数据被多次使用时可以考虑将数据行缓存起来,避免每次使用都要到 MySQL
查询。
避免使用 SELECT *
这种方式进行查询,应该只返回需要的列。
查询数据的方式有全表扫描、索引扫描、范围扫描、唯一索引查询、常数引用等。这些查询方式,速度从慢到快,扫描的行数也是从多到少。可以通过EXPLAIN
语句中的type
列反应查询采用的是哪种方式。
通常可以通过添加合适的索引改善查询数据的方式,使其尽可能减少扫描的数据行,加快查询速度。
例如,当发现查询需要扫描大量的数据行但只返回少数的行,那么可以考虑使用覆盖索引,即把所有需要用到的列都放到索引中。这样存储引擎无须回表获取对应行就可以返回结果了。
可以将一个大查询切分成多个小查询执行,每个小查询只完成整个查询任务的一小部分,每次只返回一小部分结果。
删除旧的数据是一个很好的例子。如果只用一条语句一次性执行一个大的删除操作,则可能需要一次锁住很多数据,占满整个事务日志,耗尽系统资源、阻塞很多小的但重要的查询。
将一个大的删除操作分解成多个较小的删除操作可以将服务器上原本一次性的压力分散到多次操作上,尽可能小地影响 MySQL
性能,减少删除时锁的等待时间。同时也减少了 MySQL
主从复制的延迟。
另一个例子是分解关联查询,即对每个要关联的表进行单表查询,然后将结果在应用程序中进行关联。下面的这个查询:
SELECT * FROM tag
JOIN tag_post ON tag_post.tag_id=tag.id JOIN post ON tag_post.post_id=post.idWHERE tag.tag = 'mysql';
可以分解成下面这些查询来代替:
SELECT * FROM tag WHERE tag = 'mysql';SELECT * FROM tag_post WHERE tag_id = 1234;SELECT * FROM post WHERE post.id in (123,456,567,9098,8904);
将一个关联查询拆解成多个单表查询有如下优点:
让缓存的效率更高。如果缓存的是关联查询的结果,那么其中的一个表发生变化,整个缓存就失效了。而拆分后,如果只是某个表很少的改动,并不会破坏所有的缓存;
可以减少锁的竞争;
更容易对数据库进行拆分,更容易做到高性能和可扩展;
查询本身的效率也有可能会有所提升。例如上面用IN()
代替关联查询比随机的关联更加高效。
MIN()
和MAX()
添加索引可以优化MIN()
和MAX()
表达式。
例如,要找到某一列的最小值,只需要查询对应B Tree
索引的最左端的记录即可。
类似的,如果要查询列中的最大值,也只需要读取B Tree
索引的最后一条记录
对于这种查询,EXPLAIN
中可以看到Select tables optimized away
,表示优化器已经从执行计划中移除了该表,并以一个常数取而代之。
IN()
取代OR
在 MySQL
中,IN()
先将自己列表中的数据进行排序,然后通过二分查找的方式确定列的值是否在IN()
的列表中,这个时间复杂度是O(logn)
。
如果换成OR
操作,则时间复杂度是O(n)
。
所以,对于IN()
的列表中有大量取值的时候,用IN()
替换OR
操作将会更快。
在 MySQL
中,任何一个查询都可以看成是一个关联查询,即使只有一个表的查询也是如此。
MySQL
对任何关联都执行嵌套循环的关联操作,例如对于下面的SQL
语句:
SELECT tbl1.col1, tbl2.col2 FROM tbl1 INNER JOIN tbl2 USING(col3) WHERE tbl1.col1 IN(5, 6);
下面的伪代码表示 MySQL
将如何执行这个查询:
out_iter = iterator over tbl1 where col1 IN(5,6)
outer_row = out_iter.next # 先从第一个表中取出符合条件的所有行
# 在while循环中遍历第一个表结果集的每一行
while outer_row
# 对于第一个表结果集中的每一行,在第二个表中找出符合条件的所有行
inner_iter = iterator over tbl2 where col3 = outer_row.col3
inner_row = inner_iter.next
while inner_row
# 将第一个表的结果列和第二个表的结果列拼装在一起作为结果输出
output[outer_row.col1, inner_row.col2]
inner_row = inner_iter.next
end
# 回溯,再根据第一个表结果集的下一行,继续上面的过程
outer_row = outer_iter.next
end
对于单表查询,那么只需要完成上面外层的基本操作。
优化关联查询,要确保ON
或者USING
子句中的列上有索引,并且在建立索引时需要考虑到关联的顺序。通常来说,只需要在关联顺序中的第二个表的相应列上创建索引。
例如,当表A
和表B
用列c
关联的时候,假设关联的顺序是B
、A
,那么就不需要在B
表的c
列上建立索引。没有用到的索引只会带来额外的负担。
此外,确保任何的GROUP BY
和ORDER BY
中的表达式只涉及到一个表中的列,这样才能使用索引来优化这个过程。
上面提到在 MySQL
中,任何一个查询实质上都是一个关联查询。那么对于子查询或UNION
查询是如何实现关联操作的呢。
对于UNION
查询, MySQL
先将每一个单表查询结果放到一个临时表中,然后再重新读出临时表数据来完成UNION
查询。MySQL
读取结果临时表和普通表一样,也是采用的关联方式。
当遇到子查询时,先执行子查询并将结果放到一个临时表中,然后再将这个临时表当做一个普通表对待。
MySQL
的临时表是没有任何索引的,在编写复杂的子查询和关联查询的时候需要注意这一点。
应该尽量让MySQL
使用索引进行排序。当不能使用索引生成排序结果的时候,MySQL
需要自己进行排序。
如果数据量小于“排序缓冲区”的大小,则MySQL
使用内存进行“快速排序”操作。
如果数据量太大超过“排序缓冲区”的大小,那么MySQL
只能采用文件排序,而文件排序的算法非常复杂,会消耗很多资源。
无论如何排序都是一个成本很高的操作,所以从性能角度考虑,应尽可能避免排序。所以让MySQL
根据索引构造排序结果非常的重要。
MySQL
的子查询实现的非常糟糕。最糟糕的一类查询是WHERE
条件中包含IN()
的子查询语句。
应该尽可能用关联替换子查询,可以提高查询效率。
COUNT()
查询COUNT()
有两个不同的作用:统计某个列值的数量,即统计某列值不为NULL
的个数;统计行数。
当使用COUNT(*)
时,统计的是行数,它会忽略所有的列而直接统计所有的行数。而在括号中指定了一个列的话,则统计的是这个列上值不为NULL
的个数。
可以考虑使用索引覆盖扫描或增加汇总表对COUNT()
进行优化。
处理分页会使用到LIMIT
,当翻页到非常靠后的页面的时候,偏移量会非常大,这时LIMIT
的效率会非常差。
例如对于LIMIT 10000,20
这样的查询,MySQL
需要查询10020
条记录,将前面10000
条记录抛弃,只返回最后的20
条。这样的代价非常高,如果所有的页面被访问的频率都相同,那么这样的查询平均需要访问半个表的数据。
优化此类分页查询的一个最简单的办法就是尽可能地使用索引覆盖扫描,而不是查询所有的列。然后根据需要与原表做一次关联操作返回所需的列。对于偏移量很大的时候,这样的效率会提升非常大。
考虑下面的查询:
SELECT film_id, description FROM sakila.film ORDER BY title LIMIT 50, 5;
如果这个表非常大,那么这个查询最好改写成下面的这样子:
SELECT film.film_id, film.description FROM sakila.film INNER JOIN (SELECT film_id FROM sakila.film ORDER BY title LIMIT 50, 5) AS lim USING (film_id);
注意优化中关联的子查询,因为只查询film_id
一个列,数据量小,使得一个内存页可以容纳更多的数据,这让MySQL
扫描尽可能少的页面。在获取到所需要的所有行之后再与原表进行关联以获得需要的全部列。
LIMIT
的优化问题,其实是OFFSET
的问题,它会导致MySQL
扫描大量不需要的行然后再抛弃掉。
可以借助“书签”的**记录上次取数据的位置,那么下次就可以直接从该“书签”记录的位置开始扫描,这样就避免了使用OFFSET
。
可以把主键当做“书签”使用,例如下面的查询:
SELECT * FROM sakila.rental ORDER BY rental_id DESC LIMIT 20;
假设上面的查询返回的是主键为16049
到16030
的租借记录,那么下一页查询就可以直接从16030
这个点开始:
SELECT * FROM sakila.rental WHERE rental_id < 16030 ORDER BY rental_id DESC LIMIT 20;
该技术的好处是无论翻页到多么后面,其性能都会很好。
此外,也可以用关联到一个冗余表的方式提高LIMIT
的性能,冗余表只包含主键列和需要做排序的数据列。
除非确实需要服务器消除重复的行,否则一定要使用UNION ALL
。
如果没有ALL
关键字,MySQL
会给临时表加上DISTINCT
选项,这会导致对整个临时表的数据做唯一性检查。这样做的代价非常高。
https
协议需要到ca
申请证书,一般免费证书较少,因而需要一定费用。http
是超文本传输协议,信息是明文传输,https
则是具有安全性的ssl
加密传输协议。http
和https
使用的是完全不同的连接方式,用的端口也不一样,前者是80
,后者是443
。http
的连接很简单,是无状态的;HTTPS
协议是由SSL+HTTP
协议构建的可进行加密传输、身份认证的网络协议,比http
协议安全。func main() {
s1 := []int{1, 2, 3}
s2 := []int{4, 5}
s1 = append(s1, s2)
fmt.Println(s1)
}
解析:append
的第二个参数不能直接使用slice
,需要使用...
操作符,将一个切片追加到另一个切片上,或者直接跟上元素append(s1, 1, 2, 3)
。
func main() {
a := []int{7, 8, 9}
fmt.Printf("%+v\n", a)
ap(a)
fmt.Printf("%+v\n", a)
app(a)
fmt.Printf("%+v\n", a)
}
func ap(a []int) {
a = append(a, 10)
}
func app(a []int) {
a[0] = 1
}
输出:
[7, 8, 9]
[7, 8, 9]
[1, 8, 9]
解析:append
导致底层数组重新分配了内存,ap
中的a
的底层数组与外部的a
不是同一个,并未改变外部的。
package main
import (
"fmt"
)
func main() {
a := [5]int{1, 2, 3, 4, 5}
t := a[3:4:4]
fmt.Println(t[0])
}
解析:假设一个底层数组a
的大小是n
,截取切片的操作是a[i: j]
,则获得的切片长度为j - i
,容量为n - i
。如果截取操作符存在第三个参数k
,其是用来限制新切片容量的。
func main() {
var s1 []int
var s2 = []int{}
if __ == nil {
fmt.Println("yes nil")
}else{
fmt.Println("no nil")
}
}
答案:s1
。
解析:nil
切片和 nil
相等,一般用来表示一个不存在的切片;空切片和 nil
不相等,表示一个空的集合。
var a []int
声明nil
切片;
a := []int{}
声明长度和容量都为0
的空切片。
a
、b
、c
的长度和容量分别是多少?func main() {
s := [3]int{1, 2, 3}
a := s[:0]
b := s[:2]
c := s[1:2:cap(s)]
}
答案:a
、b
、c
的长度和容量分别是 0 3
、2 3
、1 2
。
解析:假设一个底层数组a
的大小是n
,截取切片的操作是a[i: j]
,则获得的切片长度为j - i
,容量为n - i
。如果截取操作符存在第三个参数k
,其是用来限制新切片容量的。
func main() {
s1 := []int{1, 2, 3}
s2 := s1[1:]
s2[1] = 4
fmt.Println(s1)
s2 = append(s2, 5, 6, 7)
fmt.Println(s1)
}
输出:
[1 2 4]
[1 2 4]
解析:
Go
中切片底层的数据结构是数组。当使用 s1[1:]
获得切片 s2
,和 s1
共享同一个底层数组,这会导致 s2[1] = 4
语句影响 s1
。
而 append
操作会导致底层数组扩容,生成新的数组,因此追加数据后的 s2
不会影响 s1
。
但是为什么对 s2
赋值后影响的却是 s1
的第三个元素呢?这是因为切片 s2
是从数组的第二个元素开始,s2
索引为 1
的元素对应的是 s1
索引为 2
的元素。
func main() {
fmt.Println([...]int{1} == [2]int{1})
fmt.Println([]int{1} == []int{1})
}
解析:go
中不同类型是不能比较的,而数组长度是数组类型的一部分,所以 […]int{1} 和 [2]int{1}
是两种不同的类型,不能比较;切片是不能比较的。
func change(s ...int) {
s = append(s,3)
}
func main() {
slice := make([]int,5,5)
slice[0] = 1
slice[1] = 2
change(slice...)
fmt.Println(slice)
change(slice[0:2]...)
fmt.Println(slice)
}
输出:
[1 2 0 0 0]
[1 2 3 0 0]
解析:
Go
提供的语法糖...
,可以将 slice
传进可变函数,不会创建新的切片。
第一次调用 change()
时,append()
操作使切片底层数组发生了扩容,原 slice
的底层数组不会改变;
第二次调用 change()
函数时,获得一个新的切片,它的底层数组和原切片底层数组是重合的,不过长度、容量分别是 2、5,所以在 change()
函数中对底层数组的修改会影响到原切片。
var x = []int{2: 2, 3, 0: 1}
func main() {
fmt.Println(x)
}
输出:[1 0 2 3]
解析:字面量初始化切片时候,可以指定索引,没有指定索引的元素会在前一个索引基础之上加一,所以输出 [1 0 2 3]
,而不是 [1 3 2]
。
func main() {
a := [2]int{5, 6}
b := [3]int{5, 6}
if a == b {
fmt.Println("equal")
} else {
fmt.Println("not equal")
}
}
编译出错,Go
中的数组是值类型,可以进行比较,同时数组长度也是数组类型的组成部分,并且Go
语言是强类型语言,所以上面的a, b
不能进行比较,编译出错。
package main
import "fmt"
func main() {
s := make([]int, 3, 9)
fmt.Println(len(s))
s2 := s[4:8]
fmt.Println(len(s2))
}
输出:3 4
解析:从一个基础切片派生出的子切片的长度可能大于基础切片的长度。
panic
package main
func main() {
var m map[int]bool // nil
_ = m[123]
var p *[5]string // nil
for range p {
_ = len(p)
}
var s []int // nil
_ = s[:]
s, s[0] = []int{1, 2}, 9
}
解析:s, s[0] = []int{1, 2}, 9
行。因为左侧的 s[0]
中的 s
为 nil
。
func main() {
var k = 1
var s = []int{1, 2}
k, s[k] = 0, 3
fmt.Println(s[0] + s[1])
}
输出:4
解析:多重赋值按照以下的两个步骤进行:计算等号左边的索引表达式和取址表达式,接着计算等号右边的表达式;赋值。
异步IO
。
gevent
是基于greenlet
实现。通过栈的复制切换来实现不同协程之间的切换。
Python
基于yield
实现协程。
事件循环 (event loop
)。事件循环需要实现两个功能,一是顺序执行协程代码;二是完成协程的调度,即一个协程“暂停”时,决定接下来执行哪个协程。
协程上下文的切换。基本上Python
生成器的 yield
已经能完成切换,Python3
中还有特定语法支持协程切换。
2019.03.27
运行Python
文件程序的时候,Python
解释器将源代码转换为字节码,然后再由Python
解释器来执行这些字节码。
词法分析 --> 语法分析 --> 生成字节码(.pyc
文件) --> 执行
Python
的int
是怎么实现的PyIntObject
结构体表示:
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
PyObject_HEAD
宏中定义的两个属性分别是:
int ob_refcnt;
struct _typeobject *ob_type;
这两个属性是所有Python
对象固有的:
ob_refcnt
:对象的引用计数,与Python
的内存管理机制有关,它实现了基于引用计数的垃圾收集机制ob_type
:用于描述Python
对象的类型信息。PyIntObject
就是一个对C
语言中long
类型的数值的扩展。
出于性能考虑,对于小整数,Python
使用小整数对象池small_ints
缓存了[-5,257)
之间的整数,该范围内的整数在Python
系统中是共享的。
而超过该范围的整数即使值相同,但对象不一定是同一个,如下所示:当a
与b
的值都是10000
,但并不是同一个对象,而值为1
的时候,a
和b
属于同一个对象。
>>> a = 1
>>> b = 1
>>> a is b
True
>>> a = 10000
>>> b = 10000
>>> a is b
False
对于超出了[-5, 257)
之间的其他整数,Python
同样提供了专门的缓冲池,供这些所谓的大整数使用,避免每次使用的时候都要不断的malloc
分配内存带来的效率损耗。这块内存空间就是PyIntBlock
。
struct _intblock {
struct _intblock *next;
PyIntObject objects[N_INTOBJECTS];
};
typedef struct _intblock PyIntBlock;
static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;
这些内存块(PyIntBlock
)通过一个单向链表组织在一起,表头是block_list
,表头始终指向最新创建的PyIntBlock
对象。
PyIntBlock
有两个属性:next
,objects
。next
指针指向下一个PyIntBlock
对象,objects
是一个PyIntObject
数组(最终会转变成单向链表),它是真正用于存储被缓存的PyIntObjet
对象的内存空间。
free_list
单向链表是所有PyIntBlock
内存块中空闲的内存。所有空闲内存通过一个链表组织起来的好处就是在Python
需要新的内存来存储新的PyIntObject
对象时,能够通过free_list
快速获得所需的内存。
创建一个整数对象时,如果它在小整数范围内,就直接从小整数缓冲池中直接返回,如果不在该范围内,就开辟一个大整数缓冲池内存空间:
[intobject.c]
PyObject* PyInt_FromLong(long ival)
{
register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
//[1] :尝试使用小整数对象池
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
v = small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
return (PyObject *) v;
}
#endif
//[2] :为通用整数对象池申请新的内存空间
if (free_list == NULL) {
if ((free_list = fill_free_list()) == NULL)
return NULL;
}
//[3] : (inline)内联PyObject_New的行为
v = free_list;
free_list = (PyIntObject *)v->ob_type;
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
return (PyObject *) v;
}
fill_free_list
就是创建大整数缓冲池内存空间的逻辑,该函数返回一个free_list
链表,当整数对象ival
创建成功后,free_list
表头就指向了v->ob_type
,ob_type
不是所有Python
对象中表示类型信息的字段吗?怎么在这里作为一个连接指针呢?这是Python
在性能与代码优雅之间取中庸之道,对名称的滥用,放弃了对类型安全的坚持。把它理解成指向下一个PyIntObject
的指针即可。
[intobject.c]
static PyIntObject* fill_free_list(void)
{
PyIntObject *p, *q;
// 申请大小为sizeof(PyIntBlock)的内存空间
// block list始终指向最新创建的PyIntBlock
p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
((PyIntBlock *)p)->next = block_list;
block_list = (PyIntBlock *)p;
//:将PyIntBlock中的PyIntObject数组(objects)转变成单向链表
p = &((PyIntBlock *)p)->objects[0];
q = p + N_INTOBJECTS;
while (--q > p)
// ob_type指向下一个未被使用的PyIntObject。
q->ob_type = (struct _typeobject *)(q-1);
q->ob_type = NULL;
return p + N_INTOBJECTS - 1;
}
不同的PyIntBlock
里面的空闲的内存是怎样连接起来构成free_list
的呢?这个秘密放在了整数对象垃圾回收的时候,在PyIntObject
对象的tp_dealloc
操作中可以看到:
[intobject.c]
static void int_dealloc(PyIntObject *v)
{
if (PyInt_CheckExact(v)) {
v->ob_type = (struct _typeobject *)free_list;
free_list = v;
}
else
v->ob_type->tp_free((PyObject *)v);
}
原来PyIntObject
对象销毁时,它所占用的内存并不会释放,而是继续被Python
使用,进而将free_list
表头指向了这个要被销毁的对象上。
随机整数:random.randint(a, b)
,生成[a, b]
区间内的整数;
随机小数:np.random.randn(i)
生成i
个随机小数;
0~1
随机小数:random.random()
;
打乱一个列表:random.shuffle(alist)
。
*args
将一个非键值对的可变参数列表传入函数;
**kwargs
将不定长度的键值对传入一个函数。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.