尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
clusterdata生产集群数据分析:从数据加载到调度优化实战
简介面向集群管理研究工作者的阿里生产集群数据包基于阿里巴巴集群追踪计划cluster-trace发布涵盖2017、2018及GPU版本等多代生产环境trace数据真实反映现代互联网数据中心IDC的在线服务与批处理负载混合调度特征。压缩包共32个文件包含schema.csv等结构定义文件、多年份README与trace说明文档、DAG依赖图及调度分析图表并附带可运行的Python分析脚本与Jupyter Notebook便于直接复现数据探索流程整体体积仅16.22MB轻量易下载。数据中呈现的在线服务与批处理任务共部署、DAG信息等维度例如任务混部、资源利用率画像、DAG调度依赖分析对集群资源管理、调度算法优化及负载特征挖掘有较高研究价值。目前已有1162人学习下载适合从事数据中心调度、集群运维及分布式系统研究的师生与工程师作为实测数据集使用。1. 生产集群数据为什么成了调度研究的硬通货做过调度算法评估的人都懂仿真工具跑得再漂亮一迁移到真实环境就被现实打脸。原因很简单模拟数据里的任务到达规律、资源争抢节奏、机器故障分布全部是理想化的和真实生产集群的“负载指纹”完全对不上。clusterdata 就是这样一份从某头部互联网公司生产集群里采集、脱敏后公开的集群数据集它把数万台机器上真实发生过的任务提交、调度、执行、故障事件沉淀成了可复现研究的原始样本。对研究调度算法、资源利用率、负载预测、故障分析的从业者来说这份 dataset 的价值在于你的改进方案终于能在真实负载上跑一遍而不是自说自话。我建议你拿到数据后不要急着建模先在 JupyterNotebook 里把这个数据集的结构、字段语义和采样粒度摸清楚后面每一步分析才能立得住。2. 数据集构成与字段语义拿到手先看清四类表的“身份信息”2.1 从生产采集到公开数据clusterdata 的总体结构与存储粒度这份 clusterdata 数据集不是单张表而是围绕“机器—任务—实例”三个实体组织起来的一组表格。常见做法是分成四类核心表机器元数据表、机器细粒度使用表、任务事件表、任务实例运行表。机器元数据表记录集群里每台机器的静态配置包括 CPU 核数、内存大小、磁盘容量、机器上线时间与状态机器细粒度使用表则按固定时间间隔记录每台机器实时的 CPU、内存、磁盘、网络利用率任务事件表记录每个任务从提交到结束全生命周期的事件序列任务实例运行表保存着每个实例在运行期间周期性的资源占用。存储粒度方面生产集群的监控系统通常以秒级或分钟级采样所以机器使用表和实例运行表的数据量非常大。解压后原始文件多为 CSV 或列式存储格式按天分片是最常见的组织方式。这里要提醒一句不要指望一次性把整个数据集载入内存这份数据集的规模是以十 GB 甚至百 GB 来计量的。先建立对表的抽象认知再定读取策略是省内存的关键。2.2 关键字段速查从机器表到任务事件表哪几个字段是分析的锚点拿到数据集后我一般先把每张表的字段列清单整理出来标注哪些字段是主键、哪些是外键、哪些带时间语义。以下是一份基于实际分析需求的字段速查表字段名以常见公开版本为例数据表关键字段字段语义分析用途machine_metamachine_id机器唯一ID关联其他表的主键machine_metacpu_num / mem_size配置的CPU核数与内存计算资源利用率的分母machine_metastatus机器状态在线/离线/下线故障分析、可用性统计machine_usagemachine_id ts机器ID与采样时间戳时间序列聚合的键machine_usagecpu_util / mem_util采样时刻的CPU/内存利用率资源画像、超卖识别task_eventjob_id task_id作业ID与任务ID任务维度的生命周期追踪task_eventevent_type提交/调度/启动/结束/失败/被杀调度延迟、失败原因分析task_eventevent_ts事件发生时间计算等待时间、运行时间task_usageinstance_id实例ID实例级资源占用的索引task_usagecpu_avg / mem_avg实例平均资源占用真实资源需求估计需要注意任务事件表的 event_type 看似只是一个枚举字段但它是整个数据集里信息密度最高的字段。从提交事件到调度事件的时间戳差值就是任务排队等待时间从调度事件到启动事件的时间戳差值反映镜像拉取和资源准备的开销从启动到结束或失败则是任务的真实运行时长。分析时先明确你要回答的问题再决定从哪张表出发避免被字段数量带偏。2.3 时间与 ID 的坑为什么同一份数据里有两个时间轴clusterdata 里存在两个容易混淆的时间轴一个是事件时间轴记录任务状态变更发生的时刻另一个是采样时间轴记录资源使用数据被采集的时刻。事件时间是不等距的只有状态变化时才产生一条记录采样时间是等距的每隔固定秒数打一个点。初学者最容易犯的错误是直接用任务的事件时间戳去关联机器的采样时间戳导致同一时刻两边语义对不上。正确做法是先确定分析的时间基准再把另一侧的数据用“前向填充”或“就近匹配”对齐到基准时间轴上。ID 关系也是重灾区。机器表与使用表通过 machine_id 关联任务表与实例表通过 job_id、task_id、instance_id 两级关联。同一个任务可能对应多个实例比如重试、并发实例同一个作业包含多个任务。我在分析时通常先做一次基数检查确认每个 ID 是否唯一是什么粒度能一对多关联到哪一层。这个步骤看起来不起眼却决定了后续所有聚合结果的准确性。3. 环境准备与数据加载JupyterNotebook 里用 Pandas 打开几十 GB 数据3.1 加载环境配置Python 版本、内存预算与按天分片读取处理这份 dataset 的推荐环境是 Python 3.10 以上搭配 pandas、pyarrow、matplotlib、seaborn 这几个库。JupyterNotebook 是首选的工作载体因为它天然支持分步执行和可视化回显特别适合边探索边记录结论的工作流。硬件上建议至少准备 16GB 内存磁盘预留 2 到 3 倍于原始数据解压后的空间——因为你一定会生成中间缓存文件。内存规划的核心原则是“按天分片按列裁剪”。不要用 pd.read_csv 直接加载整个文件而是分批读入。下面这段代码展示了我常用的分片读取框架import pandas as pd from pathlib import Path data_dir Path(clusterdata/day_01) # 分块读取每次只处理50万行 chunk_iter pd.read_csv( data_dir / machine_usage.csv, chunksize500_000, parse_dates[ts], usecols[machine_id, ts, cpu_util, mem_util], dtype{machine_id: int32, cpu_util: float32, mem_util: float32}, ) for i, chunk in enumerate(chunk_iter): # 此处做聚合不要做全量保留 daily_stats chunk.groupby(machine_id).agg( cpu_mean(cpu_util, mean), mem_mean(mem_util, mean), ) if i 0: result daily_stats else: result pd.concat([result, daily_stats]) result.head()这段代码的逻辑是把一个大文件拆成多个小批次每一批读入后立刻按 machine_id 做聚合只保留聚合结果不保留原始明细。这样内存里始终只存在一小块数据和一个不断累积的统计结果。参数方面chunksize 决定了每个批次的量级一般设为 30 万到 100 万行之间太大容易让内存峰值过高太小则循环开销明显usecols 参数手动指定需要的列是减少内存的最直接手段dtype 参数把数值列压缩为 int32 或 float32在数据量大的时候能省出一半以上的内存空间。3.2 用 PyArrow 做列裁剪与类型压缩把内存占用降一个量级如果不喜欢手动管理分块可以换用 PyArrow 的按需读取能力。PyArrow 支持只读入指定的列并且对数值类型做更高密度的压缩。下面的写法在大文件场景下比 Pandas 分块更省心import pyarrow as pa import pyarrow.csv as csv # 只读两列并直接转为 Arrow 表 table csv.read_csv( clusterdata/day_01/machine_usage.csv, read_optionspa.csv.ReadOptions(block_size1 26), convert_optionspa.csv.ConvertOptions( include_columns[machine_id, ts, cpu_util, mem_util], column_types{ machine_id: pa.int32(), cpu_util: pa.float32(), mem_util: pa.float32(), ts: pa.timestamp(ms), }, ), ) df table.to_pandas() df.info()这里 block_size 参数控制读取块大小数值越大单次 IO 效率越高但也会抬高单块解析的内存占用64MB 是一个比较稳妥的起点。include_columns 只加载需要的列用不到的字段完全不会进入内存。把 ts 列显式声明为毫秒级时间戳可以避免 Pandas 自动推断时踩中时区或精度陷阱。这组配置下来加载同样文件的内存占用通常能降到原生读法的三分之一到五分之一。这里补充一个取舍如果你的分析场景需要反复扫描同一份数据建议第一次加载后把关心的列另存为 parquet 格式。parquet 是列式存储后续读取可以借助谓词下推只扫部分数据速度比直接读 CSV 快几倍。这个预处理是我每次拿到新数据集后的第一件事相当于给后续所有实验存了一份“后悔药”。3.3 小样本先行先抽样再全量别一开始就全表扫描面对几十 GB 的数据直接开始全量分析是最大的时间杀手。我的习惯是先从每个文件里抽出一小段把字段分布、缺失率、取值范围看清楚再决定全量分析的方案。抽样时注意保持时间连续性不要随机抽零散点否则观察不到负载随时间变化的趋势# 读取当天数据的最后 100 万行作为探索样本 sample_df pd.read_csv( clusterdata/day_01/machine_usage.csv, nrows1_000_000, usecols[machine_id, ts, cpu_util, mem_util], parse_dates[ts], ) sample_df.describe() sample_df.isna().sum() sample_df.groupby(machine_id).agg( count(ts, count), cpu_peak(cpu_util, max), mem_peak(mem_util, max), ).describe()这段代码做的事情很朴素读 100 万行样本先看整体统计量再看缺失值最后按机器分组看每台机器的采样次数和峰值。通过这三个输出你能快速判断数据质量有没有空值、有没有取值为 0 但语义不明的情况、采样间隔是否稳定。如果样本里已经能看到异常就不要继续往全量跑了先把数据卫生问题解决掉。很多集群数据集的机器使用表会存在“采集丢失”或“多采集”窗口这直接影响利用率分布的可信度早发现早处理。4. 核心分析实操从任务生命周期到资源画像的三段式复现4.1 复现任务生命周期统计提交、调度、结束的时间差到底怎么算集群管理研究的起点通常是任务生命周期。通过 task_event 表可以还原每个任务从提交到结束经历的各个阶段。这里的关键是不要把事件表当普通宽表用而是要把它看成按任务分组的时间线。第一步先按 job_id 和 task_id 分组把每个任务的事件时间戳整理成一条序列import pandas as pd events pd.read_csv( clusterdata/day_01/task_event.csv, usecols[job_id, task_id, event_type, event_ts], dtype{job_id: int32, task_id: int32, event_type: int8}, parse_dates[event_ts], ) # 转成事件透视格式每个任务一行不同阶段的时间列并排 pivot events.pivot_table( index[job_id, task_id], columnsevent_type, valuesevent_ts, aggfuncmin, ).reset_index() # 重新命名字段方便后续引用 rename_map {1: submit_ts, 2: schedule_ts, 3: start_ts, 4: finish_ts, 5: fail_ts} pivot.columns [job_id, task_id] [ rename_map.get(col, str(col)) for col in pivot.columns[2:] ] pivot[wait_time_s] (pivot[schedule_ts] - pivot[submit_ts]).dt.total_seconds() pivot[run_time_s] (pivot[finish_ts] - pivot[start_ts]).dt.total_seconds() pivot[wait_time_s].describe(percentiles[0.5, 0.9, 0.95, 0.99])这段代码的核心是 pivot_table 配合 aggfuncmin。同一事件类型在异常场景下可能出现多次记录通过取最小值还原最早发生的时间点避免重复记录导致的误差。event_type 的字段值在不同数据版本里编号不一致我这里用 rename_map 做了一层映射封装实际使用前先打印 events.event_type 的取值分布确认对应关系。wait_time_s 和 run_time_s 是后续所有调度质量分析的两个基础派生字段。值得一提的是部分任务是“提交即调度”或者“调度后长时间未启动”的这些边界样本会在百分位统计里显得很刺眼需要在后续分析中单独处理。4.2 机器资源画像CPU、内存利用率的分布与“超卖”识别任务生命周期只是调度的果机器的资源利用情况才是调度的因。接下来用 machine_usage 表画资源画像。这里要区分配置利用率和实际利用率配置利用率是使用量除以机器配置总量实际利用率是使用量除以实际可分配量。生产集群普遍存在超卖配置总量和可分配量往往不一致。我做画像时习惯同时计算两个指标usage pd.read_csv( clusterdata/day_01/machine_usage.csv, usecols[machine_id, ts, cpu_util, mem_util], dtype{machine_id: int32, cpu_util: float32, mem_util: float32}, parse_dates[ts], ) usage[hour] usage[ts].dt.floor(h) # 每台机器按小时聚合 machine_hour usage.groupby([machine_id, hour]).agg( cpu_avg(cpu_util, mean), cpu_peak(cpu_util, max), mem_avg(mem_util, mean), samples(ts, count), ).reset_index() # 阈值判定峰值利用率长期高于 85% 的机器进入热区清单 hot_machines machine_hour[ (machine_hour[cpu_peak] 85) (machine_hour[samples] 50) ].groupby(machine_id)[hour].count() hot_machines.sort_values(ascendingFalse).head(20)这段逻辑分两步走先按小时压缩原始采样点平滑噪声再用峰值和样本数两个条件筛选热点机器。85% 这个阈值的含义是生产环境里 CPU 长期超过这个水平响应时延一般会出现明显劣化通常被认为是进入超卖危险区的信号。samples 50 的条件是为了过滤掉采集中断或者数据缺失的机器避免因为采样点太少导致峰值计算失真。4.3 用可视化验证调度假设哪里是热点哪里是闲置纯统计数字的洞察力有限把机器负载投射到时间维度上才能看到生产集群的潮汐效应。我常用的是热力图加直方图的组合import matplotlib.pyplot as plt import seaborn as sns # 随机抽 200 台机器做时间序列热力图 sample_machines machine_hour[machine_id].drop_duplicates().sample(200, random_state42) plot_df machine_hour[machine_hour[machine_id].isin(sample_machines)] pivot_cpu plot_df.pivot_table( indexmachine_id, columnshour, valuescpu_avg ) fig, ax plt.subplots(figsize(12, 8)) sns.heatmap(pivot_cpu, cmapYlOrRd, cbar_kws{label: CPU utilization (%)}, axax) ax.set_xlabel(hour of day) ax.set_ylabel(machine id) plt.show()这个可视化能直观回答三个问题集群是否存在明显的昼夜周期、热点机器的占比大致是多少、是否存在成片的低利用率时段。我在实际分析中经常发现看似随机的生产负载实际上有很强的时段结构工作日上午的某个小时、晚上流量高峰前后资源使用形态完全不同。如果你的调度策略假设负载是平稳的那么后续仿真结果和生产环境的偏差就有了合理解释。可视化在这里不是用来“出图交差”而是用来验证你对数据形态的判断是否与真实分布一致。5. 避坑常见问题处理 clusterdata 时我翻过的车5.1 时间戳精度翻车毫秒还是纳秒直接影响排队延迟计算现象算出来的任务排队等待时间全是负数或者极端异常值。原因不同版本的数据集时间戳精度不一致有的字段是毫秒有的字段是微秒部分还有秒级时间戳混在里面直接减导致单位错乱。解决加载时显式指定每一列的时间精度不要交给 Pandas 自动推断。我现在的习惯是所有时间字段统一用 parse_dates 加指定格式或者读成整数再手动ts.astype(datetime64[ms])转换一次转换完再做减法运算。5.2 内存直接撑爆全量 read_csv 是新手头号杀手现象执行 read_csv 加载 machine_usage 后进程被杀或者 Jupyter 内核反复重启。原因生产集群的使用表动辄上亿行全量加载的中间 pandas DataFrame 会膨胀到十几 GB。解决优先按列裁剪加类型压缩仍然放不下的就按天分片、按 chunksize 分批聚合。切记不要打开文件只看一眼就跑全量先用read_csv(nrows...)探样本确认结构后再设计批量流程。5.3 空值与脏数据实例失效、机器下线留下的洞现象groupby 后某些机器的采样数为 0或者 CPU 利用率出现 999、-1 之类的占位值。原因生产数据在采集端就有缺失机器下线、实例失败导致的记录不完整部分数据以特殊常量填充缺失。解决先跑一遍isna()和unique()检查取值域把异常值过滤掉采样数为 0 的机器要么剔除要么单独标记为离线状态不能直接参与资源统计。这一步做完后再看分布否则均值会被脏数据拉偏。5.4 “平均利用率”的分母到底用哪个配置值还是实际可分配值现象同一批机器两个报告得出来的平均 CPU 利用率差 20% 以上。原因一个用了机器配置核数做分母一个用了集群实际可分配核数做分母而超卖环境下两者差异巨大。解决在分析文档里从一开始就写明分子分母定义。计算配置利用率时分母是 machine_meta 里的 cpu_num 与 mem_size计算实际利用率时分母是集群调度器允许分配的上限通常是配置值乘以一个超卖系数。对比历史结果时先确认分母口径否则所有结论都不可比。5.5 ID 关联错乱任务与实例一对多直接用任务 ID 汇总会把资源占用放大现象按任务 ID 汇总实例资源占用后总量超过集群资源总量明显不合逻辑。原因一个任务失败重试后会产生多个实例但实例 ID 各自独立简单按 task 去重聚合会把同一任务的不同历史尝试叠加成并行资源占用。解决先确认每个实例的运行时间窗口在计算任意时刻的资源占用时只叠加时间窗口相交的活跃实例。通常会加一步实例启停时间过滤# 每个实例取运行窗口计算重叠占用 task_usage task_usage.sort_values(instance_id) task_usage[active] 1 # 用 CPU 占用率与运行时长做时间加权 task_usage[cpu_weighted] task_usage[cpu_avg] * task_usage[run_duration_s] task_usage[mem_weighted] task_usage[mem_avg] * task_usage[run_duration_s] instance_total task_usage.groupby(instance_id).agg( cpu_total(cpu_weighted, sum), mem_total(mem_weighted, sum), )这段代码的本质是把资源占用从瞬时值转换成时间加权值再做聚合避免直接把不同运行长度的实例相加带来的虚高。instance_id 关联时需要额外注意同一个实例 ID 是否会有多行记录取决于采样频率和原始文件的组织方式首聚之后再核对一次最大占用是否超过单机配置值是一个有效的 sanity check。6. 进阶技巧做一个轻量调度质量看板来验证你的改进方案6.1 自建指标从原始字段提炼可对比的 KPI数据分析的终点不是画几张图而是产出可对比的评价指标。基于前面派生出的字段我通常定义四类 KPI平均排队延迟、P95 调度时延、资源利用率峰值均值比、装箱密度。前两个指标衡量调度器的响应速度后两个衡量资源编排的紧凑程度。计算方式很简单kpi pd.DataFrame({ avg_wait: [pivot[wait_time_s].mean()], p95_wait: [pivot[wait_time_s].quantile(0.95)], peak_to_avg_cpu: [machine_hour[cpu_peak].sum() / machine_hour[cpu_avg].sum()], packing_density: [machine_hour.groupby(hour)[cpu_avg].mean().mean() / 100], })这套 KPI 的意义在于无论你改的是调度策略还是资源超卖系数最终都能用同一套口径做前后对比。指标能稳定复现实验才有说服力。6.2 抽取一个时间窗口一小时的数据也能跑通全流程全量数据适合做离线统计但做调度策略对比实验时我会刻意只截取一个小时的数据窗口。原因是生产负载在一个小时内仍然保持真实的任务到达分布与资源争抢特征但数据量小到可以支撑迭代实验。抽样窗口一般选工作日上午 10 点到 11 点这段负载最复杂最考验调度策略。这样跑通全流程后再换窗口验证稳定性既省时间又不丢代表性。6.3 用仿真回放验证同一条数据不同策略前后对比最后分享一个让实验结论更可信的做法把截取窗口里的任务按原始时间戳重放换用不同的调度规则分配机器对比同一批任务的等待时延分布。这里用简单的事件驱动即可class Scheduler: def __init__(self, machines): self.machines machines self.queued [] def submit(self, task): self.queued.append(task) def schedule(self, now): while self.queued: task self.queued[0] machine self.find_best_machine(task, now) if machine is None: break self.queued.pop(0) task.assigned_machine machine.id def find_best_machine(self, task, now): # 自定义策略选当前负载最低的机器 candidates [m for m in self.machines if m.free_cpu(now) task.cpu_req] return min(candidates, keylambda m: m.free_cpu(now), defaultNone)这种仿真不追求复现调度器全貌而是用同一个任务流对比不同选择策略的排队表现。在数据与仿真环境一致的条件下KPI 的差异就能归因于策略本身的取舍。那次做调度对比实验我在数据加载上栽了三个跟头才把流程跑通从那以后每次拿到新数据集我都强制执行一个流程先抽样本、再统一时间精度、最后固定指标口径三步走完才开始写分析代码。这套流程救了我很多次希望也能帮你省下几天掉头发的调试时间。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Seaborn实战:用Titanic数据集掌握数据可视化核心技巧

Seaborn实战:用Titanic数据集掌握数据可视化核心技巧

1. 起因:为什么打卡第5天我选择了Seaborn今天是我这份2025 AI学习计划的第5天。前面4天啃完了NumPy、Pandas基础和数据清洗,说实话,到第4天的时候我已经有点坐不住了,因为一直在处理冷冰冰的表格,始终缺少一个“看见数…

📅 2026/10/16 9:52:42
TongHttpServer负载均衡实战:从解压到upstream配置与健康检查

TongHttpServer负载均衡实战:从解压到upstream配置与健康检查

简介:TongHttpServer(THS)是一款面向高并发业务场景的负载均衡软件产品,适合系统运维、架构设计与后端开发人员使用,用于提升服务集群的可靠性、可扩展性与资源利用率。下载包面向 x86 平台提供完整发行版,…

📅 2026/10/16 9:47:39
PySimpleGUI SystemTray实战:打造常驻后台的Python桌面小工具

PySimpleGUI SystemTray实战:打造常驻后台的Python桌面小工具

1. 桌面小工具常驻后台的刚需与托盘元素的价值做Python桌面小工具的人迟早会撞上一个需求:程序不能一直霸占任务栏,用户点关闭按钮时希望它缩到右下角继续跑,需要的时候再唤出来。这个需求在计时器、剪贴板历史、下载监控、快捷启动器这类工具…

📅 2026/10/16 9:47:39
MORE NEWS

更多资讯

📰

Spring AI Alibaba实现Text-to-SQL人工审核防线,生产级落地实践

“自然语言直接换SQL然后自动执行”听起来很爽,但这在真实系统里就是个雷区。我最近用 Spring AI Alibaba 重做了一套内部数据分析入口,把链路从“自然语言→SQL→执行”改成“自然语言→SQL→人工确认→执行”,从功能演示跨到了生产可用。这…

📰

正则表达式入门实战:从文本匹配到数据清洗的核心技巧

你有没有过这种经历:一整天泡在数据堆里,从几百行日志里手工复制用户ID,或者用Excel一个个分列去拆一段地址,眼都快看花了,结果还漏了几条。如果因为“懒”去搜了一下怎么自动化,大概率会看到一个词——正则…

📰

网络请求全链路:从DNS到HTTP/2的性能优化与排查实战

1. 一个网络请求从发出到返回,中间到底发生了什么 先说一个我常用来考团队新人的场景:你在手机App里刷到一条内容,点了个赞,屏幕上的红心立刻亮了。这个过程中,你的手机到底做了哪些事?很多人会回答“发了个…

📰

Python WebSocket从原理到实战:异步通信、心跳与踩坑指南

做后端这几年,我越来越觉得 WebSocket 是个“人人都听说过、没几个人真正写明白”的东西。身边不少同事都能用 Python 随手跑通一个 WebSocket 的 hello world,但真到了上线阶段——连接莫名其妙断开、消息偶尔丢失、单机连接数上不去、进程突然卡死——…

📰

大气与海洋如何耦合?DLESyM-V1-ERA5中SST耦合变量的完整解析

【免费下载链接】dlesym-v1-era5 项目地址: https://ai.gitcode.com/hf_mirrors/nvidia/dlesym-v1-era5 点击查看 免费下载 DLESyM-V1-ERA5 是 NVIDIA 开源的深度学习地球系统模型(DLESyM)检查点包,包含独立的大气与海洋组件&…

📰

MySQL数据库设计实战:从规范建表到索引优化与事务隔离

1. 先想清楚再建库:设计规范其实是第一课这篇mysql课堂笔记,说白了我整理了很久。最初只是想把自己的学习过程记录下来,后来发现很多同学不是不会写SQL,而是表结构从一开始就建歪了,后面怎么写都别扭。这篇笔记适合三类…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬