前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自己动手写数据库:视图元数据管理,统计元数据管理

自己动手写数据库:视图元数据管理,统计元数据管理

作者头像
望月从良
发布2023-02-26 16:23:04
3520
发布2023-02-26 16:23:04
举报
文章被收录于专栏:Coding迪斯尼Coding迪斯尼

在数据库中,除了数据表外,还有一个重要对象叫视图。视图是由SQL语句将不同字段从不同表中抽取或者构造后形成的新表,跟数据库表不同在于,它不存储在磁盘上,而是在使用时临时构建出来。

跟数据库表一样,视图同样需要进行元数据管理。跟上节相同我们定义一个ViewManager来创建视图,同时创建一个viewcat数据库表来存储视图的元数据,这个表有两个字段分别是ViewName,他是字符串类型,还有一个叫ViewDef,他是一个二进制数据类型,具体细节在后面的实现中会清楚说明。

我们看看代码实现,首先在metadata_management目录下创建文件view_manager.go文件,然后输入代码如下:

代码语言:javascript
复制
package metadata_manager

import (
    rm "record_manager"
    "tx"
)

const (
    /*
        视图元数据对应数据结构大小,通常视图元数据的定义需要几千个字节,我们这里出于学习和实验目的,只把使用
        100字节来表示
    */
    MAX_VIEWDEF = 100
)

type ViewManager struct {
    tblMgr *TableManager
}

func NewViewManager(isNew bool, tblMgr *TableManager, tx *rm.Transation) *ViewManager {
    viewMgr := &ViewManager{
        tblMgr: tblMgr,
    }

    if isNew {
        //使用表管理器创建元数据表viewcat
        sch := rm.NewSchema()
        sch.AddStringField("viewname", MAX_NAME)
        sch.AddStringField("viewdef", MAX_VIEWDEF)
        tblMgr.CreateTable("viewcat", sch, tx)
    }

    return viewMgr
}

func (v *ViewManager) CreateView(vname string, vdef string, tx *rm.Transation) {
    //每创建一个视图对象,就在viewcat表中插入一条对该视图对象元数据的记录
    layout := v.tblMgr.getLayout("viewcat", tx)
    ts := rm.NewTableScan(tx, "viewcat", layout)
    ts.Insert()
    ts.SetString("viewname", vname)
    ts.SetString("viewdef", vdef)
    ts.Close()
}

func (v *ViewManager) GetViewDef(vname string, tx *tx.Transation) string {
    result := ""
    layout := v.tblMgr.GetLayout("viewcat", tx)
    //获取视图的表结构
    ts := rm.NewTableScan(tx, "viewcat", layout)
    for ts.Next() {
        if ts.GetString("viewcat") == vname {
            result = ts.GetString("viewdef")
            break
        }
    }

    ts.Close()
    return result
}

另外还需要考虑的元数据是统计信息。统计信息一般包含当前有多少条记录,字段在磁盘中的分布信息等,这些数据在引擎执行查询时用于估计成本,统计信息处理的好能大大加快查询速度。如果是商用数据库,这些信息将会多如牛毛,我们这里简单起见就保持三种数据即可,他们分别是每个表使用了多少区块,每个表包含了多少条记录,对于某个表中的某个字段,它有多少个不重复的值。

不难看到维护这些统计信息需要付出一定的性能代价,因为当数据库表有插入,删除,更新等操作时,我们都得对统计信息进行更新,为了处理这个问题我们不再像前面那样使用元数据表来存放统计数据,而是把统计信息全部保留在内存里,当数据库系统启动时,它扫描一次所有数据库表,构造出统计信息寄存在内存中。同时每过一段时间系统就扫描数据库表然后更新统计数据。这种做法的问题在于在某个时刻统计信息跟实际情况有所不符,但问题不大,因为这些信息主要用来估算查询成本,它不是很准确问题也不大。我们看看统计元数据的实现,在当前目录增加一个文件名为stat_manager.go,实现代码如下:

代码语言:javascript
复制
package metadata_manager

import (
    rm "record_manager"
    "sync"
    "tx"
)

const (
    //数据库表发生变化100次后更新统计数据
    REFRESH_STAT_INFO_COUNT = 100
)

type StatInfo struct {
    numBlocks int //数据库表的区块数
    numRecs   int //数据库表包含的记录数
}

func newStatInfo(numBlocks int, numRecs int) *StateInfo {
    return &StatInfo{
        numBlocks: numBlocks,
        numRecs:   numRecs,
    }
}

func (s *StatInfo) BlocksAccessed() int {
    return s.numBlocks
}

func (s *StatInfo) RecordsOutput() int {
    return s.numRecs
}

func (s *StatInfo) DistincValues(fldName string) int {
    //字段包含多少不同的值
    return 1 + (s.numRecs / 3) //初步认为三分之一,后面再修改
}

type StatManager struct {
    tblMgr     *TableManager
    tableStats map[string]*StateInfo
    numCalls   int
    lock       sync.Mutex
}

func NewStatManager(tblMgr *TableManager, tx *tx.Transation) *StatManager {
    statMgr := &StatManager{
        tblMgr:   tblMgr,
        numCalls: 0,
    }
    //更新统计数据
    statMgr.refreshStatistics(tx)
    return statMgr
}

func (s *StatManager) GetStatInfo(tblName string, layout *rm.Layout, tx *tx.Transation) *StatInfo {
    s.lock.Lock()
    defer s.lock.Unlock()
    s.numCalls += 1
    if s.numCalls > REFRESH_STAT_INFO_COUNT {
        s.refreshStatistic(tx)
    }

    si := s.tableStats[talName]
    if si == nil {
        //为新数据库表创建统计对象
        si = s.calcTableStats(tblName, layout, tx)
        s.tableStats[tblName] = si
    }

    return si
}

func (s *StatManager) refreshStatistics(tx *tx.Transation) {
    s.tableStats = make(map[string]*StatInfo)
    s.numCalls = 0
    tcatLayout := s.tblMgr.GetLayout("tblcat", tx)
    tcat := rm.NewTableScan(tx, "tblcat", tcatLayout)
    for tcat.Next() {
        tblName := tcat.GetString("tblname")
        layout := s.tblMgr.GetLayout(tblName, tx)
        si := s.calcTableStats(tblName, layout, tx)
        s.tableStats[tblName] = si
    }

    tcat.Close()
}

func (s *StatManager) calcTableStats(tblName string, layout *rm.Layout, tx *tx.Transation) *StatInfo {
    numRecs := 0
    numBlocks := 0
    ts := rm.NewTableScan(tx, tblName, layout)
    for ts.Next() {
        numRecs += 1
        numBlocks = ts.GetRid().BlockNumber() + 1
    }
    ts.Close()
    return newStatInfo(numRecs, numBlocks)
}

在上面代码中,我们使用对象StatInfo来包含表的统计信息,其中包括表的记录数,区块数还有给定字段拥有的不同值的数量。StatManager用于获取统计元数据,它只在系统启动时创建,在创建时它调用自己的refreshStatistics接口创建统计数据并存储在内存中,这个接口会继续调用calcTableStats来获取每个表的相关数据,后者会从通过TableManage获取所有数据库表,然后获得每个表的相关数据,然后创建StatInfo对象,并把表的统计数据存储在其中。

最后我们使用一个名为MetaDataManager的对象将前面实现的所有Manager统一管理起来,在目录中创建meta_manager.go实现代码如下:

代码语言:javascript
复制
package metadata_manager

import (
    rm "record_manager"
    "tx"
)

type MetaDataManager struct {
    tblMgr  *TableManager
    viewMgr *ViewManager
    statMgr *StatManager
    //索引管理器以后再处理
    //idxMgr *IndexManager
}

func NewMetaDataManager(isNew bool, tx *tx.Transation) *MetaDataManager {
    metaDataMgr := &MetaDataManager{
        tblMgr: NewTableManager(isNew, tx),
    }

    metaDataMgr.viewMgr = NewViewManager(isNew, metaDataMgr.tblMgr, tx)
    metaDataMgr.statMgr = NewStatManager(metaDataMgr.tblMgr, tx)

    return metaDataMgr
}

func (m *MetaDataManager) CreateTable(tblName string, sch *rm.Schema, tx *tx.Transation) {
    m.tblMgr.CreateTable(tblName, sch, tx)
}

func (m *MetaDataManager) CreateView(viewName string, viewDef string, tx *tx.Transation) {
    m.viewMgr.CreateView(viewName, viewDef, tx)
}

func (m *MetaDataManager) GetLayout(tblName string, tx *tx.Transation) *rm.Layout {
    return m.tblMgr.GetLayout(tblName, tx)
}

func (m *MetaDataManager) GetViewDef(viewName string, tx *tx.Transation) string {
    return m.viewMgr.GetViewDef(viewName, tx)
}

func (m *MetaDataManager) GetStatInfo(tblName string, layout *rm.Layout, tx *tx.Transation) *StatInfo {
    return m.statMgr.GetStatInfo(tblName, layout, tx)
}

最后我们在main函数中调用MetaDataManager看看效果:

代码语言:javascript
复制
package main

import (
    bmg "buffer_manager"
    fm "file_manager"
    "fmt"
    lm "log_manager"
    "math/rand"
    mm "metadata_management"
    record_mgr "record_manager"
    "tx"
)

func main() {
    file_manager, _ := fm.NewFileManager("recordtest", 400)
    log_manager, _ := lm.NewLogManager(file_manager, "logfile.log")
    buffer_manager := bmg.NewBufferManager(file_manager, log_manager, 3)

    tx := tx.NewTransation(file_manager, log_manager, buffer_manager)
    sch := record_mgr.NewSchema()
    sch.AddIntField("A")
    sch.AddStringField("B", 9)

    mdm := mm.NewMetaDataManager(true, tx)
    mdm.CreateTable("MyTable", sch, tx)
    layout := mdm.GetLayout("MyTable", tx)
    size := layout.SlotSize()
    fmt.Printf("MyTable has slot size: %d\n", size)
    sch2 := layout.Schema()
    fmt.Println("Its fields are: ")
    for _, fldName := range sch2.Fields() {
        fldType := ""
        if sch2.Type(fldName) == record_mgr.INTEGER {
            fldType = "int"
        } else {
            strlen := sch2.Length(fldName)
            fldType = fmt.Sprintf("varchar ( %d )", strlen)
        }

        fmt.Printf("%s :  %s\n", fldName, fldType)
    }

    ts := record_mgr.NewTableScan(tx, "MyTable", layout)
    //测试统计元数据
    for i := 0; i < 50; i++ {
        ts.Insert()
        n := rand.Intn(50)
        ts.SetInt("A", n)
        strField := fmt.Sprintf("rec%d", n)
        ts.SetString("B", strField)
    }
    si := mdm.GetStatInfo("MyTable", layout, tx)
    fmt.Printf("blocks for MyTable is %d\n", si.BlocksAccessed())
    fmt.Printf("records for MyTable is :%d\n", si.RecordsOutput())
    fmt.Printf("Distinc values for field A is %d\n", si.DistinctValues("A"))
    fmt.Printf("Distinc values for field B is %d\n", si.DistinctValues("B"))

    //统计视图信息
    viewDef := "select B from MyTable where A = 1"
    mdm.CreateView("viewA", viewDef, tx)
    v := mdm.GetViewDef("viewA", tx)
    fmt.Printf("View def = %s\n", v)
    tx.Commit()
}

上面代码运行后输出结果如下:

代码语言:javascript
复制
MyTable has slot size: 33
Its fields are: 
A :  int
B :  varchar ( 9 )
blocks for MyTable is 5
records for MyTable is :50
Distinc values for field A is 17
Distinc values for field B is 17
View def = select B from MyTable where A = 1
transation 1  committed

具体的逻辑请在B站搜索Coding迪斯尼查看调试演示和逻辑讲解。代码下载路径:https://github.com/wycl16514/database_system_meta_data_manager

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-12-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coding迪斯尼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档