Golang中关于defer语句理解的一道题
示例
我们先看一下源代码
package main
import "fmt"
func f(n int) (r int) {
defer func() {
r += n
recover()
}()
var fc func()
defer fc()
fc = func() {
r += 2
}
return n + 1
}
func main() {
fmt.Println(f(3))
}
大家感觉着打印的值是多少呢?5、9还是7?执行完以后发现是7。好像与多数理解的有些出入,为什么是7,而不是9呢。下面我们来分析一下。
问题分析
对于defer执行的顺序是FIFO这一点都很清楚,我们只需要看搞懂f()函数的执行顺序就行了。
执行顺序为:
- 注册第1个defer 函数, 这里为匿名函数,函数体为 “func() { r += n recover() }()”,内部对应一个函数指针。这里延时函数所有相关的操作一步完成。
- 注册第2个defer函数,函数名为fc(),无函数体, 函数指针为nil(也有可能指针不会空,但指针指向的内容非函数体类型)。由于只是注册操作还未执行,所以并不会产生错误,继续执行。
- 对上面声明的函数进行函数体定义
- 执行return 语句
- 处理defer语句,根据FIFO原则,首先执行第二个函数fc(),发现函数指针为nil,此时会抛出一个恐慌,并继续操作。
- 执行第一个defer函数,对r值进行操作,同时处理恐慌。由于是最后一个defer语句,所以直接将r的值真正返回
可以看到上面第2、3步骤,是先注册的defer函数(函数不存在,所以指针为nil),再进行的函数体定义,导致第二个defer延时函数执行时产生恐慌,后面对函数体的单独定义没有任何意义,大家可以将此函数删除再次运行会发生没有任何问题,直到第一个defer函数对此处理并返回r值结束。
如果打印恐慌错误信息的话,会输出“runtime error: invalid memory address or nil pointer dereference”。
By admin
read more开发者必知redis知识点
剖析Redis常用数据类型对应的数据结构 https://time.geekbang.org/column/article/79159
redis中的COW(Copy-On-Write) https://www.jianshu.com/p/b2fb2ee5e3a0
redis常用有哪些数据类型及每种数据类型的使用场景有哪些 https://www.cnblogs.com/tqlin/p/10478459.html
如果存储一个JSON数据时,选择hash还是string 存储数据? https://segmentfault.com/a/1190000019552836
redis与memcache的区别 https://www.cnblogs.com/JavaBlackHole/p/7726195.html https://blog.csdn.net/qq_34126805/article/details/81748107
redis支持多CPU吗?如何发挥多cpu? https://blog.csdn.net/tanga842428/article/details/52641484
redis为什么这么快?底层设计原理说明 https://blog.csdn.net/chenyao1994/article/details/79491337
redis有哪几种持久化方式?分别有什么不同? https://www.cnblogs.com/chenliangcl/p/7240350.html
redis为单线程还是多线程,为什么这样设计? https://mp.weixin.qq.com/s/sa3aBfqmXlp8ta546E7lBw https://mp.weixin.qq.com/s/jCcwTXr3OUYf7LxUM_BRJA
redis中用到哪些数据结构和算法? https://mp.weixin.qq.com/s/3TU9qxHJyxHJgVDaYXoluA https://www.cnblogs.com/tangtangde12580/p/8302185.html https://www.jianshu.com/p/84faf961ae80
redis中用到的IO多路复用机制如何理解? (https://blog.haohtml.com/archives/18160)
redis的过期策略有哪些? https://www.cnblogs.com/sunsing123/p/11093038.html
redis高可用方案有哪些? https://www.jianshu.com/p/7d5fbf90bcd7
redis中的分布式锁如何理解 http://redis.cn/topics/distlock.html
reids中的RedLock了解过吗?介绍一下 http://redis.cn/topics/distlock.html https://zhuanlan.zhihu.com/p/101599712
高可用性中的一致性算法原理 (https://blog.haohtml.com/archives/19275)
[大厂Redis 性能优化的 13 条军规] https://mp.weixin.qq.com/s/JVTtowoqsIixiaK8WL7wgQ
By admin
read moregolang中有关select的几个知识点
golang中的select语句格式如下
select {
case <-ch1:
// 如果从 ch1 信道成功接收数据,则执行该分支代码
case ch2 <- 1:
// 如果成功向 ch2 信道成功发送数据,则执行该分支代码
default:
// 如果上面都没有成功,则进入 default 分支处理流程
}
可以看到select的语法结构有点类似于switch,但又有些不同。
select里的case后面并不带判断条件,而是一个信道的操作,不同于switch里的case,对于从其它语言转过来的开发者来说有些需要特别注意的地方。
golang 的 select 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作每个case语句里必须是一个IO操作,确切的说,应该是一个面向channel的IO操作。
注:Go 语言的
select
语句借鉴自 Unix 的select()
函数,在 Unix 中,可以通过调用select()
函数来监控一系列的文件句柄,一旦其中一个文件句柄发生了 IO 动作,该select()
调用就会被返回(C 语言中就是这么做的),后来该机制也被用于实现高并发的 Socket 服务器程序。Go 语言直接在语言级别支持select
关键字,用于处理并发编程中通道之间异步 IO 通信问题。
注意:如果 ch1
或者 ch2
信道都阻塞的话,就会立即进入 default
分支,并不会阻塞。但是如果没有 default
语句,则会阻塞直到某个信道操作成功为止。
知识点
- select语句只能用于信道的读写操作
- select中的case条件(非阻塞)是并发执行的,select会选择先操作成功的那个case条件去执行,如果多个同时返回,则随机选择一个执行,此时将无法保证执行顺序。对于阻塞的case语句会直到其中有信道可以操作,如果有多个信道可操作,会随机选择其中一个 case 执行
- 对于case条件语句中,如果存在信道值为nil的读写操作,则该分支将被忽略,可以理解为从select语句中删除了这个case语句
- 如果有超时条件语句,判断逻辑为如果在这个时间段内一直没有满足条件的case,则执行这个超时case。如果此段时间内出现了可操作的case,则直接执行这个case。一般用超时语句代替了default语句
- 对于空的select{},会引起死锁
- 对于for中的select{}, 也有可能会引起cpu占用过高的问题
下面列出每种情况的示例代码
By admin
read moregolang中的sync.Pool对象缓存
参考文章
- Golang 的 协程调度机制 与 GOMAXPROCS 性能调优
- 深入Golang之sync.Pool详解
- golang sync.Pool 分析
- [译] Go: 理解 Sync.Pool 的设计
- 视频 sync.pool对象缓存
知识点
- Pool只是一个缓存,一个缓存,一个缓存。由于生命周期受GC的影响,一定不要用于数据库连接池这类的应用场景,它只是一个缓存。
- golang1.13版本对 Pool 进行了优化,结构体添加了两个字段 victim 和 victimSize。
- 适应于通过复用,降低复杂对象的创建和GC代价的场景
- 因为init()的时候会注册一个PoolCleanup函数,他会在gc时清除掉sync.Pool中的所有的缓存的对象。所以每个sync.Pool的生命周期为两次GC中间时段才有效,可以手动进行gc操作 runtime.GC()
- 由于要保证协程安全,所以会有锁的开销
- 每个Pool都有一个私有池(协程安全)和共享池(协程不安全),其中私有池只有存放一个值。
- 每次Get()时会先从当前P的私有池private中获取( 类似MPG模型中的G)
- 如果获取失败,再从当前P的共享池share中获取
- 如果仍失败,则从其它P中共享池中拿一个,需要加锁保证协程安全
- 如果还失败,则表示所有P中的池(也有可能只是共享池)都为空,则需要New()一个并直接返回(此时不会被放入池中) 每次取值出来后,会从原来存储的地方将该值删除。
By admin
read moregolang 的编程模式之“功能选项”
最近在用go重构iot中的一个服务时,发现库 [email protected] 在初始化消费客户端实现时,实现的极其优雅,代码见 https://github.com/apache/rocketmq-client-go/blob/v2.0.0-rc1/examples/consumer/simple/main.go#L32
c, _ := rocketmq.NewPushConsumer(
consumer.WithGroupName("testGroup"),
consumer.WithNameServer([]string{"127.0.0.1:9876"}),
)
err := c.Subscribe("test", consumer.MessageSelector{}, func(ctx context.Context,
msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
for i := range msgs {
fmt.Printf("subscribe callback: %v n", msgs[i])
}
return consumer.ConsumeSuccess, nil
})
这里创建结构体 rocketmq.NewPushConsumer() 的时候,与我们平时的写法不同并没有写死结构体的字段名和值,而是每个属性都使用了一个函数来实现了,同时也不用考虑属性字段的位置关系,比起以前写kv键值对的方法实在是太灵活了。 我们再看一下其中一个 WithGroupName() 函数的实现方法
func WithGroupName(group string) Option {
return func(opts *consumerOptions) {
if group == "" {
return
}
opts.GroupName = group
}
}
传递的参数为consumerOptions指针类型,这里用到了一个匿名函数,返回的类型为Option(定义 *type Option func(consumerOptions) )。看到这里大概明白实现原理了吧。 为了确认我们的判断,我们再看一下 rocketmq.NewPushConsumer() 函数
func NewPushConsumer(opts ...consumer.Option) (PushConsumer, error) {
return consumer.NewPushConsumer(opts...)
}
这里直接调用了另一个 consumer包里的 NewPushConsumer() 函数,其内容如下( 为了方便理解,在代码里直接加了注释)
By admin
read moreMySQL中的 InnoDB Buffer Pool
一、InnoDB Buffer Pool简介
Buffer Pool是InnoDB引擎内存中的一块区域,主要用来缓存表和索引数据使用。我们知道从内存读取数据要比磁盘读取效率要高的多,这也正是buffer pool发挥的主要作用。一般配置值都比较大,在专用数据库服务器上,大小为物理内存的80%左右。
二、Buffer Pool LRU 算法
Buffer Pool 链表使用优化改良后LRU(最近最少使用)算法进行管理。
整个LRU链表可分为两个子链表,一个是New Sublist,也称为Young列表或新生代,另一个是Old Sublist ,称为Old 列表或老生代。每个子链表都有一个Head和Tail,中间部分是存储Page数据的地方。
当新的Page放入 Buffer Pool 缓存池的时候,会交其Page插入就是两个子链表的交界处,称为midpoint,同时就会有旧的Page被淘汰,整个操作过程都需要对链接进行维护。
Young 链表区存放的数据是经常访问的数据; Old 链表区存放是即将被淘汰的数据;
一个新数据先被插入到midpoint 位置,根据LRU算法访问频率高的Page,会慢慢向Young区Head方向移动。同时访问频率低的Page会从Young区慢慢转到Old 链表的Tail,至到被淘汰。Figure 14.2 Buffer Pool List
算法操作流程如下:
Old区占 Buffer Pool大小的 3/8,此值可以通过 innodb_old_blocks_pct 调整,单位为百分比。
midpoint是young和Old的相交边界,新页插入的位置。
当执行一个read操作时,如select查询语句,这时InnoDB会将页读入到 buffer pool中,最初时会放在midpoint位置(放在Old区的head部分)。(这一点和传统的LRU 算法不一样,并没有放在Old的Tail位置。
如果访问的是Old区的页,会使此页被标记为”young”, 并将其向到 Young区的头部。如果由于用户操作引起的将page放在buffer pool中,当插入到midpoint位置后,立即进行访问操作,会将此page标识为”young”。而如果一个page是由于预读操作而访问的话,则不会产生立即访问这个行为,并且有可能在此page被淘汰之前一次也没有访问过。
随着数据库地运行,新page不断的插入midpoint位置,并不断地被访问到,其page慢慢向Young区的Tail位置移动,同时未被访问的page也会慢慢向Old区的Tail方向移动,最终将其从Old区的尾部被淘汰。
什么是预读?
磁盘读写,并不是按需读取,而是按页读取,一次至少读一页数据(一般是4K),如果未来要读取的数据就在页中,就能够省去后续的磁盘IO,提高效率。在InnoDB中一个page一般为14K,是操作系统的4倍,当然这个值是可以通过参数innodb_page_size调整的。
预读有哪些好处?
数据访问,通常都遵循“集中读写”的原则,使用一些数据,大概率会使用附近的数据,这就是所谓的“局部性原理”,它表明提前加载是有效的,确实能够减少磁盘IO。
三、Page 移动策略
上面我们提到过这里使用的LRU算法和传统的算法有些区别,这里我们主要介绍一下不同点。
如果使用传统算法的话,发现当我们执行一个访问频率很小的 query 时,如一个全表扫描,会把大量的数据插入到midpoint位置,短时间内多次访问的话,会导致这些数据被移动到young区(尽管这些数据以后不再访问了),并多次进行链表修改,不但大量占用了young区的空间,同时还造成了频繁修改young链表的成本,这时就需要一种方法来解决此问题。
参数:innodb_old_blocks_time
单位:毫秒
默认值:1000
此参数主要用来控制Old区的数据移动策略,如果Page在Old区停留innodb_old_blocks_time时间后,再次被访问才会向Young区移动,否则保存位置不变。这样就避免了频繁向Young区移动数据,减少维护LRU链表的成本。例如原来1秒内访问了10次,会修改Young链表10次,现在只修改一次就可以了,大大节省了LRU链表维护成本。
可以看到将此值调整大的话,将直接影响old数据向young移动的难度,从而加速old区淘汰的频繁,保护了Young区内被频繁访问的数据不被淘汰。
四、监控
在SHOW ENGINE INNODB STATUS 里面提供了Buffer Pool一些监控指标,有几个我们需要关注一下:
By admin
read more使用Dockerfile 多阶段构建Golang 应用
docker在开发和运维中使用的场景越来越多,作为开发人员非常有必要了解一些docker的基本知识,而离我们工作中最近的也就是对应用的docker部署编排了,小到一个dockerfile, docker-compse文件的编写,大到k8s的管理。这里我们以 golang应用为例讲解一些Dockerfile的基本用法,在ci/cd中经常用到这些知识。
前提
项目清单:
drwxr-xr-x 9 sxf staff 288 12 31 16:13 .
drwx------@ 17 sxf staff 544 12 31 14:59 ..
-rw-r--r-- 1 sxf staff 14 12 31 16:09 .dockerignore
drwxr-xr-x 14 sxf staff 448 12 31 16:21 .git
-rw-r--r-- 1 sxf staff 467 12 31 16:08 Dockerfile
-rw-r--r-- 1 sxf staff 11 12 31 15:01 README.md
-rw-r--r-- 1 sxf staff 84 12 31 15:51 go.mod
-rw-r--r-- 1 sxf staff 3433 12 31 15:51 go.sum
-rw-r--r-- 1 sxf staff 191 12 31 16:02 main.go
文件说明:
.dockerignore 看名字就知道他的作用是用为忽略一些文件的,它的使用主要是在Dockerfile中使用COPY/ADD 指令时发挥作用。以行为单位,这里共两行,行内容分别是.git 和 README.md
.git 这个是项目Git仓库
Dockerfile 我们文章的重点
go.mod Golang启用了模块管理功能
go.sum 启用模块管理时,会在此文件中记录依赖的三方库
main.go 我们的主要go程序文件,一个简单的webserver应用
项目仓库地址:github.com/cfanbo/democice
By admin
read moreGolang中的goroutine泄漏问题
goroutine作为Go中开发语言中的一大利器,在高并发中发挥着无法忽略的作用。但东西虽好,真正做到用好还是有一些要注意的地方,特别是对于刚刚接触这门开发语言的新手来说,稍有不慎,就极有可能导致goroutine 泄漏。
什么是goroutine Leak
goroutine leak 的意思是go协程泄漏,那么什么又是协程泄漏呢?我们知道每次使用go关键字开启一个gorountine任务,经过一段时间的运行,最终是会结束,从而进行系统资源的释放回收。而如果由于操作不当导致一些goroutine一直处于阻塞状态或者永远运行中,永远也不会结束,这就必定会一直占用系统资源。最球的情况下是随着系统运行,一直在创建此类goroutine,那么最终结果就是程序崩溃或者系统崩溃。这种情况我们一般称为goroutine leak。
出现的问题
先看一段代码:
package main
import (
"fmt"
"math/rand"
"runtime"
"time"
)
func query() int {
n := rand.Intn(100)
time.Sleep(time.Duration(n) * time.Millisecond)
return n
}
// 每次执行此函数,都会导致有两个goroutine处于阻塞状态
func queryAll() int {
ch := make(chan int)
go func() { ch <- query() }()
go func() { ch <- query() }()
go func() { ch <- query() }()
// <-ch
// <-ch
return <-ch
}
func main() {
// 每次循环都会泄漏两个goroutine
for i := 0; i < 4; i++ {
queryAll()
// main()也是一个主groutine
fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())
}
}
运行结果
By admin
read moreMySQL8.0中的跳跃范围扫描优化Skip Scan Range Access Method介绍
在MySQL8.0以前,索引使用规则有一项是索引左前缀,假如说有一个索引idx_abc(a,b,c),能用到索引的情况只有查询条件为a、ab、abc、ac这四种,对于只有字段b的where条件是无法用到这个idx_abcf索引的。这里再强调一下,这里的顺序并不是在where中字段出现的顺序,where b=2 and 1=1 也是可以利用到索引的,只是用到了(a,b)这两个字段
针对这一点, 从MySQL 8.0.13开始引入了一种新的优化方案,叫做 Skip Scan Range,翻译过来的话是跳跃范围扫描。如何理解这个概念呢?我们可以拿官方的SQL示例具体讲一下()
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL, PRIMARY KEY(f1, f2));
INSERT INTO t1 VALUES
(1,1), (1,2), (1,3), (1,4), (1,5),
(2,1), (2,2), (2,3), (2,4), (2,5);
INSERT INTO t1 SELECT f1, f2 + 5 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 10 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 20 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 40 FROM t1;
ANALYZE TABLE t1;
EXPLAIN SELECT f1, f2 FROM t1 WHERE f2 > 40;
我们这里创建了一个t1表,其中主键为(f1,f2),这里是两个字段。执行完这个sql语句后表里有160条记录,执行计划为
By admin
read more一致性哈希算法及其在分布式系统中的应用(推荐)
摘要
本文将会从实际应用场景出发,介绍一致性哈希算法(Consistent Hashing)及其在分布式系统中的应用。首先本文会描述一个在日常开发中经常会遇到的问题场景,借此介绍一致性哈希算法以及这个算法如何解决此问题;接下来会对这个算法进行相对详细的描述,并讨论一些如虚拟节点等与此算法应用相关的话题。
分布式缓存问题
假设我们有一个网站,最近发现随着流量增加,服务器压力越来越大,之前直接读写数据库的方式不太给力了,于是我们想引入Memcached作为缓存机制。现在我们一共有三台机器可以作为Memcached服务器,如下图所示。
很显然,最简单的策略是将每一次Memcached请求随机发送到一台Memcached服务器,但是这种策略可能会带来两个问题:一是同一份数据可能被存在不同的机器上而造成数据冗余,二是有可能某数据已经被缓存但是访问却没有命中,因为无法保证对相同key的所有访问都被发送到相同的服务器。因此,随机策略无论是时间效率还是空间效率都非常不好。
要解决上述问题只需做到如下一点:保证对相同key的访问会被发送到相同的服务器。很多方法可以实现这一点,最常用的方法是计算哈希。例如对于每次访问,可以按如下算法计算其哈希值:
h = Hash(key) % 3
其中Hash是一个从字符串到正整数的哈希映射函数。这样,如果我们将Memcached Server分别编号为0、1、2,那么就可以根据上式和key计算出服务器编号h,然后去访问。
这个方法虽然解决了上面提到的两个问题,但是存在一些其它的问题。如果将上述方法抽象,可以认为通过:
h = Hash(key) % N
这个算式计算每个key的请求应该被发送到哪台服务器,其中N为服务器的台数,并且服务器按照 0 – (N-1) 编号。
这个算法的问题在于容错性和扩展性不好。所谓容错性是指当系统中某一个或几个服务器变得不可用时,整个系统是否可以正确高效运行;而扩展性是指当加入新的服务器后,整个系统是否可以正确高效运行。
现假设有一台服务器宕机了,那么为了填补空缺,要将宕机的服务器从编号列表中移除,后面的服务器按顺序前移一位并将其编号值减一,此时每个key就要按 h = Hash(key) % (N-1) 重新计算;同样,如果新增了一台服务器,虽然原有服务器编号不用改变,但是要按 h = Hash(key) % (N+1) 重新计算哈希值。因此系统中一旦有服务器变更,大量的key会被重定位到不同的服务器从而造成大量的缓存不命中。而这种情况在分布式系统中是非常糟糕的。
一个设计良好的分布式哈希方案应该具有良好的单调性,即服务节点的增减不会造成大量哈希重定位。一致性哈希算法就是这样一种哈希方案。
一致性哈希算法
算法简述
一致性哈希算法(Consistent Hashing)最早在论文《 Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web》中被提出。简单来说,一致性哈希将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为 0 – 232-1(即哈希值是一个32位无符号整形),整个哈希空间环如下:
整个空间按顺时针方向组织。0和232-1在零点中方向重合。
下一步将各个服务器使用H进行一个哈希,具体可以选择服务器的ip或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置,这里假设将上文中三台服务器使用ip地址哈希后在环空间的位置如下:
接下来使用如下算法定位数据访问到相应服务器:将数据key使用相同的函数H计算出哈希值h,通根据h确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器。
例如我们有A、B、C、D四个数据对象,经过哈希计算后,在环空间上的位置如下:
根据一致性哈希算法,数据A会被定为到Server 1上,D被定为到Server 3上,而B、C分别被定为到Server 2上。
By admin
read moreRedis 选择hash还是string 存储数据?
在Redis中存储数据时,经常使用string和hash这两种类型,至于这两者有什么不同,底层实现有何区别,推荐参考: https://segmentfault.com/a/1190000019552836
By admin
read more一文理解MySQL中的page页
在介绍InnoDB中的页的时候,很有必要先让大家了解一下InnoDB中的存储结构
从InnoDB存储引擎的逻辑结构看,所有数据都被逻辑地存放在一个空间内,称为表空间(tablespace),而表空间由段(sengment)、区(extent)、页(page)组成。 在一些文档中extend又称块(block)。
一、表空间(table space)
表空间(Tablespace)是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可以划分为系统表空间、用户表空间、撤销表空间、临时表空间等。
在 InnoDB 中存在两种表空间的类型:共享表空间和独立表空间。如果是共享表空间就意味着多张表共用一个表空间。如果是独立表空间,就意味着每张表有一个独立的表空间,也就是数据和索引信息都会保存在自己的表空间中。独立的表空间可以在不同的数据库之间进行迁移。可通过命令
mysql > show variables like 'innodb_file_per_table';
查看当前系统启用的表空间类型。目前最新版本已经默认启用独立表空间。
InnoDB把数据保存在表空间内,表空间可以看作是InnoDB存储引擎逻辑结构的最高层。本质上是一个由一个或多个磁盘文件组成的虚拟文件系统。InnoDB用表空间并不只是存储表和索引,还保存了回滚段、双写缓冲区等。
** 二、段(segment)**
段(Segment)由一个或多个区组成,区在文件系统是一个连续分配的空间(在 InnoDB 中是连续的 64 个页),不过在段中不要求区与区之间是相邻的。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段。
三、区(extent)
在 InnoDB 存储引擎中,一个区会分配 64 个连续的页。因为 InnoDB 中的页大小默认是 16KB,所以一个区的大小是 64*16KB=1MB。在任何情况下每个区大小都为1MB,为了保证页的连续性,InnoDB存储引擎每次从磁盘一次申请4-5个区。默认情况下,InnoDB存储引擎的页大小为16KB,即一个区中有64个连续的页。
四、页(Page)
页是InnoDB存储引擎磁盘管理的最小单位,每个页默认16KB;InnoDB存储引擎从1.2.x版本碍事,可以通过参数innodb_page_size将页的大小设置为4K、8K、16K。若设置完成,则所有表中页的大小都为innodb_page_size,不可以再次对其进行修改,除非通过mysqldump导入和导出操作来产生新的库。
innoDB存储引擎中,常见的页类型有:
数据页(B-tree Node)
undo页(undo Log Page)
系统页 (System Page)
事物数据页 (Transaction System Page)
插入缓冲位图页(Insert Buffer Bitmap)
插入缓冲空闲列表页(Insert Buffer Free List)
未压缩的二进制大对象页(Uncompressed BLOB Page)
压缩的二进制大对象页 (compressed BLOB Page)
五、行(row)
InnoDB存储引擎是按行进行存放的,每个页存放的行记录也是有硬性定义的,最多允许存放16KB/2-200,即7992行记录。
了解了整体架构,下面我们开始详细对Page来做一些介绍。
先贴一张Page完整的结构图Innodb引擎中的Page完整结构图(注意箭头)
上较的概念实在太多了,为了方便理解,可以按下面的分解一下Page的结构Page结构示意图1
每部分的意义Page结构示意图2
页结构整体上可以分为三大部分,分别为通用部分(文件头、文件尾)、存储记录空间、索引部分。
第一部分通用部分,主要指文件头和文件尾,将页的内容进行封装,通过文件头和文件尾校验的CheckSum方式来确保页的传输是完整的。
By admin
read more一列说明数组与Hash效率的区别到底多大
在数组中添加 10000 个元素,然后分别对这 10000 个元素进行检索,最后统计检索的时间。
数组Array
import time
# 插入数据,数组
result = []
for i in range(10000):
result.append(i)
# 检索数据
time_start=time.time()
for i in range(10000):
temp = result.index(i)
time_end=time.time()
print('检索时间', time_end-time_start)
运行结果:
检索时间为 1.2436728477478027 秒。
Hash哈希
import time
# 插入数据
result = {}
for i in range(1000000):
result[i] = i
# 检索数据
time_start=time.time()
for i in range(10000):
temp = result[i]
time_end=time.time()
print('检索时间:',time_end-time_start)
运行结果:
检索时间为 0.0019941329956054688 秒。
你能看到 Hash 方式检索差不多用了 2 毫秒的时间,检索效率提升得非常明显。这是因为 Hash 只需要一步就可以找到对应的取值,算法复杂度为 O(1),而数组检索数据的算法复杂度为 O(n)。
By admin
read more