Mysql存储-EAV模式

news2024/11/28 9:27:21

Mysql存储-EAV模式

最近又又又搞一点新东西,要整合不同业务进行存储和查询,一波学习过后总结了一下可扩展性MAX的eav模式存储。

在eav这里的数据结构设计尤为关键,需要充分考虑你需要使用的字段、使用场景,当数据结构设计完成后便会发现eav模型需要多次join操作才能完成查询,因此性能优化的难点也是在如何充分使用索引

一、简介

1、概念

EAV(Entity-Attribute-Value)模式,也称为对象-属性-值模式,是一种常用于数据库设计的灵活模式,适用于具有大量属性和属性值的实体。它在MySQL数据库中的实现可以解决一些传统关系型数据库表结构无法轻松满足的需求,例如动态属性、稀疏属性等。

EAV模式的核心思想是将实体(Entity)的属性(Attribute)和值(Value)分别存储在不同的表中。这样可以在不修改表结构的情况下轻松添加或删除属性,从而提高数据库的灵活性。

EAV模式在MySQL数据库中通常包含以下三个表:

  1. 实体表(Entity Table):存储实体的基本信息,如ID、名称等。每个实体对应该表中的一行记录。
  2. 属性表(Attribute Table):存储属性的元数据,如属性ID、属性名称、数据类型等。每个属性对应该表中的一行记录。
  3. 值表(Value Table):存储实体的属性值。每个属性值对应该表中的一行记录,包括实体ID、属性ID和属性值。

在这里插入图片描述

2、特点

EAV模式的优点:

  1. 高度灵活:可以轻松添加、删除或修改属性,而无需更改表结构。
  2. 节省存储空间:对于具有大量稀疏属性的实体,EAV模式可以避免在数据表中存储大量NULL值。

EAV模式的缺点:

  1. 查询复杂:由于属性和值分散在多个表中,查询和聚合操作通常需要多表连接,导致查询性能较差。
  2. 数据完整性:EAV模式较难实现属性值的数据类型和约束检查,可能导致数据完整性问题。

二、详细设计

在这里插入图片描述

写入时:

  • 在实际业务上会接入不同领域的数据,不同领域数据内容也不尽相同,在领域分治的情况下便只需要考虑单一的固定数据。

  • 同一领域内数据具有一定的相似性,将较多出现的数据存放于entity表中,以减少多次join操作的情况,性能++

  • 同一领域内的相同扩展字段名称可能会出现不同数据类型的情况,因此需要在attributes表中增加name、type的唯一键,进行upsert操作,保证该表数据满足全部场景

  • 根据传入的interface类型,将数据存储到对应的字段中。例如,如果传入的数据是整数类型,将数据存储到int_value字段中

查询时:

  • 需要增加表,用于记录单个领域下的entity中的固定字段,在查询时先查询该领域的固定字段是否cover查询要求的字段,如果cover住则不需要查询values表。
  • 根据attributes表中的type字段进行“类型断言”。例如,如果attributes表中的type值为’int’,则从values表中的int_value字段中读取数据(应在各场景下最大程度地减少使用断言)
    • 类型断言是Golang内置的特性,不需要额外引入包
    • 反射是指在运行时动态获取变量的类型信息、操作变量的方法

三、demo

SQL:

CREATE TABLE entities (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    status VARCHAR(255) NOT NULL,
    type VARCHAR(255) NOT NULL
);

CREATE TABLE attributes (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL UNIQUE
);

CREATE TABLE values (
    entity_id INT,
    attribute_id INT,
    value VARCHAR(255) NOT NULL,
    PRIMARY KEY (entity_id, attribute_id),
    FOREIGN KEY (entity_id) REFERENCES entities(id),
    FOREIGN KEY (attribute_id) REFERENCES attributes(id)
);

Golang:

package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
)

type Data struct {
	ID         int
	Name       string
	Status     string
	Type       string
	ExtraData  map[string]string
}

func main() {
	db, err := sql.Open("mysql", "username:password@tcp(localhost:3306)/dbname")
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// 插入数据
	extraData := map[string]string{
		"check_data": "2021-10-01",
		"start_time": "10:00:00",
	}

	entityID, err := insertData(db, "name1", "status1", "type1", extraData)
	if err != nil {
		panic(err)
	}

	// 查询数据
	data, err := getData(db, entityID)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Data: %+v\n", data)
}

func insertData(db *sql.DB, name, status, dataType string, extraData map[string]string) (int, error) {
	res, err := db.Exec("INSERT INTO entities (name, status, type) VALUES (?, ?, ?)", name, status, dataType)
	if err != nil {
		return 0, err
	}

	entityID, err := res.LastInsertId()
	if err != nil {
		return 0, err
	}

	for attributeName, value := range extraData {
		attributeID, err := getOrCreateAttribute(db, attributeName)
		if err != nil {
			return 0, err
		}

		_, err = db.Exec("INSERT INTO values (entity_id, attribute_id, value) VALUES (?, ?, ?)", entityID, attributeID, value)
		if err != nil {
			return 0, err
		}
	}

	return int(entityID), nil
}

func getData(db *sql.DB, entityID int) (*Data, error) {
	row := db.QueryRow("SELECT id, name, status, type FROM entities WHERE id = ?", entityID)

	var data Data
	err := row.Scan(&data.ID, &data.Name, &data.Status, &data.Type)
	if err != nil {
		return nil, err
	}

	rows, err := db.Query("SELECT a.name, v.value FROM attributes a JOIN values v ON a.id = v.attribute_id WHERE v.entity_id = ?", entityID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	data.ExtraData = make(map[string]string)
	for rows.Next() {
		var attributeName, value string
		if err := rows.Scan(&attributeName, &value); err != nil {
			return nil, err
		}
		data.ExtraData[attributeName] = value
	}

	return &data, nil
}

func getOrCreateAttribute(db *sql.DB, attributeName string) (int, error) {
	var attributeID int
	err := db.QueryRow("SELECT id FROM attributes WHERE name = ?", attributeName).Scan(&attributeID)
	if err == sql.ErrNoRows {
		res, err := db.Exec("INSERT INTO attributes (name) VALUES (?)", attributeName)
		if err != nil {
			return 0, err
		}

		id, err := res.LastInsertId()
		if err != nil {
			return 0, err
		}
		attributeID = int(id)
	} else if err != nil {
		return 0, err
	}

	return attributeID, nil
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1069975.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

skywalking功能介绍

服务 服务信息 请求接口后查看skywalking,可以看到有一个请求,响应时间为1852ms,性能指数Apdex为0.5。 详细表盘 点进应用可以看到表盘 可以看到显示有一个slow endpoints,就是我请求的这个接口。 JVM信息 也可以看到JVM信息。…

点餐小程序实战教程06-首页开发

用户注册功能开发好了之后,我们就要开发小程序,首先我们是规划小程序的功能模块,我们一共是四个模块,分别是首页、订单、消息和我的。 首页我们主要是点餐的功能,可以选择菜品,加入到购物车,然…

deckGL自定义图层学习笔记

1.自定义图层 当使用DeckGL提供的图层还无法满足需求时(https://deck.gl/docs/api-reference/layers),可能就需要自定义图层了。在DeckGL中有常见的三种自定义图层的方式 创建复合层(composite layers.)——复合层是一…

ffmpeg从一个视频中提取音频

ffmpeg -i ~/video/video.mp4 -vn -acodec copy ~/video/audioFile.m4a 从video.mp4中提取音频到文件audioFile.m4a中 查看提取的音频文件 ffprobe ~/video/audioFile.m4a

OneDrive下的OneNote扩容方法,及查看OneDrive容量的方法(详细图文教程)

目录 一、内存不足的问题二、土豪续费扩容法三、X宝扩容法3.1 购买链接3.2 登录接口3.3 详细图文操作过程3.3.1 获取链接:3.3.2 用订单号和获取链接扩容: 3.4 扩容后的容量 四、查看自己OneDrive的容量五、总结 一、内存不足的问题 一直都在用OneNote记…

STM32H723 CubeMX 三路FDCAN 代码

时钟频率 FDCAN1 设置250kbit/s FDCAN2 设置500kbit/s FDCAN3 设置500kbit/s fdcan.c /* USER CODE BEGIN Header */ /********************************************************************************* file fdcan.c* brief This file provides code fo…

【刷题篇】回溯算法(深度优先搜索(二))

文章目录 岛屿数量电话号码的字母组合组合总和活字印刷 岛屿数量 给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直…

CentOS7.9中使用packstack安装train版本

这里写目录标题 材料准备为什么选择packstack安装静态ip系统配置使用阿里云yum源安装packstack部署openstack 安装成功和后续使用all in one模式下虚拟机外网和浮动ip原理讲解nat网桥的创建方法 材料准备 ecs云服务器8核心16g内存一台,系统盘100GB,系统…

2.2 数据通信的基础知识

前言: 2.2.1 数据通信的基础知识 **笔记**:2.2.1 数据通信系统的模型 --- **1. 数据通信系统组成**: - 三大部分: 1. 源系统 (发送端) 2. 传输系统 (传输网络) 3. 目的系统 (接收端) --- **2. 源系统**: -…

vue-4

一、文章内容概括 1.组件的三大组成部分(结构/样式/逻辑) ​ scoped解决样式冲突/data是一个函数 2.组件通信 组件通信语法父传子子传父非父子通信(扩展) 3.进阶语法 v-model原理v-model应用于组件sync修饰符ref和$refs$nex…

SAP SMARTFORMS 文本框显示默认浏览器

问题描述:新上的SAP系统SMARTFORMS文本框显示浏览器,导致无法拉取系统变量 解决方法: 类CL_COS_UTILITIES做隐式增强 IF sy-tcode SMARTFORMS.rv_is_s4h .ENDIF. 然后执行程序:RSCPSETEDITOR 把这俩√去掉后激活即可

MISRA C 2012 阅读笔记

背景 C语言诞生至今已有50年,因其语言简洁,语法丰富,可移植性高,和执行效率高等优点,至今仍保持着强大的生命力,在各个行业发挥着作用。 然而C语言的一些优点有时候也是一把双刃剑,在使用者使用…

Acwing.886 求组合数Ⅱ

题目 给定n组询问&#xff0c;每组询问给定两个整数a&#xff0c; b&#xff0c;请你输出 的值。 输入格式 第一行包含整数n。 接下来n行&#xff0c;每行包含—组a和b。 输出格式 共n行&#xff0c;每行输出—个询问的解。 数据范围 1<n≤10000, 1 <b<a≤105…

Acwing.889 满足条件的01序列

题目 给定n个0和n个1&#xff0c;它们将按照某种顺序排成长度为2n的序列&#xff0c;求它们能排列成的所有序列中&#xff0c;能够满足任意前缀序列中0的个数都不少于1的个数的序列有多少个。 输出的答案对109&#xff0b;7取模。 输入格式 共一行&#xff0c;包含整数n。 …

【python】python虚拟环境--20231008

https://blog.csdn.net/m0_69023493/article/details/129158656 安装好python和pip 略 新建python虚拟空间 安装virtualenv pip install virtualenv -i https://pypi.tuna.tsinghua.edu.cn/simple安装virtualenvwrapper-win&#xff08;可选&#xff09; pip install vir…

软件测试/测试开发丨接口测试学习笔记-常见的接口协议

点此获取更多相关资料 本文为霍格沃兹测试开发学社学员学习笔记分享 原文链接&#xff1a;https://ceshiren.com/t/topic/27726 一、网络模型 二、常见接口协议 RPC协议 RPC(remote procedure call)以本地代码调用的方式实现远程执行主要用于公司内部的服务调用 优点 传输效…

3分钟轻松实现网关网口连接罗克韦尔AB CompactLogix系列PLC

目录 EG网关网口连接罗克韦尔AB CompactLogix系列PLC 一. 准备工作 1.1 在对接前我们需准备如下物品 1.2 EG20网关准备工作 1.3 PLC准备工作 二. EMCP平台设置 2.1 新增EG设备 2.2 远程配置网关 2.3 网关绑定 2.4 通讯参数设置 2.5 创建设备驱动 2.5.1 添加变量 2.…

springcloud之项目实战搭建单体

写在前面 在上篇文章 中我们介绍了项目的整体内容以及架构&#xff0c;本文就开始实现一个单体的版本&#xff0c;在之后的文章中&#xff0c;在使用springcloud相关组件将这个单体的版本一步步的拆分为微服务的版本&#xff0c;在开始之前再贴下组件图&#xff1a; 本文我们分…

win11 vscode配置c/c++,使用mingw编译器

文章目录 第一步&#xff1a;装好vscode第二步&#xff1a;下载 mingw创建一个文件夹作为C或者C的项目文件夹&#xff0c;用vscode打开 第一步&#xff1a;装好vscode 之前使用python时装过 第二步&#xff1a;下载 mingw 官网 3.从这个界面一直往下滑 找到&#xff1a; 下…

是真的吗?Nuture子刊告诉你这么多年的微生物组经验都是错的?!

发表期刊&#xff1a;Nature Microbiology 发表时间&#xff1a;2023 影响因子&#xff1a;28.3 DOI: 10.1038/s41564-023-01426-7 在过去的二十年里&#xff0c;人们对人类微生物组研究的兴趣呈指数级增长&#xff0c;同时伴随而来的一系列相关研究的文献发表数目也是逐年递…