96SEO 2026-02-19 11:01 33
自定义udfudtfudaf讲一下这几个函数的区别编写的时候要继承什么类实现什么方法8

工作中遇到数据倾斜处理过吗是怎么处理的针对你刚刚提的方案讲一下具体怎么实现。
用代码实现以及用sql实现。
19
讲一下spark是如何将一个sql翻译成代码执行的里面的原理介绍一下21
有这样一个需求统计一个用户的已经曝光了某一个页面想追根溯是从哪几个页面过来的然后求出在这几个来源所占的比例。
你要怎么建模处理23
说一下你门公司的数据是怎么分层处理的每一层都解决了什么问题26
包括向yarn申请资源、DAG切割、TaskScheduler、执行task等过程
用户在Driver端提交任务初始化运行环境(SparkContext等)Driver根据配置向ResoureManager申请资源(executors及内存资源)ResoureManager资源管理器选择合适的worker节点创建executor进程Executor向Driver注册并等待其分配task任务Driver端完成SparkContext初始化创建DAG分配taskset到Executor上执行。
Executor启动线程执行task任务返回结果。
Driver端提交程序并向Yarn申请ApplicationYarn接受请求响应在NodeManager节点上创建AppMasterAppMaster向Yarn
ResourceManager申请资源(Container)选择合适的节点创建Container(Executor进程)后续的Driver启动调度运行任务
详细原理见上图。
我们使用spark-submit提交一个Spark作业之后这个作业就会启动一个对应的Driver进程。
根据你使用的部署模式deploy-mode不同Driver进程可能在本地启动也可能在集群中某个工作节点上启动。
Driver进程本身会根据我们设置的参数占有一定数量的内存和CPU
core。
而Driver进程要做的第一件事情就是向集群管理器可以是Spark
Standalone集群也可以是其他的资源管理集群美团•大众点评使用的是YARN作为资源管理集群申请运行Spark作业需要使用的资源这里的资源指的就是Executor进程。
YARN集群管理器会根据我们为Spark作业设置的资源参数在各个工作节点上启动一定数量的Executor进程每个Executor进程都占有一定数量的内存和CPU
在申请到了作业执行所需的资源之后Driver进程就会开始调度和执行我们编写的作业代码了。
Driver进程会将我们编写的Spark作业代码分拆为多个stage每个stage执行一部分代码片段并为每个stage创建一批task然后将这些task分配到各个Executor进程中执行。
task是最小的计算单元负责执行一模一样的计算逻辑也就是我们自己编写的某个代码片段只是每个task处理的数据不同而已。
一个stage的所有task都执行完毕之后会在各个节点本地的磁盘文件中写入计算中间结果然后Driver就会调度运行下一个stage。
下一个stage的task的输入数据就是上一个stage输出的中间结果。
如此循环往复直到将我们自己编写的代码逻辑全部执行完并且计算完所有的数据得到我们想要的结果为止。
Spark是根据shuffle类算子来进行stage的划分。
如果我们的代码中执行了某个shuffle类算子比如reduceByKey、join等那么就会在该算子处划分出一个stage界限来。
可以大致理解为shuffle算子执行之前的代码会被划分为一个stageshuffle算子执行以及之后的代码会被划分为下一个stage。
因此一个stage刚开始执行的时候它的每个task可能都会从上一个stage的task所在的节点去通过网络传输拉取需要自己处理的所有key然后对拉取到的所有相同的key使用我们自己编写的算子函数执行聚合操作比如reduceByKey()算子接收的函数。
这个过程就是shuffle。
当我们在代码中执行了cache/persist等持久化操作时根据我们选择的持久化级别的不同每个task计算出来的数据也会保存到Executor进程的内存或者所在节点的磁盘文件中。
因此Executor的内存主要分为三块第一块是让task执行我们自己编写的代码时使用默认是占Executor总内存的20%第二块是让task通过shuffle过程拉取了上一个stage的task的输出后进行聚合等操作时使用默认也是占Executor总内存的20%第三块是让RDD持久化时使用默认占Executor总内存的60%。
core同一时间只能执行一个线程。
而每个Executor进程上分配到的多个task都是以每个task一条线程的方式多线程并发运行的。
如果CPU
core数量比较充足而且分配到的task数量比较合理那么通常来说可以比较快速和高效地执行完这些task线程。
以上就是Spark作业的基本运行原理的说明大家可以结合上图来理解。
理解作业基本原理是我们进行资源参数调优的基本前提。
筛选出满足函数func的元素并返回一个新的数据集map(func)
将每个元素传递到函数func中并将结果返回为一个新的数据集flatMap(func)
与map()相似但每个输入元素都可以映射到0或多个输出结果groupByKey()
Iterable)形式的数据集reduceByKey(func)
V)形式的数据集其中每个值是将每个key传递到函数func中进行聚合后的结果
通过函数func输入两个参数并返回一个值聚合数据集中的元素foreach(func)
所有的transformation都是采用的懒策略就是如果只是将transformation提交是不会执行计算的计算只有在action被提交的时候才被触发。
变换/转换这种变换并不触发提交作业完成作业中间过程处理。
Transformation
map(func):对调用map的RDD数据集中的每个element都使用func然后返回一个新的RDD,这个返回的数据集是分布式的数据集filter(func):
对调用filter的RDD数据集中的每个元素都使用func然后返回一个包含使func为true的元素构成的RDDflatMap(func):和map差不多但是flatMap生成的是多个结果mapPartitions(func):和map很像但是map是每个element而mapPartitions是每个partitionmapPartitionsWithSplit(func):和mapPartitions很像但是func作用的是其中一个split上所以func中应该有indexsample(withReplacement,faction,seed):抽样union(o***rDataset)返回一个新的dataset包含源dataset和给定dataset的元素的集合distinct([numTasks]):返回一个新的dataset这个dataset含有的是源dataset中的distinct的elementgroupByKey(numTasks):返回(K,Seq[V])也就是hadoop中reduce函数接受的key-valuelistreduceByKey(func,[numTasks]):就是用一个给定的reducefunc再作用在groupByKey产生的(K,Seq[V]),比如求和求平均数sortByKey([ascending],[numTasks]):按照key来进行排序是升序还是降序ascending是boolean类型join(o***rDataset,[numTasks]):当有两个KV的dataset(K,V)和(K,W)返回的是(K,(V,W))的dataset,numTasks为并发的任务数cogroup(o***rDataset,[numTasks]):当有两个KV的dataset(K,V)和(K,W)返回的是(K,Seq[V],Seq[W])的dataset,numTasks为并发的任务数cartesian(o***rDataset)笛卡尔积就是m*n大家懂的
reduce(func)说白了就是聚集但是传入的函数是两个参数输入返回一个值这个函数必须是满足交换律和结合律的collect()一般在filter或者足够小的结果的时候再用collect封装返回一个数组count():返回的是dataset中的element的个数first():返回的是dataset中的第一个元素take(n):返回前n个elements这个士driverprogram返回的takeSample(withReplacementnumseed)抽样返回一个dataset中的num个元素随机种子seedsaveAsTextFilepath把dataset写到一个textfile中或者hdfs或者hdfs支持的文件系统中spark把每条记录都转换为一行记录然后写到file中saveAsSequenceFile(path):只能用在key-value对上然后生成SequenceFile写到本地或者hadoop文件系统countByKey()返回的是key对应的个数的一个map作用于一个RDDforeach(func):对dataset中的每个元素都使用func
map执行完map后会得到一个新的分布式数据集数据集中每个元素是之前的RDD映射得来的与之前RDD每个元素存在一一对应的关系。
flatmap而flatmap有一点不同每个输入的元素可以被映射为0个或者多个输出的元素原RDD与新RDD的元素是一对多的关系。
当然光看定义比较抽象下面用一个图说明
自定义udfudtfudaf讲一下这几个函数的区别编写的时候要继承什么类实现什么方法
UDF用户定义普通函数只对单行数值产生作用UDTF输入一行输出多行类似explode函数
Functions用户定义表生成函数用来解决输入一行输出多行UDAF输入多行输出一行类似聚合函数
Funcation用户定义聚合函数可对多行数据产生作用等同与SQL中常用的SUM()AVG()也是聚合函数
org.apache.hadoop.hive.ql.udf.generic
org.apache.hadoop.hive.ql.udf.generic
org.apache.hadoop.hive.ql.udf.generic
org.apache.hadoop.hive.ql.udf.generic
org.apache.hadoop.hive.ql.udf.generic
-----AbstractGenericUDAFResolver-----
---------GenericUDAFEvaluator----------
--------AbstractAggregationBuffer-------
一个Buffer作为中间处理数据的缓冲获取getNewAggregationBuffer、重置reset四个阶段Mode
初始化init遍历iteratePARTIAL1和COMPLETE阶段合并mergePARTIAL2和FINAL阶段终止terminatePartialPARTIAL1和PARTIAL2阶段terminateCOMPLETE和FINAL阶段
如果这个临时表并不需要保存并且下文只需要用有限的几次我们可以采用下面的方法。
也叫做子查询部分首先定义一个sql片段该sql片段会被整个sql语句所用到为了让sql语句的可读性更高些作为提供数据的部分也常常用在union等集合操作中。
as就类似于一个视图或临时表可以用来存储一部分的sql语句作为别名不同的是with
其最大的好处就是适当的提高代码可读性而且如果with子句在后面要多次使用到这可以大大的简化SQL更重要的是一次分析多次使用这也是为什么会提供性能的地方达到了“少读”的目标。
as语句后不能加分号不然会报错。
with子句必须在引用的select语句之前定义,同级with关键字只能使用一次,多个只能用逗号分割如果定义了with子句但其后没有跟select查询则会报错前面的with子句定义的查询在后面的with子句中可以使用。
但是一个with子句内部不能嵌套with子句
创建的临时表仅仅在当前会话可见数据会被暂存到hdfs上退出当前会话表和数据将会被删除。
数据将存储在用户的scratch目录中并在会话结束时删除。
从Hive1.1开始临时表可以存储在内存或SSD使用hive.exec.temporary.table.storage参数进行配置该参数有三种取值memory、ssd、default。
如果临时表的命名的表名和hive的表名一样当前会话则会查询临时表的数据用户在这个会话内将不能使用原表除非删除或者重命名临时表临时表不支持分区字段不支持创建索引。
第一范式列的原子性字段值不可再分比如某个字段的取值是姓名手机号那就要把姓名和手机号分成两个字段第二范式第一范式的基础上非主键列不能依赖主键的一部分例如字段a和字段b组成的主键某个字段只依赖a就需要把这个字段剥离到a对应的表第三范式第二范式的基础上非主键列不能传递依赖主键例如字段c依赖字段b字段b依赖主键字段a那么就可以把这个字段c剥离到字段b为主键的表
三范式是要解决字段冗余节省存储空间数据维护更方便不需要多处更新同样的字段
范式化设计的优点可以减少数据冗余数据表体积小更新快范式化的更新操作比
缺点对于查询需要对多个表会关联多个表在应用中进行表关联的成本是很高
反范式化设计的优点可以减少表的关联可以对查询更好的进行索引优化
缺点表结构存在数据冗余和数据维护异常对数据的修改需要更多资源。
参考你了解数据库三大范式吗用来解决什么问题_数据库三范式解决了什么问题_我是等闲之辈的博客-CSDN博客mysql--数据库优化的目的、数据库设计的步骤以及什么是三范式、三范式的优缺点-CSDN博客
维度建模从分析决策的需求出发构建模型为分析需求服务因此它重点关注用户如何更快速地完成需求分析同时具有较好的大规模复杂查询的响应性能。
其典型的代表是星形模型以及在一些特殊场景下使用的雪花模型。
其设计分为以下几个步骤。
选择粒度。
在事件分析中我们要预判所有分析需要细分的程度从而决定选择的粒度。
粒度是维度的一个组合。
识别维表。
选择好粒度之后就需要基于此粒度设计维表包括维度属性用于分析时进行分组和筛选。
按是否规范化设计划分分符合三范式的维表和反规范化的维表对应雪花模型和星型模型按是否包含属性层次结构分分包含层次结构的维表和不包含层次结构的维表如行业维表就是包含层次结构的维表递归层次一般采用①扁平化、②层次桥接表两种方式设计按水平拆分基于业务类型可以划分为主维表存放公共属性子维度表包含公共属性和特有属性按垂直拆分基于性能可以划分为主维表存放使用频率高的属性扩展维表存放使用频率低的属性按是否归档处理分为历史维度表普通表按缓慢变化维划分一种是使用代理键的就是Kimball中的8种类型的缓慢变化维度如果不使用代理键则划分快照维表、采用极限存储的维度表一些特殊维度类型支架维度、杂项维度、行为维度事实衍生维度如买家常用地址、多值维度
事务性事实表又分为单事务事实表多事务事实表周期快照事实表又分为单维度的每天快照事实表混合维度的每天快照事实表全量快照事实表累计快照事实表
Dimension中文一般翻译为“一致性维度”。
一致性维度是Kimball的多维体系结构中的三个关键性概念之一另两个是总线架构Bus
总线架构初期进行需求沟通和整体设计的产物汇总一致性维度和业务过程的表格
参考一篇文章搞懂数据仓库总线架构、一致性维度、一致性事实-阿里云开发者社区
Dimensions数仓的特点之一就是反映历史变化与增长较快的事实表相比维度变化相对缓慢。
Kimball整理的处理方法一共有8种但往往只有3种比较常用类型1、类型2、类型3。
类型0属性值不可能变保留原样。
类型1重写覆盖原值类型1不能反映历史。
类型2新增一行并生成新的代理键事实表旧的记录使用旧的代理键新的记录使用新的代理键同时维表增加创建时间和截止时间来标识最新有效的记录和历史记录新的记录有效期可以设置一个极大值如9999.12.31类型2的缺点是旧的记录只能用旧的维度分析新的记录只能用新的维度进行分析。
类型3类型1只能满足新维度分析的需求类型2既不能满足新维度分析的需求也不能满足旧维度分析的需求如果要同时满足新旧两种维度分析的需求可以考虑使用类型3。
类型3是通过两个字段分别存储新旧值来满足这种两种同时都要的需求的但是类型3的问题在如果只变化一次还行但如果变化第2次、第3次就需要增加新的字段会比较麻烦。
类型4微型维度。
解决的是高频变更导致的记录过多的问题剥离高频变化的维度如果是数值可以用范围值来减少数量有多个高频变化的维度就用这些维度的笛卡尔积来组合成维表可以用组合缩写来生成代理键注意微型维度没有自然键。
另外如果需要记录精确值可以考虑无事实的事实表。
需要注意的是类型4只能在事实表中出现如果维度表和微型维度发生的关联那就是类型5。
类型5类型1微型维度。
主维表通过类型1关联微信维度反映最新值微型维度与事实表进行关联通过事实表来反映历史变化。
类型6类型1类型2类型3。
通过类型3构建新旧两列同一个自然键的通过类型1更新为最新值通过类型2处理历史变化。
类型6的缺点是如果这种列很多150个列那就要翻倍来存300个列这是可以考虑类型7。
类型7双类型1类型2。
用类型2生成主维表同时生成一个最新值的视图取9999.12.31事实表中存放两个外键或者单个外键连关联这两张维表。
代理键可以反映当时的变化自然键超自然键可以反映当前最新值。
双重外键一个键存放主维表的代理键反映当时的情况一个键存放最新视图的自然键最好是超自然键反映最新情况。
单外键存的是主维表的代理键视图里存当时的代理键和最新的代理键。
主要是为了记录历史变化并节省存储空间。
适合大数据量场景下变化频率又不高的情况。
如果每天存储全量表很多都是重复的记录造成浪费。
可以应用在维表也可以应用在事实表。
比如某个员工维表为了反映部门历史变动订单事实表为了反映订单状态的历史变化未支付、已支付、已发货、已完成。
有些业务系统的表不保存变更流水记录的只保存最新的值比如订单表只保留订单的最新状态。
如果要反映历史变化需要跟进binlog日志或者定时采集来记录变化。
具体实现步骤
第一步创建并初始化拉链表执行一次第二步合并当日新增和修改的数据到临时表每天执行一次
all拉链表的记录注意要修改历史记录的有效日期。
第三步把临时表覆盖写入拉链表每天执行一次
拉链表如何回滚参考https://www.cnblogs.com/lxbmaomao/p/9821128.html
拉链表的详细实现过程好文点赞收藏_拉链表的实现过程_KG大数据的博客-CSDN博客
https://www.cnblogs.com/lxbmaomao/p/9821128.html
是一种受限的雪花维度是星型模型和雪花模型之间的一种折中如日期维度表、地址维度表使用场景当一个属性集合例如日期、地点在某个维度或多个维度表中反复出现时就可以考虑使用支架表。
使用条件
----------------------小文件合并----------------------
hive.input.formatorg.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
hive.input.formatorg.apache.hadoop.hive.ql.io.HiveInputFormat;
hive.merge.size.per.task256000000;
mapred.max.split.size256000000;
mapred.min.split.size.per.node1;
mapred.min.split.size.per.rack1;
联合起来##1、默认情况先把这个节点上的所有数据进行合并如果合并的那个文件的大小超过了256M就开启另外一个文件继续合并
##2、如果当前这个节点上的数据不足256M那么就都合并成一个逻辑切片。
----------------------Map
mapreduce.input.fileinputformat.split.minsize1
mapreduce.input.fileinputformat.split.maxsize256000000##
合并小文----------------------Reduce
参数1hive.exec.reducers.bytes.per.reducer
hive.mapjoin.smalltable.filesize
hive会基于表的size自动的将普通join转换成mapjoin
hive.auto.convert.join.noconditionaltasktrue;
多大的表可以自动触发放到内层LocalTask中默认大小10M
hive.auto.convert.join.noconditionaltask.size10000000;----------开启bucket
hive.enforce.sortmergebucketmapjoinfalse;
hive.auto.convert.sortmerge.jointrue;
hive.optimize.bucketmapjoinfalse;
hive.optimize.bucketmapjoin.sortedmergefalse;------------------数据倾斜优化-------------------
设置map端预聚合的行数阈值超过该值就会分拆job默认值100000
hive.groupby.mapaggr.checkinterval100000
中最后完成最终的聚合操作。
---------join优化----------
join的键对应的记录条数超过这个值则会进行分拆值根据具体数据量设置
hive.optimize.skewjoinfalse;------------------使用
hive.vectorized.execution.enabledtrue
hive.vectorized.execution.reduce.enabledtrue;---------------本地执行优化------------------
hive.exec.mode.local.auto.input.files.max4;
hive.exec.mode.local.auto.inputbytes.max134217728;---------------并行执行---------------
hive.exec.parallel.thread.number16;---------------严格模式---------------
hive.exec.dynamic.partition.modenostrict;Spark参数
--classcom.abc.sparktuning.utils.InitUtil
spark-tuning-1.0-SNAPSHOT-jar-with-dependencies.jar
spark-submit中通过--master参数指定集群的资源管理器也可以在代码中硬编码指定master通过--deploy-mode参数指定以client模式运行还是以cluster模式运行。
master常用参数有localDriver和Executor运行在同一个节点的同一个JVM中、Spark
Standalone集群Spark自带的简易资源管理器分Master节点和Worker节点、mesos、yarn、k8s。
部署模式client和cluster两种client模式是Driver进程在提交任务的节点运行cluster模式是Driver进程在Woker节点中运行Driver进程。
等组件的余量yarn.nodemanager.resource.cpu-vcores
70所以num-executors是70executor-cores是4
yarn-nodemanager.resource.memory-mb100G那么每个
yarn.scheduler.maxinum-allocation-mb
容器最大内存设置因为executor-memory指定的是堆内内存除了堆内内存还有堆外内存。
两种方式提交Executor和Driver的内存分配情况也是不同的。
Yarn中的ApplicationMaster都启用一个Container来运行
Client模式下的Container默认有1G内存1个cpu核Cluster模式的配置则由driver-memory和driver-cpu来指定也就是说Client模式下的driver是默认的内存值Cluster模式下的dirver则是自定义的配置。
SparkConf().set(spark.serializer,
org.apache.spark.serializer.KryoSerializer)
result.persist(StorageLevel.MEMORY_ONLY_SER)
conf.set(spark.default.parallelism,
spark.default.parallelism只有在处理RDD时才会起作用对Spark
spark.sql.shuffle.partitions则是对Spark
spark.sql.shuffle.partitions500
spark.default.parallelism用户配置同一的并行度统一性
reduceByKey(1000)类shuffle算子具体任务中可以特定配置的并行度。
特定性
spark.sql.shuffle.partitions对sparksql中的joins和aggregations有效但其他的无效针对这种情况可以采用repartition算子对dataframe进行重分区
因为实际情况与理想情况不同的有些task会运行的快一点比如50s就完了有些task可能会慢一点要1分半才运行完所以如果你的task数量刚好设置的跟cpu
core数量相同可能还是会导致资源的浪费因为比如150个task10个先运行完了剩余140个还在运行但是这个时候有10个cpu
core就空闲出来了就导致了浪费。
那如果task数量设置成cpu
core总数的2~3倍那么一个task运行完了以后另一个task马上可以补上来就尽量让cpu
core不要空闲同时也是尽量提升spark作业运行的效率和速度提升性能。
工作中遇到数据倾斜处理过吗是怎么处理的针对你刚刚提的方案讲一下具体怎么实现。
用代码实现以及用sql实现。
过滤无效的导致倾斜的key【适用场景不多】比如过滤null、空字符串、非整数字符串等
方案一剥离null不参与shuffle方案二对null加随机数打散shufflereduce
join【适合小表join大表】spark可以通过broadcast来广播小表增加shuffle并行度【缓解】shuffle算子入reduceByKey(1000)传入并行度数值或者SparkSQL设置spark.sql.shuffle.partitions参数默认200都能提高shuffle
task的并行度两阶段聚合局部聚合整体聚合【适用于聚合类倾斜】
第四步全局聚合。
采样倾斜key并分开join【适合大表join大表一个大表有少数几个key倾斜另一个比较均匀】
通过sample采样出倾斜的key然后包这些key从两张表里剥离出来一张加随机数打散成n份一张扩充n倍进行Join剥离出key剩下的数据进行普通join即可最后把结果合并在一起全部数据加随机数打散N份扩容N倍【大表Join大表扩容导致内存消耗大】对join类型的数据倾斜基本都可以处理而且效果也相对比较显著性能提升效果非常不错。
viewexplode(split(0.0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,,))
null,concat(hive,rand()),a.user_id)
source和sink对接方式Flume对接Kafka详细过程_flume
channel对接方式flume--KafkaChannel的使用_kafka
讲一下spark是如何将一个sql翻译成代码执行的里面的原理介绍一下
SparkSQL主要是通过Catalyst优化器将SQL翻译成最终的RDD算子的
Plan未解析的逻辑计划sqlParser分析Resolved
Plan优化后的逻辑计划Optimizer转换Physical
算子都会经过Catalyst一系列的分析和优化最终转换成高效的RDD的操作主要流程如下
Spark运行物理计划先是对物理计划再进行进一步的优化最终映射到RDD的操作上和Spark
Execution功能会根据运行时信息动态调整执行计划从而得到更高的执行效率
by然后再count变成两个mapreduce过程先去重再count。
spark类似会发生两次shuffle产生3个stage经过4个步骤①先map端去重②然后再shuffle到reduce端去重③然后通过map做一次partial_count④最后shuffle到一个reduce加总。
的N个key组合成List行数就翻了N倍这时最好分开来降低单个任务的数据量。
有这样一个需求统计一个用户的已经曝光了某一个页面想追根溯是从哪几个页面过来的然后求出在这几个来源所占的比例。
你要怎么建模处理
里然后可能需要自定义udf函数更主要的是考虑一些异常情况比如点击流中间是断开的或者点击流不全怎么应对
参考元数据数据治理的基石-腾讯云开发者社区-腾讯云一文彻底了解元数据管理与架构设计-腾讯云开发者社区-腾讯云
数据治理之元数据管理的利器——Atlas入门宝典-腾讯云开发者社区-腾讯云
雪花模型去除了冗余设计复杂可读性差关联的维度表多查询效率低但是可扩展性好适合OLAP星型模型冗余度高设计简单可读性高关联的维度表少查询效率高可扩展性低适合OLTP
星型模型和雪花模型最根本的区别就是维度表是直接连接到事实表还是其他的维度表。
1星型模型因为数据的冗余所以很多统计查询不需要做外部的连接因此一般情况下效率比雪花模型要高。
3雪花模型由于去除了冗余有些统计就需要通过表的连接才能产生所以效率不一定有星型模型高。
4正规化也是一种比较复杂的过程相应的数据库结构设计、数据的ETL、以及后期的维护都要复杂一些。
因此在冗余可以接受的前提下实际运用中星型模型使用更多也更有效率。
表通过数据冗余来减少查询次数以提高查询效率。
雪花模型在关系型数据库中MySQL/Oracle更加常见。
在具体规划设计时应结合具体场景及两者的优缺点来进行设计找到一个平衡点去开展工作。
三大数据模型星型模型、雪花模型、星座模型-腾讯云开发者社区-腾讯云
星型模型与雪花模型的区别、分别有哪些优缺点_星型模型和雪花模型的区别和使用场景?-CSDN博客
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback