Mysql存储-EAV模式
Mysql存储-EAV模式
最近又又又搞一点新东西,要整合不同业务进行存储和查询,一波学习过后总结了一下可扩展性MAX的eav模式存储。
在eav这里的数据结构设计尤为关键,需要充分考虑你需要使用的字段、使用场景,当数据结构设计完成后便会发现eav模型需要多次join操作才能完成查询,因此性能优化的难点也是在如何充分使用索引
一、简介
1、概念
EAV(Entity-Attribute-Value)模式,也称为对象-属性-值模式,是一种常用于数据库设计的灵活模式,适用于具有大量属性和属性值的实体。它在MySQL数据库中的实现可以解决一些传统关系型数据库表结构无法轻松满足的需求,例如动态属性、稀疏属性等。
EAV模式的核心思想是将实体(Entity)的属性(Attribute)和值(Value)分别存储在不同的表中。这样可以在不修改表结构的情况下轻松添加或删除属性,从而提高数据库的灵活性。
EAV模式在MySQL数据库中通常包含以下三个表:
- 实体表(Entity Table):存储实体的基本信息,如ID、名称等。每个实体对应该表中的一行记录。
- 属性表(Attribute Table):存储属性的元数据,如属性ID、属性名称、数据类型等。每个属性对应该表中的一行记录。
- 值表(Value Table):存储实体的属性值。每个属性值对应该表中的一行记录,包括实体ID、属性ID和属性值。

2、特点
EAV模式的优点:
- 高度灵活:可以轻松添加、删除或修改属性,而无需更改表结构。
- 节省存储空间:对于具有大量稀疏属性的实体,EAV模式可以避免在数据表中存储大量NULL值。
EAV模式的缺点:
- 查询复杂:由于属性和值分散在多个表中,查询和聚合操作通常需要多表连接,导致查询性能较差。
- 数据完整性: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 的包管理工具,基于命令行,用于安装、升级、移除、管理依赖项。 常用命令: npm init:初始化一个新的 npm 项目,创建 package.json 文件。(括号里为默认值) description&am…...
周总结【java项目】
项目进度: 学习了JavaFX,下载了sceneBuilder辅助工具构建窗口(目前建立了登陆,注册,忘记密码的界面),然后是学习了MySQL的连接,现在的项目是刚连上数据库; 下一步&…...
《深度不确定条件下的决策:从理论到实践》PDF
制定未来计划时需要预测变化,尤其是制定长期计划或针对罕见事件的计划时。当这些变化存在高度不确定性的时候,这种预期就变得越来越困难。 今天给大家介绍的这本《深度不确定条件下的决策:从理论到实践》正是解决以上问题的良方。完整书籍文…...
【MySQL】表的基础增删改查
前面我们已经知道怎么来创建表了,接下来就来对创建的表进行一些基本操作。 这里先将上次创建的表删除掉: 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源码分析
记录问题:当配置了 mybatis.type-aliases-packagecom.runjing.erp.entity 配置项时,如果entity文件夹下存在不同子文件夹下的同名类型时,mybatis初始化加载映射时会爆出org.apache.ibatis.type.TypeException: The alias TestDemo…...
淘宝商品评论数据分析接口,淘宝商品评论接口
淘宝商品评论数据分析接口可以通过淘宝开放平台API获取。 通过构建合理的请求URL,可以向淘宝服务器发起HTTP请求,获取商品评论数据。接口返回的数据一般为JSON格式,包含了商品的各种评价信息。 获取到商品评论数据后,可以对其进…...
RK3288 android7.1 修改双屏异触usb tp触摸方向
一,问题描述: android机器要求接两个屏(lvdsmipi)两个usb tp要实现双屏异触。由于mipi的方向和lvds方向转成一样的了。两个usb tp的方向在异显示的时候也要作用一样。这个时候要根据pid和vid修改触摸上报的数据。usb tp有通用的触…...
全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...
前端高频面试题2:浏览器/计算机网络
本专栏相关链接 前端高频面试题1:HTML/CSS 前端高频面试题2:浏览器/计算机网络 前端高频面试题3:JavaScript 1.什么是强缓存、协商缓存? 强缓存: 当浏览器请求资源时,首先检查本地缓存是否命中。如果命…...
Java并发编程实战 Day 11:并发设计模式
【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天,今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案,它们不仅提供了优雅的设计思路,还能显著提升系统的性能…...
Python环境安装与虚拟环境配置详解
本文档旨在为Python开发者提供一站式的环境安装与虚拟环境配置指南,适用于Windows、macOS和Linux系统。无论你是初学者还是有经验的开发者,都能在此找到适合自己的环境搭建方法和常见问题的解决方案。 快速开始 一分钟快速安装与虚拟环境配置 # macOS/…...
