首页 文章 精选 留言 我的

精选列表

搜索[智能问数],共10000篇文章
优秀的个人博客,低调大师

每日一博 | 四问复合索引,让你的数据查询速度飞起

本文分享自华为云社区《华为云GES持久化图数据库复合索引介绍》,作者:村头树下。 本文章主要介绍索引的作用,以及如何实现这种功能,希望可以帮助理解索引的作用以及如何使用索引 1. 什么是复合索引 复合索引是用户手动建立的用于加速查询的一类额外数据。详细参数可以参考规格文档 https://support.huaweicloud.com/api-ges/ges_03_0454.html 2. 复合索引能做什么 复合索引有两类。一是label索引,用于加速label的扫描。二是属性索引,用于加速属性过滤。 这里列举了一些常用接口(语句)与索引的关系 api接口 索引加速方式 summary 扫描label索引,统计各label点边数目 match (n:user) return count(*) 扫描点label索引,统计label为user的点数目 match ()-[r:label]-() return count® 扫描边label索引,统计指定label点数目 match (n:user) return n limit 1 通过点label索引快速寻找label为user的点 match (n:user) where n.age > 10 return n limit 1 仅有label索引时扫描label索引,寻找user的点,然后进行属性过滤。当存在age属性索引时直接使用属性索引定位到目标点 match (n:user) where n.age in [1, 10] return n limit 1 同上 3. 无索引时如何查询 首先了解无索引的情况下,查询的逻辑,才可以理解索引在此基础上做了什么使得查询能够加速。查询逻辑主要与两个方面有关:数据结构,以及数据访问方式,以及查询场景。 a) 原始点结构 持久化版本所有数据都是以KV(键值对)的方式存储在分布式KV数据库中,在没有建立索引的时候,数据库中仅有原始点边KV。以点数据结构为例: Key: Value: key的开始部分为kVType,这是所有数据都会存在的固定前缀,用以区分不同类型的数据。然后是Vid是全局唯一点id。Labelid是标识label的内置编码。Value则是属性的数据。 b) 数据访问方式 所有的图数据的查询最终都是依托于KV数据库的访问。常用的访问KV数据的方式有两种: 精确查询接口,指定完整的key查询value 前缀查询接口,仅指定key的前缀部分,查询所有key的前缀匹配的KV数据对。前缀查相对来说会更加频繁的使用。一个场景可能会需要多次前缀查,而前缀查的次数越多,结果越多,相应的此场景响应速度就越慢。前缀查结果大小直接与前缀的长度有关,前缀越长或者越精确,那么前缀查的结果越少。需要的计算量也越少。相应速度就会越快。 c) 查询场景: 常见查询场景的对应的kv层接口调用: 场景 KV接口及调用次数 查询速度 对应Cypher语句 指定id过滤 前缀查 * 1 快,由于KVType和Vid已知,可以拼出前缀,同时一个id一般不会有太多label,前缀查的结果不会特别多。 match(n) where id(n)=‘0’ return n 指定label过滤 前缀查 n + 过滤 m 慢, 由于不知道Vid,所以只能先拼出只有KVType的前缀,然后前缀查出所有点,再逐个过滤Label,点数据较多时,会有多次前缀查,分批获取再过滤。 match(n:Label) return n 指定label+属性过滤 前缀查 n + 过滤 m 慢, 查询前缀为KvType,遍历全图点,先进行Label过滤,再进行属性过滤 match (n:Label) where n.prop=‘xx’ return n 指定属性过滤 前缀查 n + 过滤 m 非常慢, 查询前缀为KvType,遍历全图点,全部进行属性过滤 match (n) where n.prop=‘xx’ return n 可见,除了指定id的查询,其他所有查询均非常慢。这些查询都需要进行全图点扫描加过滤的方式来获取结果。这与查询出来的结果数目无关。对于较大的图来说,这样的查询代价是十分巨大的。 4. 复合索引如何加速 查询慢的场景无外乎两种场景,label查询或者属性查询。在没有索引的情况下,这两种查询都是建立在全局点扫描的基础上,进行过滤。当有效数据占比越低(例如全局点1w,目标点仅有1个),这种扫描方式就越显得不划算。 对于这两种场景,我们可以建立对应的索引。索引本身也是KV数据。所以其key的布局就决定了其功能。 1.对于label过滤场景,索引的key的格式为: 对于每一个点,都会有一条对应的Label索引KV。 当需要过滤特定Label时,可以拼出KVType+Label的前缀,利用kv数据底座的前缀查接口,就能直接将所有符合条件的点过滤出来。 2. 对于属性过滤的场景,索引的key格式为: 属性索引只针对个别过滤较为频繁的属性而建立。所以也只会对包含此属性的点才会生成属性索引kv。相比于Label索引这里只是多了一个property字段。此字段填的是Vid对应点的属性的值。需要注意的是,property字段并不包含全部的点属性,仅仅是待过滤属性的值。 当进行属性查询时,由于知道目标值(例如where n.prop=1,目标值就是1)。直接拼出KVTypr+Label+Property,调用前缀查询接口。即可查出所有符合条件的点。 当利用索引查出匹配的索引KV之后,就可以很方便的拿到对应的VId。然后根据此Vid,就能快速查询到这个点的属性,或者邻居等信息。 5. 索引建立的若干建议 索引并不是没有代价的,虽然它能加速查询,但是会降低写操作的性能,以及耗费更多的磁盘空间。所以建立索引之前需要考虑是不是必要的。这可以从数据区分度,数据大小,以及访问频率三个方面来评估。 数据区分度:对于属性索引建议在过滤性好的属性上建立。值分布较为分散,比较适合建立。例如身份证号,手机号。但是对于性别这种属性,就不建议为此建立。对于label索引,如果图里面只有一个label,那么建label索引其实也是没有什么必要的,但是大部分情况,label索引都是必要的。 数据大小:这主要是针对属性索引来说的,在已经有Label索引的前提下,如果某个label下的点边数目很少,即使扫描所有label代价也不高,这时候没有必要再为其建立属性索引。 访问频率:这一点很好理解,只对频繁在where子句中出现的属性建立索引。 点击关注,第一时间了解华为云新鲜技术~

优秀的个人博客,低调大师

面试官问:Go 中的参数传递是值传递还是引用传递?

一个程序中,变量分为变量名和变量内容,变量内容的存储一般会被分配到堆和栈上。而在 Go 语言中有两种传递变量的方式值传递和引用传递。其中值传递会直接将变量内容附在变量名上传递,而引用传递会将变量内容的地址附在变量名上传递。 Golang 中是如何做到 如果在面试时有面试官提问你:“Go 的参数是如何传递的?”你会怎么回答呢? 这个问题其实只有一个答案。因为在 Golang 中所有的类型传递都是通过值传递实现的,而不是引用传递,即使是指针的传递也是通过 copy 指针的方式进行。另外对于一些包裹了底层数据的数据结构,其值传递的过程中,复制的也只是实例的指针,而不是底层数据所暴露出来的指针。 下面以 Go 版本 1.8 的 slice 为例来简单了解一下: func makeslice(et *_type, len, cap int) unsafe.Pointer { mem, overflow := math.MulUintptr(et.size, uintptr(cap)) if overflow || mem > maxAlloc || len < 0 || len > cap { // NOTE: Produce a 'len out of range' error instead of a // 'cap out of range' error when someone does make([]T, bignumber). // 'cap out of range' is true too, but since the cap is only being // supplied implicitly, saying len is clearer. // See golang.org/issue/4085. mem, overflow := math.MulUintptr(et.size, uintptr(len)) if overflow || mem > maxAlloc || len < 0 { panicmakeslicelen() } panicmakeslicecap() } return mallocgc(mem, et, true) // 申请内存 } 可以看到 slice 在初始化的过程中调用了 runtime 中的 makeslice 函数,这个函数会将 slice 的地址返回给接受的变量。 type slice struct { array unsafe.Pointer // 底层数组的地址 len int cap int } // 初始化过程 p := make([]int,0) fmt.Printf("变量p的地址%p", &p) fmt.Printf("slice的地址%p\n", p) 上面打印时出现的是的内容,这是因为 Go 内部实现了自动解引用(即 Go 内部实现的解引用操作)。 自动解引用时 receive 会从指针类型转变为值类型。顺带一提自动取引用时 receiver 会从值类型转变为指针类型。 如果未实现自动解引用时会怎样呢?下面是未实现自动解引用的情况: // 当我们打印变量p的时候,实际过程是发生了这样的变化 // 只是猜测,当然发生解引用是一定的 // & 取地址操作符 // * 根据地址取值操作 也称之为解引用运算法,间址运算符 // 1. 获取指针地址 &p // 2. 获取array的地址 &((&p).array) // 3. 获取底层数组实际内容 *&((&p).array) 未实现自动借用的函数传递过程,也是通过复制指针的方式来传递的,内容如下: package main import ( "fmt" ) func change(p1 []int) { fmt.Printf("p1的内存地址是: %p\n", &p1) // p1的内存地址是: 0xc0000a6048 fmt.Printf("函数里接收到slice的内存地址是:%p\n", p1) // 函数里接收到slice的内存地址是:0xc00008c030 p1 = append(p1, 30) } func main() { p := make([]int, 3) // 抛出一个指针 p = append(p, 20) fmt.Printf("p的内存地址是: %p\n", &p) // p的内存地址是: 0xc00009a018 fmt.Printf("slice的内存地址是:%p\n", p) // slice的内存地址是:0xc00008c030 change(p) // 重新生成一份地址p1 指向slice地址 fmt.Printf("修改之后p的内存地址 %p\n", &p) // 修改之后p的内存地址 0xc00009a018 fmt.Printf("修改之后slice的内存地址 %p\n", p) // 修改之后slice的内存地址 0xc00008c030 fmt.Println("修改之后的slice:", p) // 修改之后的slice [0 0 0 20] fmt.Println(*&p) // [0 0 0 20] } 需要注意的是,在函数传递的过程中 copy 的不是 slice 内部指向底层数组的指针,而是在 makeslice 函数所返回的指针。 源码实现 大家在看一些老旧的文章的时候,可能看到过这样的说法:make 返回的是 slice 的实例。但其实这种说法已经过时了,在 Golang 1.2 版本之后 make 返回的就是实例的指针。 github pr 地址:https://github.com/golang/go/commits/dev.boringcrypto.go1.12/src/runtime/slice.go 扩展 其实和 slice 类似的还有 map,chan。 先说 map,map 的官网定义:“Go provides a built-in map type that implements a hash table.Map types are reference types, like pointers or slices.”而 chan 和 map 一样也是一个指针,也就是说二者和 slice 的原理相似。 func makemap(t *maptype, hint int, h *hmap) *hmap { mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size) if overflow || mem > maxAlloc { hint = 0 } ... } func makechan(t *chantype, size int) *hchan { ... mem, overflow := math.MulUintptr(elem.size, uintptr(size)) if overflow || mem > maxAlloc-hchanSize || size < 0 { panic(plainError("makechan: size out of range")) } ... } 如果平时的使用中不注意,会出现一些不必要的麻烦,如: package main import "fmt" type InfoIns struct { Name string info []string } func NewInfoIns() InfoIns{ return InfoIns{ Name: "", info: nil, } } func (n *InfoIns) SetInfo(info []string){ n.info = info } func main(){ infoIns := NewInfoIns() info := []string{"p1", "p2", "p3"} infoIns.SetInfo(info) info[1] = "p4" fmt.Println(infoIns.info) // [p1 p4 p3] } 这里的InfoIns 在SetInfo之后存的是 info 的地址。一旦 info 在后续有改动 InfoIns 中的内容也随之会被改动。解决的方法是在 SetInfo 的时候重新申请一份地址。 func (n *InfoIns) SetInfo(info []string){ n.info = make([]string, len(info)) copy(n.info, info) } 脚注 借助 Goland 查看 Go 源码的方式:Ctrl+Shift+f 全局搜索,选择 Scope 中的 ALL Place。 推荐阅读 一文聊透 IP 地址的那些事 Golang 常见设计模式之装饰模式

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册