[译]Go里面的unsafe包详解

unsafe包位置: src/unsafe/unsafe.go

https://gocn.vip/question/371

指针类型:
*类型:普通指针,用于传递对象地址,不能进行指针运算。
unsafe.Pointer:通用指针,用于转换不同类型的指针,不能进行指针运算。
uintptr:用于指针运算,GC 不把 uintptr 当指针,uintptr 无法持有对象。uintptr 类型的目标会被回收。

unsafe.Pointer 可以和 普通指针 进行相互转换。
unsafe.Pointer 可以和 uintptr 进行相互转换。

也就是说 unsafe.Pointer 是桥梁,可以让任意类型的指针实现相互转换,也可以将任意类型的指针转换为 uintptr 进行指针运算。

一般使用流程:
第一步:将结构体->通用指针unsafe.Pointer(struct)->uintptr(通用指针)获取内存段的起始位置start_pos,并记录下来,第二步使用。
第二步:使用start_pos + unsafe.Offsetof(s.b)->将地址转为能用指针unsafe.Pointer(new_pos)->转为普通指针np = (*byte)(p)->赋值 *np = 20
第三步:打印结构体,发现内容发生了更改。

参考:https://www.cnblogs.com/golove/p/5909968.html

golang中slice切片理解总结

首先我们对切片有一个大概的理解,先看一下slice的内部结构,共分三部分,一个是指向底层数组的时候,一个是长度len,另一个就是slice的容量cap了。如cap不足以放在新值的时候,会产生新的内存地址申请。

先看代码

package main

import "fmt"

func main() {
	// 创建一个切片,长度为9,容量为10
	fmt.Println("----- 1.测试切片变量append的影响(未申请新的内存空间)-----")
	a := make([]int, 9, 10)
	fmt.Printf("%p len=%d cap=%d %v\n", a, len(a), cap(a), a)

	// 切片进行append操作,由于原来len(a)长度为9,而cap(a)容量为10,未达到扩展内存的要求,此时新创建的切片变量还指向原来的底层数组,只是数组的后面添加一个新值
	// 此时一共两个切片变量,一个是a,另一个是s4。但共指向的一个内存地址
	s4 := append(a, 4)
	fmt.Printf("%p len=%d cap=%d %v\n\n", s4, len(s4), cap(s4), s4)

	// 测试上面提到的切片变量a和s4共指向同一个内存地址, 发现切片数组的第一个值都为7,而唯一不同的是len的长度,而cap仍为10
	fmt.Println("----- 2.测试切片变量共用一个底层数组(内存地址一样)-----")
	a[0] = 7
	fmt.Printf("%p len=%d cap=%d %v\n", a, len(a), cap(a), a)
	fmt.Printf("%p len=%d cap=%d %v\n\n", s4, len(s4), cap(s4), s4)

	// 切片进行append操作后,发现原来的cap(a)的长度已用完了(因为a和s4共用一个底层数组,你也可以理解为cap(s4)),此时系统需要重新申请原cap*2大小的内存空间,所以cap值为10*2=20,将把原来底层数组的值复制到新的内存地址
	// 此时有两个底层数组,一个是切片变量a和s4指向的数组,另一个就是新的切片变量s4
	fmt.Println("----- 3.测试切片变量append的影响(申请了新的内存空间,内存地址不一样了)-----")
	s4 = append(s4, 5)
	fmt.Printf("%p len=%d cap=%d %v\n\n", s4, len(s4), cap(s4), s4)

	// 注意:原切片未发生任何变化,(打印a[0]=7是因为上面第3段落代码已把默认的0值改为了7)
	fmt.Println("----- 4.测试原切片变量a未发生变化-----")
	fmt.Printf("%p len=%d cap=%d %v\n\n", a, len(a), cap(a), a)
}

运行结果:

----- 1.测试切片变量append的影响(未申请新的内存空间)-----
0x10450030 len=9 cap=10 [0 0 0 0 0 0 0 0 0]
0x10450030 len=10 cap=10 [0 0 0 0 0 0 0 0 0 4]

----- 2.测试切片变量共用一个底层数组-----
0x10450030 len=9 cap=10 [7 0 0 0 0 0 0 0 0]
0x10450030 len=10 cap=10 [7 0 0 0 0 0 0 0 0 4]

----- 3.测试切片变量append的影响(申请了新的内存空间)-----
0x10458050 len=11 cap=20 [7 0 0 0 0 0 0 0 0 4 5]

----- 4.测试原切片变量a未发生变化-----
0x10450030 len=9 cap=10 [7 0 0 0 0 0 0 0 0]

在线运行代码:https://play.golang.org/p/pfxZa8T0H1_g

当对slice进行append的时候,会出现以下情况:
1.如果cap超出了原slice的cap长度,则申请原来cap*2的内存空间,再把原来切片所指的底层数组的值复制一份存储到新申请的内存空间里,这样是两块内存地址了,所有原来slice指的底层数组内容没有变化的。此时两个切片,两个底层数组了,每个切片有自己对应的底层数组。
2.如果cap没超出原slice的cap的话,底层对应的数组是没有变化的,但会产生一个新的slice变量,仍然会指向到原来的底层数组(这个新slice变量有自己的内存地址,我们不用关心它,只需要关心他对应的底层数组就可以了)。这时一共两个切片,但共用一个底层数组。

总结:
1.当对slice进行append的时候,如果原slice可以存放下新增加的值,则不会申请新的内存空间,否则会申请cap*2大小基准申请内存空间。而当cap>=1024的时候,会以cap*1.25倍的大小空间基准申请内存空间。
2.每次切片进行append操作,都会返回一个新的切片变量,这个变量有自己的内存地址。 这点开发者知道这点就可以了,一般不用不用关心这点。
3.当切片进行append后,原切片对应的底层数组是不会变化的,这点见第4点。

请参考golang开发源码包 src/runtime/slice.go 中的growslice函数,可以看到对于cap的增长处理逻辑。

MySQL 5.6新特性MRR

一、什么是MRR 
MMR全称是Multi-Range Read,是MYSQL5.6优化器的一个新特性,在MariaDB5.5也有这个特性。优化的功能在使用二级索引做范围扫描的过程中减少磁盘随机IO和减少主键索引的访问次数。是优化器将随机 IO 转化为顺序 IO 以降低查询过程中 IO 开销的一种手段。(参考:https://blog.csdn.net/caomiao2006/article/details/52205177

二、MRR和没有MRR的区别 

给出一个简单的例子,在innodb表执行下面的查询:

SELECT non_key_column FROM tbl WHERE key_column=x

在没有MRR的情况下,它是这样得到结果的

1.  select key_column, pk_column from tb where key_column=x order  by key_column —>
假设这个结果集是t

2.  for each row in t ;
select non_key_column from tb where pk_column = pk_column_value。(回表)

在有MRR的情况下,它是这样执行的:
1.  select key_column, pk_column from tb where key_column = x  order by key_column —> 假设这个结果集是t
2.  将结果集t放在buffer里面(直到read_rnd_buffer_size这个buffer满了),然后对结果集t按照pk_column排序      —> 假设排序好的结果集是t_sort
3.  select non_key_column from tb where pk_column in (select pk_column from t_sort)

两者的区别主要是两点:

1. 没有MRR的情况下,随机IO增加,因为从二级索引里面得到的索引元组是有序,但是他们在主键索引里面却是无序的,所以每次去主键索引里面得到non_key_column的时候都是随机IO。(如果索引覆盖,那也就没必要利用MRR的特性了,直接从索引里面得到所有数据)

2. 没有MRR的情况下,访问主键索引的次数增加。没有MRR的情况下,二级索引里面得到多少行,那么就要去访问多少次主键索引(也不能完全这样说,因为mysql实现了BNL),而有了MRR的时候,次数就大约减少为之前次数t/buffer_size。

https://blog.csdn.net/liang_0609/article/details/44040357
https://dev.mysql.com/doc/refman/8.0/en/mrr-optimization.html
http://www.percona.com/blog/2012/03/21/multi-range-read-mrr-in-mysql-5-6-and-mariadb-5-5/

When MRR is used, the Extra column in EXPLAIN output shows Using MRR.

参考阅读:
MYSQL之ICP、MRR、BKA(推荐)

MySQL中select中的for update 使用说明(原创)

注意: FOR UPDATE 只能用在事务区块(BEGIN/COMMIT)中才有效。

有时候我们会看到一些select语句后面紧跟一句for update,表示手动加锁的意思,这里我们就介绍一下对for update的理解。对于另一种手动加锁lock in share mode 的区别见:https://blog.csdn.net/liangzhonglin/article/details/65438777

for update是一个排它锁,也就是说对于select … for update 这条语句所在的事务中可以进行任何操作(锁定的是记录,这里指主键id=1的这条记录),但其它事务中只能读取(对这条id=1的记录),不能进行update更新操作。、

一般用在并发场景下,如双11的时候商品数量的更新,如果不添加for update的话,则会出现商品数量被多减的bug。

为了更加方便理解,我们举例说明(事务隔离级别为 RR,这里表tb的id为主键)

事务A:

start transaction;
select * from tb where id=1 for update;
update tb set product_num=product_num-1 where id=1;

此时另一个事务B执行同样的程序语句:

start transaction;
// 下面此时会被阻塞,直到事务A提交或者回滚
select * from tb where id=1 for update; 
update tb set product_num=product_num-1 where id=1;

对于其它主键值非1的不存在这种情况,只要两个事务操作的不是同一条记录就可以正常运行。

推荐阅读:https://blog.csdn.net/u011957758/article/details/75212222