颠覆传统:基于全文索引驱动下的高效一对多表结构设计!

news2024/9/20 1:08:17

首发公众号:赵侠客

引言

在数据库表结构设计中,一对多(1:N)关系的处理是非常常见需求,如一个用户有多个分类或角色。传统关系型数据库表设计方式通常要包括三张表:用户表、分类表、以及用户与分类之间的关联关系表,主要是通过关系表来存储一对多的关系。虽然这种表结构设计符合数据库设计种的范式要求,但是在进行分类查询时往往需要执行复杂的关联查询,这会对性能造成影响,对开发人员SQL水平要求较高并且需要经常的优化慢SQL,同时也会增加未来对数据库分库分表时的复杂度。为了解决这个问题,本文提出了一种改进性的单表设计方案,通过对表添加全文索引,可以更简单地通过单表解决一对多的关系,避免关联查询,并减少对数据库结构的依赖,实现更高效、简单的数据库查询。

二、传统表设计

一对多(1:N)的关系就指一个表中的记录可以与另一个表中的多个记录相关联,常见的场景有用户分类、文章打标签、用户角色等,这种应用场景有一个特点:1的数据量往往远远大于N的数据量,这里我们拿最常见的用户分类应用场景为例看看传统表结构是怎么设计。

2.1、创建分类表

针对用户分类场景首先我们需要创建一张分类表user_type,存储用户的分类信息

CREATE TABLE `user_type` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `type_name` varchar(200) NOT NULL DEFAULT '' COMMENT '用户分类名',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB COMMENT='用户分类'

插入测试数据 :

INSERT INTO `user_type`(`id`, `type_name`) VALUES (1, '90后');
INSERT INTO `user_type`(`id`, `type_name`) VALUES (2, 'JAVA开发');
INSERT INTO `user_type`(`id`, `type_name`) VALUES (3, '程序员');
INSERT INTO `user_type`(`id`, `type_name`) VALUES (4, '前端');
INSERT INTO `user_type`(`id`, `type_name`) VALUES (5, '后端');
INSERT INTO `user_type`(`id`, `type_name`) VALUES (6, '测试');
INSERT INTO `user_type`(`id`, `type_name`) VALUES (7, 'HR');
INSERT INTO `user_type`(`id`, `type_name`) VALUES (8, '80后');
INSERT INTO `user_type`(`id`, `type_name`) VALUES (9, '富二代');

2.2、创建用户名

然后我们需要创建一张用户表存储用户信息,数据库设计的第一范式要求每个字段都是不可再分的原子值。所有字段值必须是单一的,不可拆分的。

CREATE TABLE `user` (
  `id` bigint(255) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(200) NOT NULL DEFAULT '' COMMENT '用户名',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB COMMENT='用户表'

插入测试数据 :

INSERT INTO `user`(`id`, `user_name`) VALUES (1, '赵侠客');
INSERT INTO `user`(`id`, `user_name`) VALUES (2, '赵云');
INSERT INTO `user`(`id`, `user_name`) VALUES (3, '马超');
INSERT INTO `user`(`id`, `user_name`) VALUES (4, '刘备');
INSERT INTO `user`(`id`, `user_name`) VALUES (5, '关羽');
INSERT INTO `user`(`id`, `user_name`) VALUES (6, '张飞');
INSERT INTO `user`(`id`, `user_name`) VALUES (7, '曹操');
INSERT INTO `user`(`id`, `user_name`) VALUES (8, '马云');
INSERT INTO `user`(`id`, `user_name`) VALUES (9, '子龙');

2.3、创建用户分类关系表

在关系型数据库设计中当一个用户有多个分类时我们需要创建一个关联关系表用于记录用户与分类的关系:

CREATE TABLE `user_type_relation` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `user_id` bigint(24) NOT NULL COMMENT '用户ID',
  `type_id` bigint(24) NOT NULL COMMENT '用户分类ID',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `indx_user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB  COMMENT='用户分类关联表'

添加用户分类数据 :

INSERT INTO `user_type_relation`(`id`, `user_id`, `type_id`) VALUES (1, 1, 1);
INSERT INTO `user_type_relation`(`id`, `user_id`, `type_id`) VALUES (2, 1, 2);
INSERT INTO `user_type_relation`(`id`, `user_id`, `type_id`) VALUES (3, 1, 3);
INSERT INTO `user_type_relation`(`id`, `user_id`, `type_id`) VALUES (4, 2, 1);
INSERT INTO `user_type_relation`(`id`, `user_id`, `type_id`) VALUES (5, 2, 3);
INSERT INTO `user_type_relation`(`id`, `user_id`, `type_id`) VALUES (6, 2, 4);
INSERT INTO `user_type_relation`(`id`, `user_id`, `type_id`) VALUES (7, 2, 5);

2.4、查询

有了上述三张表后,基本上所有的查询需求我们都可以通过复杂的关联查询查出来的,如以以几个查询需求:

90后不是富二代的JAVA程序员

90后的type_id=1,JAVA程序员的type_id=2,富二代的type_id=9,使用关联查询SQL为:

查询用户:

SELECT
	u.id,
	u.user_name 
FROM
	USER u
	JOIN user_type_relation utr1 ON u.id = utr1.user_id 
	AND utr1.type_id = 1
	JOIN user_type_relation utr2 ON u.id = utr2.user_id 
	AND utr2.type_id = 2
	LEFT JOIN user_type_relation utr4 ON u.id = utr4.user_id 
	AND utr4.type_id IN ( 9 ) 
WHERE
	utr4.user_id IS NULL 
GROUP BY
	u.id 
ORDER BY
	u.id ASC 
	LIMIT 10;

我在1C1G的个人测试服务器上生成了100条分类,10万条用户数据,每个用户有三个分类,这条SQL耗时0.141S

90后不是富二代的JAVA程序员

查询用户总量:

SELECT 
    COUNT(DISTINCT u.id) AS result_count
FROM
    user u
    JOIN user_type_relation utr1 ON u.id = utr1.user_id 
        AND utr1.type_id = 1
    JOIN user_type_relation utr2 ON u.id = utr2.user_id 
        AND utr2.type_id = 2
    LEFT JOIN user_type_relation utr4 ON u.id = utr4.user_id 
        AND utr4.type_id IN (9) 
WHERE
    utr4.user_id IS NULL;

用户数量统计耗时0.178s:

用户数量统计

不是富二代的90后和JAVA程序员

查询用户:

SELECT
	u.id,u.user_name
FROM
	user u
	LEFT JOIN user_type_relation utr ON u.id = utr.user_id 
WHERE
	utr.type_id IN ( 1, 2 ) 
	AND utr.type_id NOT IN ( 9 ) 
GROUP BY
	u.id 
ORDER BY
	u.id ASC 
	LIMIT 10

关联查询耗时0.162s

查询用户总量:

SELECT
	COUNT(DISTINCT u.id)
FROM
	user u
	LEFT JOIN user_type_relation utr ON u.id = utr.user_id 
WHERE
	utr.type_id IN ( 1, 2 ) 
	AND utr.type_id NOT IN ( 9 ) 

统计数量耗时:0.148ms

三、基于全文索引表结构设计

3.1、全文索引

说到全文索引大都数人第一时间想到的可能是ElasticSearch,主要是通过分词后建立倒排序索引完成对自然语言的搜索。在Mysql中也是支持全文索引的,这里简单介绍一下Mysql的全文索引,后面可以新写一篇专门介绍如何使用Mysql全文索引,Mysql中全文索引主要支持以下两种分词器:

默认分词(自然语言分词)

默认分词器基于空格、标点符号、停止词的来分隔单词,主要针对英文按单词分词,对中文这种没有空格的语言就难搞。

使用方法:

CREATE FULLTEXT INDEX idx_content ON articles(content);ALTER TABLE articles ADD FULLTEXT INDEX idx_content(content);

ngram分词

ngram分词器将文本分解成所有可能的连续字符子串(n-gram),其中n是一个指定的数字,表示子串的长度。它不会区分单词边界,因此适用于没有明确单词分隔符的语言,如中文、日文和韩文。它为每个n-gram创建索引条目,可以用于执行全文搜索。它适用于中文、日文、韩文等不使用空格分隔单词的语言。适用于需要按字符序列进行搜索的场景。
Mysql默认ngram分词器的长度为2:

查询默认ngram分词器的长度:

show VARIABLES like 'ngram_token_size'
SET GLOBAL ngram_token_size = 2;

使用方法:

ALTER TABLE `test_user`
ADD FULLTEXT INDEX `idx_user_type` (`user_type`) WITH PARSER ngram 

MySQL 全文索引支持三种查询模式:

1. 布尔模式(IN BOOLEAN MODE);

select * from test_user where MATCH (user_type) against ('+01' in Boolean MODE);

常见的Boolea匹配模式有:

  • +操作、必须出现
  • -操作、必须不出现
  • ~操作、负相关性
  • >操作、增加相关性
  • <操作、减少相关性
  • *操作、(通配符)
  • “”、短语查询
  • 小括号、分组查询
  • 空格、多个关键词

2. 自然语言模式(NATURAL LANGUAGE MODE);

select * from test_user where MATCH (user_type) against ('01' IN NATURAL LANGUAGE MODE);

3. 查询拓展(QUERY EXPANSION);

SELECT *
FROM your_table
WHERE MATCH(your_column) AGAINST('+your_search_query' WITH QUERY EXPANSION);

本文主要使用ngram分词+布尔模式查询

3.2、添加编码字段

为了使用单表可以查出关联的数据我们需要对用户表添加一个字段user_type_ngram记录用户的分类,然后对用户的分类进行编码,如用户ID=1的赵侠客有type_id=40、2、8三个分类,我们将user_type_ngram编码40#02#08,因为ngram默认长度为2,所以2和8必需要编码成2位长度02和08后才能被分词搜索出来。

ALTER TABLE user
ADD COLUMN `user_type_ngram` varchar(255) NOT NULL DEFAULT '' COMMENT '用户分类编码' ;

完成编码后的user表

3.3、添加全文索引

当添加user_type_ngram字段后我们需要对该字段添加一个全文索引,指定分词器类型为ngram:

ALTER TABLE `user`
ADD FULLTEXT INDEX `idx_user_type_ngram` (`user_type_ngram`) WITH PARSER ngram;

3.4、查询

有了idx_user_type_ngram这个全文索引后我们就可以使用单表完成各种多表关联查询的需要了

90后不是富二代的JAVA程序员

这条需求就是查询用户分类ID必需有1和2并且不能有9,我们使用全文索引的布尔模式为:(+01 +02 -09)

SELECT
	id,user_name
FROM
user
WHERE
	MATCH ( user_type_ngram ) AGAINST ( '+01 +02 -09' IN BOOLEAN MODE ) 
ORDER BY
	id 
	LIMIT 10

因为使用了全文索引,查询速度也是非常快的,在机器非常差的情况下耗时0.059S,比使用多表关联查询耗时0.141S快了1倍多。

全文索引耗时0.059S

统计用户数量:

SELECT
	count(*) AS user_cout 
FROM
user
WHERE
	MATCH ( user_type_ngram ) AGAINST ( '+01 +02 -09' IN BOOLEAN MODE ) 

耗时:0.019s比关联查询0.178快了一个数量级

全文索引统计用户数量

不是富二代的90后和JAVA程序员

这里要查出90后或者是JAVA程序员可以用+(01 02) 然后去除富二代用 -09

SELECT
	id,
	user_name 
FROM
	user
WHERE
	MATCH ( user_type_ngram ) AGAINST ( '+(01 02)  -09 ' IN BOOLEAN MODE ) 
ORDER BY
	id 
	LIMIT 10

可以看出这条SQL耗时0.084S比使用关联查询的0.162s也是快了一倍

不是富二代的90后和JAVA程序员

统计数量:

SELECT
 count(*)
FROM
	user
WHERE
	MATCH ( user_type_ngram ) AGAINST ( '+(01 02)  -09 ' IN BOOLEAN MODE ) 

耗时0.025S比关联查询数量统计0.148ms快了一个数量级
统计数量

总结

本文介绍了基于Mysql文本索引使用单表查询解决在关系型数据一对多的应用场景下使用多表设计导致关联查询SQL变复杂、查询性能变慢的问题,这种设计主要有以下优点和缺点:

优点:

  • 查询简单、几乎所有的查询都可以走单表,通过Boolean匹配可以很简单的实现以前联合多表的复杂查询
  • 提升性能、使用了全文索引+单表在查询性能上比单表要快很多
  • 数据一致性、全文索引是Mysql本身就支持的一种索引类型,不需将数据同步到像ElastiSearch这样的数据库,还要解决数据一致性的问题

缺点:

  • 索引空间变大、因为要创建倒排序索引所以索引空间会大大增加
  • 影响更新字段性能、更新字段后对应的倒排索引也需要更新这会对更新性能产生一定的影响
  • 字段类型修改、不像ElastiSearch字段类型不可修改,MySQL中是可以修改的,但是创建全文索引后修改字段类型,全文索引中是不会立刻更新的,全量更新全文索引需要耗费大量性能

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

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

相关文章

LC开源电路的学习(一)

TI的升压芯片&#xff0c;电压虽然能升高&#xff0c;但是带来的问题就是最大电流大幅降低&#xff1a; CC1和CC2芯片接快充芯片之后&#xff0c;直接接到单片机的下载口&#xff1a; 这个有点意思&#xff0c;用导线换电阻&#xff1a; 、 PD快充芯片CH224K需要连接typeC的D…

华为云征文|基于Flexus云服务器X实例的应用场景-部署脚手架开源项目若依

&#x1f534;大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂 先看这里 写在前面**Flexus X实例**的云服务器简介环境准备若依项目拉取导入数据库启动本地项目&#xff08;后端&#xff09;启动本地项目&#xff08;前端&#xff09;打包后…

图片转为PDF怎么弄?看这里,三款软件助你一键转换!

嘿&#xff0c;朋友们&#xff01;现在信息这么多&#xff0c;图片在我们学习、工作、生活中帮了大忙。但有时候&#xff0c;我们想把图片整理好、分享给别人或者打印出来&#xff0c;PDF格式就特别合适。PDF文件不管在哪儿打开&#xff0c;内容都不会变样&#xff0c;还能加密…

Pandas 9-绘制柱状图

1. 准备数据 首先&#xff0c;需要准备一个DataFrame。 import pandas as pd # 创建一个DataFrame data { Name: [Alice, Bob, Charlie, David], Age: [24, 27, 22, 32], City: [New York, Los Angeles, Chicago, Houston], Score: [85, 92, 78, 88]} df pd.…

【生成模型系列(初级)】自编码器——深度学习的数据压缩与重构

【通俗理解】自编码器——深度学习的数据压缩与重构 第一节&#xff1a;自编码器的类比与核心概念 1.1 自编码器的类比 你可以把自编码器想象成一个“智能压缩机”&#xff0c;它能够把输入的数据&#xff08;比如图片&#xff09;压缩成一个更小的表示&#xff08;编码&#…

超声波模块

HCSR04超声波模块是一种常用的测距模块&#xff0c;它通过检测超声波发射后遇到障碍物所反射的回波&#xff0c;从而测量出与障碍物之间的距离。以下是对HCSR04超声波模块的详细讲解&#xff1a; 一、模块组成与工作原理 组成&#xff1a;HCSR04超声波模块主要由两个压电陶瓷超…

【我的Android进阶之旅】快来给你的Kotlin代码添加Markdown格式的注释吧!

文章目录 一、 传统 HTML 格式注释二、 Markdown 格式注释三.、Markdown格式注释详解3.1. 基础语法3.1.1 单行注释3.1.1 多行注释3.2 标题3.3 列表3.4 加粗和斜体3.5 代码块3.6 链接3.7 引用3.8 表格3.9. 图片3.10. 示例代码3.11. 注释模板的使用场景3.12 实例示例四、总结在 A…

2024年9月深圳200万~300万的三房笔记

​整理了2024年9月深圳200万~300万的三房笔记&#xff0c;数据可能有​出入。有些商品房数据是我看到工抵房的数据&#xff0c;群里说工抵房的房价数据需要乘以1.2就比较接近当前现场的价格​。对于我个人来说关注地铁&#xff0c;即是否方便打工还有价格​。看着一些商品房的工…

【华三】不懂链路聚合?看这篇就够了!华三配置详解

【华三】不懂链路聚合&#xff1f;看这篇就够了&#xff01;华三配置详解 背景链路聚合基本概念聚合组和聚合接口的类型二层聚合组/二层聚合接口三层聚合组/三层聚合接口聚合接口特性 聚合接口参考端口成员端口 操作Key成员端口的配置分类协议类配置-第一类配置属性配置-第二类…

Day 1 : 数据结构

引入 以张三为例&#xff1a;CEF不能同时举办。 数据的逻辑结构 数据结构是什么 研究计算机数据之间的关系 逻辑结构和存储结构及其操作 基本概念 数据 数据元素 逻辑结构 按前趋和后继数将逻辑结构分为&#xff1a;线性结构和非线性结构。 即&#xff1a;找前…

云首席产品规划专家的必修课

大家好&#xff0c;我是卢旗。 今天和大家探讨一下云计算规划专家的必备研究内容。 一、市场与客户需求分析&#xff1a; 1&#xff0c;市场调研&#xff1a;深入了解当前及未来云计算市场的趋势、竞争格局、客户需求变化等&#xff0c;识别出潜在的市场机会和威胁。 结合20…

C++ string类—容量、元素获取

一、Capacity size&#xff1a;string类对象中字符的个数为size&#xff1b;length&#xff1a;size作用一样&#xff0c;返回string对象中有效数据个数&#xff1b;capacity:一个string对象的容量capacity决定了这个对象能存储多少字符&#xff08;不包括\0&#xff09;&…

活动系统开发之采用设计模式与非设计模式的区别-需求整理

用户需求(活动系统)&#xff1a; 1、活动类型&#xff1a;答题、图片展示、签到、抽奖、组团等活动 2、活动介绍&#xff1a; a、答题活动&#xff1a; 第一种是签到后&#xff0c;随机抽取10道题&#xff0c;答对8到就可以抽奖&#xff1b; 第二种是随机抽取一道题&#xff0…

Python优化算法24——基于觅食生境选择的粒子群算法(FHSPSO)

科研里面优化算法都用的多&#xff0c;尤其是各种动物园里面的智能仿生优化算法&#xff0c;但是目前都是MATLAB的代码多&#xff0c;python几乎没有什么包&#xff0c;这次把优化算法系列的代码都从底层手写开始。 需要看以前的优化算法文章可以参考&#xff1a;Python优化算…

c/c++基础及类和对象汇总

目录 c基础 extern关键字及c中&#xff08;隐式类型转换时&#xff09;的引用 c中的引用&#xff08;&&#xff09;及&做返回值问题 c语言中的宏函数及c的内联函数及auto及NULL 计算类的大小及深入理解this指针&#xff08;深入浅出&#xff09; c中的const权限及s…

C语言典型例题58

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 例题4.10 求100~200中的全部素数。 代码&#xff1a; //《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 //例题4.10 求100~200中的全部素数。#include <stdio.h> #include <math.h>int m…

python学习11-Pytorch环境安装与模型搭建

先查看下自己的电脑是否是英伟达显卡 如果不是就需要租用平台了,如 AutoDL算力云 https://www.autodl.com/home CUDA 当涉及到深度学习和 Python 时&#xff0c;CUDA 是一个非常重要的概念&#xff0c;它是 NVIDIA 开发的并行计算平台和应用程序编程接口&#xff08;API&am…

【WiFi主要技术学习2】

WiFi协议学习2 WiFi SPEC理解频段信道带宽协商速率安全与加密WiFi主要技术理解BP直接序列扩频(Direct Sequence Spread Spectrum,DSSS)BPSKQPSK正交幅度调制(Quadrature Amplitude Modulation,QAM)互补码键控(Complementary Code Keying,CCK)正交频分复用(Orthogonal…

如何选择合适的JDK:功能、性能与适用场景的全面解析

如何选择合适的JDK&#xff1a;功能、性能与适用场景的全面解析 前言 在 Java 开发领域&#xff0c;开发者有众多的 JDK 选择&#xff0c;如 OpenJDK、GraalVM、Oracle JDK、Dragonwell、Kona、Bisheng、Corretto、Zulu、Liberica、SapMachine、Semeru、Temurin、Mandrel等。 …

YOLOv8改进 | Conv篇 | YOLOv8引入SAConv模块

1. SAConv介绍 1.1 摘要: 许多现代物体检测器通过使用三思而后行的机制表现出出色的性能。 在本文中,我们在目标检测的主干设计中探索了这种机制。 在宏观层面,我们提出了递归特征金字塔,它将特征金字塔网络的额外反馈连接合并到自下而上的骨干层中。 在微观层面,我们提出…