当前位置: 首页 > news >正文

Mysql存储-EAV模式

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 mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql"
)type Data struct {ID         intName       stringStatus     stringType       stringExtraData  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 Dataerr := 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 stringif 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 interr := 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
}

相关文章:

Mysql存储-EAV模式

Mysql存储-EAV模式 最近又又又搞一点新东西,要整合不同业务进行存储和查询,一波学习过后总结了一下可扩展性MAX的eav模式存储。 在eav这里的数据结构设计尤为关键,需要充分考虑你需要使用的字段、使用场景,当数据结构设计完成后便…...

全局变量报错:\Output\STM32.axf: Error: L6218E: Undefined symbol

全局变量报错: .\Output\STM32.axf: Error: L6218E: Undefined symbol key_num (referred from main.o). 这里只说全局变量哦,这是因为你在调用的.c文件里 把定义写在了函数里面,写函数外面就没事了 改为: .h的声明文件根本不用写…...

算法错题簿(持续更新)

自用算法错题簿,按算法与数据结构分类 python1、二维矩阵:记忆化搜索dp2、图论:DFS3、回溯:129612964、二叉树:贪心算法5、字符串:记忆化搜索6、01字符串反转:结论题7、二进制数:逆向…...

基于Springboot实现疫情网课管理系统项目【项目源码+论文说明】

基于Springboot实现疫情网课管理系统演示 摘要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势;对于疫情网课管理系统当然也不能排除在外,随着网络技术的不断成熟,带动了疫情…...

Linux文件与目录的增删改查

一、增 1、mkdir命令 作用: 创建一个新目录。 格式: mkdir [选项] 要创建的目录 常用参数: -p:创建目录结构中指定的每一个目录,如果目录不存在则创建,如果目录已存在也不会被覆盖。 用法示例&a…...

JVM的内存模型

一、JVM的内存模型 1.1、目标 内存模型是用来描述JVM内部的内存结构和内存管理的模型。它定义了JVM在运行Java程序时所需要的各种内存区域,以及每个内存区域的作用和特点。 1.2、结构划分 1.2.1、栈 每个线程在执行Java方法时会创建一个栈帧(Stack …...

数据采集项目之业务数据(三)

1. Maxwell框架 开发公司为Zendesk公司开源,用java编写的MySQL变更数据抓取软件。内部是通过监控MySQL的Binlog日志,并将变更数据以JSON格式发送到Kafka等流处理平台。 1.1 MySQL主从复制 主机每次变更数据都会生成对应的Binlog日志,从机可…...

vuedraggable影响点击事件的解决办法

在工作中有很多场景需要针对广告、商品、信息推广等进行一个排序,或者对展示的顺序做出调整,方便放用户第一眼看到自己感兴趣的信息,因此避免不了需要用到排序的插件,这里以vue为例子,采用的插件是vuedraggable,这个插件针对于排序的功能相对完善,官网地址:vuedraggable官网 但…...

Linux 中的 grep 命令

Linux 中的 grep 命令是一个强大的文本搜索工具,它允许用户在文件中查找指定的文本模式,并将匹配的行打印出来。grep 是“Global Regular Expression Print”的缩写,它使用正则表达式来进行文本搜索,因此具有强大的灵活性和功能。…...

阶段五-Day03-Ajax

一、JavaWeb中路径的说明 1. JavaWeb中的路径 在JavaWeb中, 路径分为相对路劲和绝对路径两种: 相对路径: ./ 表示当前目录 ../ 表示当前文件所在目录的上一级目录 绝对路径: 完整的路径名 2. 在JavaWeb中/的不同意义 /斜杠如果被浏览器解析,得到的是 协议本地ip端口号…...

EPOLL单线程版本 基于reactor 的 httpserver文件下载 支持多个客户端同时处理

之前写了一个httpserver的问价下载服务器 如果有多个客户端请求过来只能串行处理必须得等当前的操作完成之后才会处理 另外还存在 文件大的时候 会出错 处理不了 原因就是 sendfile是在一个while循环中处理的 当调用send失败返回-1之后 就 结束了 而一般来讲 se…...

uniapp实现微信小程序隐私协议组件封装

uniapp实现微信小程序隐私协议组件封装。 <template><view class"diygw-modal basic" v-if"showPrivacy" :class"showPrivacy?show:" style"z-index: 1000000"><view class"diygw-dialog diygw-dialog-modal bas…...

【Node.js】NPM 和 package.json

NPM npm 是 Node.js 的包管理工具&#xff0c;基于命令行&#xff0c;用于安装、升级、移除、管理依赖项。 常用命令&#xff1a; npm init&#xff1a;初始化一个新的 npm 项目&#xff0c;创建 package.json 文件。&#xff08;括号里为默认值&#xff09; description&am…...

周总结【java项目】

项目进度&#xff1a; 学习了JavaFX&#xff0c;下载了sceneBuilder辅助工具构建窗口&#xff08;目前建立了登陆&#xff0c;注册&#xff0c;忘记密码的界面&#xff09;&#xff0c;然后是学习了MySQL的连接&#xff0c;现在的项目是刚连上数据库&#xff1b; 下一步&…...

《深度不确定条件下的决策:从理论到实践》PDF

制定未来计划时需要预测变化&#xff0c;尤其是制定长期计划或针对罕见事件的计划时。当这些变化存在高度不确定性的时候&#xff0c;这种预期就变得越来越困难。 今天给大家介绍的这本《深度不确定条件下的决策&#xff1a;从理论到实践》正是解决以上问题的良方。完整书籍文…...

【MySQL】表的基础增删改查

前面我们已经知道怎么来创建表了&#xff0c;接下来就来对创建的表进行一些基本操作。 这里先将上次创建的表删除掉&#xff1a; mysql> use test; Database changedmysql> show tables; ---------------- | Tables_in_test | ---------------- | student | -----…...

第11章 Redis(二)

11.11 Redis 哨兵机制和集群有什么区别 难度:★★★ 重点:★★ 白话解析 前面的题目都是Redis的原理,接下来就是实际使用的问题了,首先Redis为了保证高可用,在微服务场景下必须是部署集群的,而Redis的集群部署通常就两种方式:主从和Redis Cluster。 参考答案 1、主从…...

mybatis配置entity下不同文件夹同类型名称的多个类型时启动springboot项目出现TypeException源码分析

记录问题&#xff1a;当配置了 mybatis.type-aliases-packagecom.runjing.erp.entity 配置项时&#xff0c;如果entity文件夹下存在不同子文件夹下的同名类型时&#xff0c;mybatis初始化加载映射时会爆出org.apache.ibatis.type.TypeException&#xff1a; The alias TestDemo…...

淘宝商品评论数据分析接口,淘宝商品评论接口

淘宝商品评论数据分析接口可以通过淘宝开放平台API获取。 通过构建合理的请求URL&#xff0c;可以向淘宝服务器发起HTTP请求&#xff0c;获取商品评论数据。接口返回的数据一般为JSON格式&#xff0c;包含了商品的各种评价信息。 获取到商品评论数据后&#xff0c;可以对其进…...

RK3288 android7.1 修改双屏异触usb tp触摸方向

一&#xff0c;问题描述&#xff1a; android机器要求接两个屏&#xff08;lvdsmipi&#xff09;两个usb tp要实现双屏异触。由于mipi的方向和lvds方向转成一样的了。两个usb tp的方向在异显示的时候也要作用一样。这个时候要根据pid和vid修改触摸上报的数据。usb tp有通用的触…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

C++初阶-list的底层

目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

管理学院权限管理系统开发总结

文章目录 &#x1f393; 管理学院权限管理系统开发总结 - 现代化Web应用实践之路&#x1f4dd; 项目概述&#x1f3d7;️ 技术架构设计后端技术栈前端技术栈 &#x1f4a1; 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 &#x1f5c4;️ 数据库设…...

现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?

现有的 Redis 分布式锁库&#xff08;如 Redisson&#xff09;相比于开发者自己基于 Redis 命令&#xff08;如 SETNX, EXPIRE, DEL&#xff09;手动实现分布式锁&#xff0c;提供了巨大的便利性和健壮性。主要体现在以下几个方面&#xff1a; 原子性保证 (Atomicity)&#xff…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...