news 2026/9/13 20:37:57

StarRocks表达式分区:精准时间窗口裁剪实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
StarRocks表达式分区:精准时间窗口裁剪实战指南

1. 什么是StarRocks里的表达式分区?它到底解决了什么真问题?

我第一次在生产环境里遇到“按小时滚动窗口聚合”需求时,手头的StarRocks表还在用传统的RANGE分区,每天一个分区,结果凌晨三点跑批任务一查,发现昨天23点到今天0点的数据全被切到了两个分区里——一半在p20240515,一半在p20240516。运维同事盯着监控面板直摇头:“这查询得跨两个分区扫,IO翻倍,缓存命中率掉到30%以下。”那一刻我才真正意识到:分区不是为了“看着整齐”,而是为了让查询能精准命中最小数据集。而表达式分区,就是StarRocks为解决这类“时间边界不重合、业务逻辑难映射到日期字符串”问题给出的底层破局方案。

简单说,表达式分区(Expression Partitioning)不是让你手动写一堆PARTITION p20240515 VALUES LESS THAN ("2024-05-16"),而是允许你直接把SQL函数嵌进分区定义里,让StarRocks自己算出每条数据该进哪个分区。比如date_trunc("hour", event_time)——这条表达式会把2024-05-15 23:47:12自动截成2024-05-15 23:00:00,再把这个值作为分区键;time_slice(event_time, INTERVAL 15 MINUTE, 'floor')则能把时间切到最近的15分钟边界上。它背后不是语法糖,而是StarRocks在BE层对分区裁剪逻辑的深度重构:传统分区依赖字符串字典序比较,而表达式分区要求引擎在写入时实时计算表达式结果,并将其序列化为分区标识符,查询时再用同样逻辑反向推导目标分区范围。

这个能力直接影响三类典型场景:第一是IoT设备上报数据,时间戳精度到毫秒,但业务分析只关心“每15分钟设备在线数”,硬按天分会导致单次查询扫描TB级冷数据;第二是金融交易流水,需要按“交易发生小时+业务线”做二级分区,但业务线字段是字符串,传统LIST分区无法和时间组合;第三是AB测试埋点,实验周期可能跨多天,但统计口径必须严格按“实验启动后第N小时”,用date_sub(now(), interval N hour)动态生成分区边界。这些需求用传统分区要么要写复杂视图兜底,要么得靠调度脚本每天预建分区,运维成本高得离谱。而表达式分区让DDL一步到位,分区逻辑和业务语义完全对齐——这才是它被高频搜索的根本原因:它把DBA从“分区手工匠”解放成了“业务语义翻译官”

你可能会问:那Doris或ClickHouse能不能干这事?实测下来,Doris目前仅支持date_trunc有限函数,且不支持嵌套表达式;ClickHouse的PARTITION BY虽灵活,但分区键必须是表字段,不能是计算列,更无法像StarRocks这样在建表时就绑定表达式并保证写入一致性。这也是为什么“starrocks vs apache druid 性能对比”里,StarRocks在时间窗口类查询上常胜出——Druid的segment粒度固定,窗口切分依赖外部任务调度,而StarRocks的表达式分区让窗口计算下沉到存储层,查询时连WHERE条件都不用改,引擎自动完成分区裁剪。所以当你搜“StarRocks数据分区”时,真正想挖的不是语法手册,而是:怎么用最少的DDL,让数据物理分布和业务查询模式严丝合缝?

2. 表达式分区的核心设计逻辑与选型深挖

2.1 为什么非得用表达式分区?传统方案的硬伤在哪?

先看一个真实案例:某电商用户行为分析表,原始建表语句是这样的:

CREATE TABLE user_behavior ( event_time DATETIME, user_id BIGINT, action STRING, page STRING ) DUPLICATE KEY(event_time, user_id) PARTITION BY RANGE (event_time) ( PARTITION p20240501 VALUES LESS THAN ("2024-05-02"), PARTITION p20240502 VALUES LESS THAN ("2024-05-03") ) DISTRIBUTED BY HASH(user_id) BUCKETS 10;

表面看没问题,但业务方提了个需求:“查过去24小时内,每小时的加购转化率”。SQL写出来是:

SELECT date_trunc("hour", event_time) AS hour_slot, count_if(action='cart_add') / count(*) AS cr_rate FROM user_behavior WHERE event_time >= date_sub(now(), INTERVAL 1 DAY) GROUP BY hour_slot;

问题来了:WHERE event_time >= ...只能裁剪到p20240514p20240515两个分区,但实际需要的数据只分布在p20240514的后半段和p20240515的前半段——这意味着BE节点得把这两个分区全量读入内存,再用date_trunc函数逐行过滤。我们抓取过执行计划,ScanNodeCardinality显示扫描行数是实际返回行数的8.3倍,IO带宽打满,查询耗时从1.2秒飙到6.7秒。

如果换成表达式分区,建表语句变成:

CREATE TABLE user_behavior_expr ( event_time DATETIME, user_id BIGINT, action STRING, page STRING ) DUPLICATE KEY(event_time, user_id) PARTITION BY EXPRESSION (date_trunc("hour", event_time)) DISTRIBUTED BY HASH(user_id) BUCKETS 10;

此时同样的查询,StarRocks会做什么?它在解析WHERE event_time >= date_sub(now(), INTERVAL 1 DAY)时,会自动将这个条件“反向映射”到分区表达式上:先算出date_sub(now(), INTERVAL 1 DAY)对应的小时边界(比如2024-05-14 15:00:00),再根据date_trunc("hour", event_time)的逆运算,确定需要扫描的分区范围——精确到p2024-05-14-15p2024-05-14-16...直到p2024-05-15-14,共24个分区,每个分区只读取本小时内数据。实测下来,扫描行数下降92%,P99延迟稳定在0.8秒内。

这个差异的本质,在于分区裁剪的触发时机不同:传统RANGE分区裁剪发生在谓词解析阶段,依赖原始字段值的范围比较;而表达式分区裁剪发生在分区键计算阶段,引擎会把WHERE条件中的时间函数,用和分区表达式相同的算法重新计算,确保物理扫描和逻辑过滤完全同步。这就像快递分拣——传统方式是按“收件城市”粗筛,再人工翻包裹找具体街道;表达式分区则是直接按“街道+门牌号”生成二维码,扫码即达。

2.2date_trunctime_slice:选哪个?参数怎么调才不踩坑?

网络热词里总把date_trunctime_slice并列,但它们定位完全不同,混用会出大问题。我见过最典型的错误,是有人把time_slice(event_time, INTERVAL 30 MINUTE, 'ceil')当成date_trunc("hour", event_time)的替代品,结果分区数量爆炸。

先说date_trunc:它只接受year/month/day/hour/minute五种精度,作用是向下截断到指定单位起点。比如date_trunc("hour", "2024-05-15 14:37:22")结果是2024-05-15 14:00:00date_trunc("minute", "2024-05-15 14:37:22")2024-05-15 14:37:00。它的优势是结果可预测、分区名规整(p2024-05-15-14),适合做小时级、天级聚合。但注意:date_trunc("day", event_time)RANGE分区效果几乎一样,因为"2024-05-15"字符串本身就能做字典序比较,没必要用表达式——除非你要混用其他字段,比如date_trunc("day", event_time) + '_' + channel

再看time_slice:它更像一个“时间刻度尺”,核心参数是intervalmodeinterval可以是任意时间间隔,比如INTERVAL 15 MINUTEINTERVAL 7 DAY甚至INTERVAL 2 HOURmode决定对齐方式:'floor'(向下取整)、'ceil'(向上取整)、'round'(四舍五入)。举个例子:time_slice("2024-05-15 14:37:22", INTERVAL 15 MINUTE, 'floor')结果是2024-05-15 14:30:00,而'ceil'结果是2024-05-15 14:45:00。这里有个致命细节:time_slice的返回值类型是DATETIME,但StarRocks内部会把它序列化为字符串分区名,且不补零。比如2024-5-15 14:30:00会被存成p2024-5-15-14-30-00,而不是p2024-05-15-14-30-00——如果你用程序自动生成分区清理脚本,正则匹配p\d{4}-\d{2}-\d{2}就会漏掉这些分区。

所以选型原则很明确:

  • 稳定、易管理、和业务报表口径一致→ 无脑选date_trunc,精度按需选hourday
  • 非标时间窗口(如每17分钟汇总、每周二0点起的7天周期)→ 必须用time_slice,且mode优先选'floor',避免'ceil'导致跨天分区(比如23:59:59被切到第二天00:15:00);
  • 绝对不要用time_sliceINTERVAL 1 HOUR,这和date_trunc("hour", ...)功能重复,但分区名更丑、兼容性更差。

提示:time_sliceinterval单位必须是MICROSECOND/MILLISECOND/SECOND/MINUTE/HOUR/DAY/WEEK/MONTH/YEAR之一,不能写INTERVAL 90 SECOND——StarRocks会报错Invalid time unit。正确写法是INTERVAL 1.5 MINUTE,但注意浮点数可能导致精度丢失,生产环境建议统一用整数单位。

2.3 表达式分区和普通分区能共存吗?多级分区怎么搭才高效?

StarRocks官方文档没明说,但实测证明:表达式分区可以和RANGE/LIST分区组合,形成多级分区,但必须遵循严格顺序。比如你想按“业务线+小时”两级分区,正确写法是:

PARTITION BY LIST (business_line) ( PARTITION p_shop VALUES IN ("shop"), PARTITION p_pay VALUES IN ("pay") ) SUBPARTITION BY EXPRESSION (date_trunc("hour", event_time));

这里LIST是第一级分区,EXPRESSSION是子分区。StarRocks会先按business_line路由到不同tablet,再在每个tablet内按小时表达式做子分区。这种设计的好处是:查询带WHERE business_line='shop' AND event_time >= ...时,能同时裁剪两级——先定位到p_shop分区,再在其中精确扫描目标小时子分区,比单级分区减少80%的tablet扫描数。

但反过来就不行:PARTITION BY EXPRESSION (...) SUBPARTITION BY LIST (...)会报错Expression partitioning cannot be used as top-level partition。原因在于表达式分区的计算开销比静态分区大,StarRocks要求它必须作为叶子节点存在,避免在顶层做大量实时计算影响写入吞吐。

另一个关键限制:表达式分区不支持动态添加新分区。传统RANGE分区可以用ALTER TABLE ADD PARTITION随时加,但表达式分区的分区边界由表达式逻辑决定,新增数据会自动落入对应分区,无需人工干预。这看似省事,实则暗藏风险——如果某天event_time字段出现脏数据(比如'0000-00-00 00:00:00'),date_trunc("hour", ...)会返回NULL,而StarRocks会把NULL值统一归入一个特殊分区p__null__。这个分区会越积越大,成为性能黑洞。我们线上就遇到过,因上游ETL未过滤非法时间戳,导致p__null__占了整个表35%的数据量,查询时被迫全表扫描。

注意:p__null__分区无法用DROP PARTITION删除,必须先用UPDATE修复脏数据,再通过ALTER TABLE DROP PARTITION p__null__清理。但操作前务必确认该分区无有效数据——我们曾误删过,结果发现部分埋点SDK上报的event_time'1970-01-01',也被判为NULL。解决方案是在建表时加CHECK约束:CHECK (event_time > '1990-01-01'),或者在导入时用WHERE event_time IS NOT NULL AND event_time > '1990-01-01'过滤。

3. 从建表到压测:表达式分区的完整实操链路

3.1 建表阶段:参数选择与避坑清单

建表是表达式分区成败的第一关。很多人照着文档抄完DDL就跑,结果第二天发现数据写不进去,或者查询慢得像蜗牛。我总结了一套必须检查的七项清单,少一项都可能翻车:

  1. 表达式字段必须是表中真实存在的列:不能是date_trunc("hour", now())这种常量,也不能是concat(date, '_', hour)这种拼接列——StarRocks要求表达式必须基于基表字段计算,且该字段在写入时已存在。我们试过用cast(event_time as date),结果报错Expression contains unsupported function cast,因为cast不在白名单函数里。

  2. 表达式返回类型必须是DATE/DATETIME/INT/BIGINT/STRINGdate_trunc返回DATETIME,time_slice也是,没问题;但year(event_time)返回INT,虽然合法,却会导致分区名变成p2024这种极简格式——当你要查2024年Q1数据时,WHERE year(event_time)=2024无法裁剪到具体季度,因为分区只存了年份。所以宁可用date_trunc("month", event_time),分区名p2024-01,查询WHERE event_time >= '2024-01-01'就能精准命中。

  3. 分区名长度不能超64字符:StarRocks内部用分区名做路径标识,超长会截断。time_slice(event_time, INTERVAL 1 SECOND, 'floor')会产生p2024-05-15-14-37-22(19字符),安全;但time_slice(event_time, INTERVAL 1 MICROSECOND, 'floor')会生成p2024-05-15-14-37-22-123456(26字符),加上前缀p_和分隔符,轻松突破64。实测超过后,BE日志报partition name too long,写入失败。

  4. 必须显式指定DISTRIBUTED BY:表达式分区不改变分桶逻辑,但新手常忽略这点,建表时漏写DISTRIBUTED BY HASH(...),结果所有数据挤在一个tablet里,查询并发度为1。我们线上有个表,因DISTRIBUTED BY HASH(user_id)写成DISTRIBUTED BY HASH(event_time),导致热点集中在几个小时分区,QPS峰值时单BE CPU冲到95%。

  5. PROPERTIESreplication_num别设太高:表达式分区天然带来数据倾斜(比如凌晨流量少,分区小;白天流量大,分区大),如果副本数设3,小分区存储浪费严重。我们生产环境统一设"replication_num" = "2",既保证高可用,又节省30%磁盘。

  6. in_memory属性慎用"in_memory" = "true"会让分区常驻内存,对小时级分区很友好(内存够放24个分区),但对分钟级分区就是灾难——1小时60个分区,24小时1440个,内存根本扛不住。我们压测过,time_slice(..., INTERVAL 1 MINUTE, ...)in_memory=true,BE OOM频率高达每小时2次。

  7. 首次建表后,立刻执行SHOW PARTITIONS FROM table_name验证:别等写入数据再查。正常情况应该看到类似:

    PartitionName | Range | DistributionKey | Buckets | ReplicationNum | State p2024-05-15-14 | [2024-05-15 14:00:00, 2024-05-15 15:00:00) | user_id | 10 | 2 | NORMAL

    如果Range显示[NULL, NULL),说明表达式有语法错误,赶紧DROP TABLE重建。

3.2 数据写入:Flink CDC和Stream Load的适配要点

表达式分区对写入端有隐性要求:数据必须带时间字段,且该字段在写入时已确定。这对Flink CDC和Stream Load两种主流方式影响不同。

先说Flink CDC:我们用Flink SQL同步MySQL binlog到StarRocks,原SQL是:

INSERT INTO starrocks_table SELECT * FROM mysql_table;

问题来了:mysql_tableevent_time是TIMESTAMP类型,但Flink CDC默认把它转成STRING,date_trunc("hour", event_time)就会报错No matching function with signature: date_trunc(VARCHAR, VARCHAR)。解决方案是显式CAST:

INSERT INTO starrocks_table SELECT CAST(event_time AS DATETIME) AS event_time, user_id, action FROM mysql_table;

更稳妥的做法是在Flink DDL里定义event_timeTIMESTAMP(3),并开启server-time-zone='Asia/Shanghai',避免时区转换导致时间偏移。

再说Stream Load:这是StarRocks原生导入方式,但JSON格式容易踩坑。比如你发一个JSON:

{ "event_time": "2024-05-15 14:37:22", "user_id": 12345, "action": "click" }

StarRocks默认把event_time当STRING处理,date_trunc函数失效。必须在Stream Load请求头里加:

curl -X PUT -H "label:abc" \ -H "column_separator:," \ -H "columns: event_time,user_id,action" \ -H "format: json" \ -H "timezone: Asia/Shanghai" \ --data-binary @data.json \ http://fe_host:8030/api/db/table/_stream_load

关键是timezone参数,它告诉StarRocks把字符串"2024-05-15 14:37:22"按东八区解析成DATETIME。否则默认UTC,date_trunc("hour", ...)会算成2024-05-15 06:00:00,数据全进错分区。

我们还发现一个隐藏问题:Stream Load的strict_mode=false时,遇到非法时间字符串(如"2024-05-xx 14:37:22")会转成NULL,触发p__null__分区堆积。生产环境必须设strict_mode=true,配合timeout=600,让导入失败立即报警,而不是静默写入脏数据。

3.3 查询优化:如何让表达式分区真正发挥威力?

建好表、写入数据只是开始,查询才是检验分区价值的考场。很多用户反馈“用了表达式分区,查询还是慢”,根源往往在WHERE条件写法不对。

核心原则:WHERE条件中的时间谓词,必须和分区表达式保持数学等价。比如分区用date_trunc("hour", event_time),查询就不能写WHERE event_time BETWEEN '2024-05-15 14:00:00' AND '2024-05-15 14:59:59'——这看起来合理,但StarRocks无法把BETWEEN条件反向映射到date_trunc,裁剪仍会扫描全天24个分区。

正确写法是:

-- 方案1:直接用分区表达式 WHERE date_trunc("hour", event_time) = '2024-05-15 14:00:00' -- 方案2:用范围查询,但边界必须对齐小时 WHERE event_time >= '2024-05-15 14:00:00' AND event_time < '2024-05-15 15:00:00'

方案1最精准,但需要业务方知道具体小时值;方案2更通用,且StarRocks能识别<>=组合等价于date_trunc("hour", event_time),自动裁剪。我们压测过,方案2的P95延迟比方案1高0.03秒,但可维护性高得多。

另一个常见误区:在WHERE里用函数包装分区字段。比如分区是date_trunc("hour", event_time),却写WHERE hour(event_time) = 14。这会导致全表扫描,因为hour()函数无法和分区表达式对齐。必须改成WHERE date_trunc("hour", event_time) = '2024-05-15 14:00:00'

对于多条件查询,比如“查某用户昨天每小时的行为数”,SQL应该是:

SELECT date_trunc("hour", event_time) AS hour_slot, count(*) AS cnt FROM user_behavior_expr WHERE user_id = 12345 AND event_time >= date_sub('2024-05-15', INTERVAL 1 DAY) AND event_time < '2024-05-15' GROUP BY hour_slot;

这里user_id = 12345走分桶裁剪,event_time范围走分区裁剪,两者叠加,最终只扫描1个tablet内的24个子分区。如果把AND event_time < '2024-05-15'写成AND date_trunc("day", event_time) = '2024-05-14',虽然语义相同,但StarRocks无法同时利用两个函数裁剪,性能反而下降。

最后分享一个实战技巧:用EXPLAIN命令看分区裁剪效果。执行EXPLAIN SELECT ...后,找OlapScanNode部分的PartitionCountTabletCount。理想状态是PartitionCount等于你预期的分区数(比如查1小时就是1),TabletCount等于PartitionCount * Buckets(比如1分区×10桶=10tablet)。如果PartitionCount显示24,说明裁剪失败,赶紧检查WHERE条件。

4. 真实故障复盘:那些文档里不会写的坑与对策

4.1 分区爆炸:time_slice配错mode引发的雪崩

去年双十一流量高峰,我们一个实时风控表突然响应变慢,监控显示BE节点磁盘IO持续95%。紧急排查发现,SHOW PARTITIONS返回了1200多个分区,而正常应该只有200左右。根源是time_slice(event_time, INTERVAL 1 HOUR, 'ceil')——'ceil'模式下,2024-10-10 23:59:59被切到2024-10-11 00:00:00,导致每天多出1个跨天分区。更糟的是,由于event_time字段有少量未来时间(上游系统时钟漂移),'ceil'2024-10-12 00:00:00也切到了2024-10-12 01:00:00,分区数呈指数增长。

解决方案分三步:

  1. 立即停写,用ALTER TABLE DROP PARTITION批量删掉未来分区(脚本用p2024-10-12-01正则匹配);
  2. 修改表结构,把'ceil'换成'floor',并加CHECK (event_time <= now() + INTERVAL 1 HOUR)防止未来时间;
  3. 写个巡检脚本,每天凌晨跑SELECT count(*) FROM information_schema.partitions WHERE table_name='xxx' AND partition_name LIKE 'p2024%' GROUP BY LEFT(partition_name, 10),分区数突增10%就告警。

实操心得:time_slicemode'floor'是底线,'round'在边界值(如xx:29:59)可能四舍五入到下一区间,比'ceil'更难预测。生产环境永远用'floor',用date_add()函数在应用层补足“向上对齐”的逻辑。

4.2 NULL分区吞噬资源:脏数据治理全流程

前面提过p__null__分区,但真正让它成为定时炸弹的,是我们的埋点SDK升级。新版本把未触发的曝光事件event_time设为NULL,每天新增500万NULL记录。p__null__分区三个月涨到2TB,查询时即使加WHERE event_time IS NOT NULL,StarRocks仍要扫描这个分区(因为NULL值无法被谓词裁剪)。

治理流程我们走了四个月:

  • 第一阶段(应急):用INSERT OVERWRITE把有效数据导出到新表,WHERE event_time IS NOT NULL AND event_time > '2023-01-01',耗时3小时,期间服务降级;
  • 第二阶段(拦截):在Flink作业里加FILTER event_time IS NOT NULL,并在Stream Load前置加Python清洗脚本,把NULL转成'1970-01-01 00:00:00'(这样至少能进p1970-01-01-00,方便后续清理);
  • 第三阶段(根治):推动客户端SDK修改,默认event_time为当前时间戳,NULL值只在异常场景出现,且上报时打标is_error:true,单独进错误表。

现在我们的建表模板强制包含:

CHECK (event_time IS NOT NULL), CHECK (event_time > '1990-01-01'), CHECK (event_time < date_add(now(), INTERVAL 1 DAY))

三个CHECK加起来,写入失败率从0.3%降到0.001%,p__null__分区再没出现过。

4.3 Docker部署下的时区陷阱:容器里的时间不是你想象的那样

docker run -d --name starrocks-fe -p 8030:8030 starrocks/starrocks:3.1部署时,我们发现date_trunc("hour", now())返回的时间比宿主机慢8小时。查日志发现FE容器时区是UTC,而now()函数返回UTC时间,date_trunc("hour", ...)截的是UTC小时,但业务方要的是东八区小时。

解决方案有两个:

  • 推荐:启动容器时挂载宿主机时区文件,-v /etc/localtime:/etc/localtime:ro,并加环境变量-e TZ=Asia/Shanghai
  • 备选:在建表时用date_trunc("hour", convert_tz(event_time, '+00:00', '+08:00')),但convert_tz函数性能较差,不建议在高频写入场景用。

我们最终选了挂载方案,验证方法是在容器里执行date命令,输出必须是CST(中国标准时间),而不是UTC。这个坑踩过一次,后面所有Docker部署StarRocks的机器,CI/CD脚本里都固化了时区配置。

4.4 性能对比实录:StarRocks vs Druid在窗口查询上的真实差距

网上“starrocks vs apache druid 性能对比”文章很多,但多数用TPC-H标准测试,和真实业务脱节。我们拿“每15分钟UV统计”这个典型场景做了7天压测:

场景StarRocks(表达式分区)Druid(TimeChunk 1h)差异原因
单次查询P95延迟0.42s1.87sDruid需扫描整个1小时segment,StarRocks只读15分钟子分区
写入吞吐(万条/秒)86124Druid基于LSM-tree,写入快;StarRocks表达式计算增加CPU开销
存储空间(1TB原始数据)1.2TB1.8TBStarRocks列存压缩率更高,且无Druid的index冗余
运维复杂度低(自动分区)高(需维护overlord调度、segment合并)Druid的segment生命周期管理是隐形成本

关键结论:如果查询以时间窗口为主,StarRocks表达式分区是更优解;如果写入吞吐是第一优先级,且窗口固定(如每小时),Druid仍有优势。但我们线上混合负载(80%查询+20%写入),综合得分StarRocks胜出。

最后分享个小技巧:StarRocks的time_slice配合物化视图,能实现准实时聚合。比如建物化视图:

CREATE MATERIALIZED VIEW mv_uv_15min AS SELECT time_slice(event_time, INTERVAL 15 MINUTE, 'floor') AS window_start, count(distinct user_id) AS uv FROM user_behavior_expr GROUP BY window_start;

这个MV会自动跟随表达式分区更新,查询时SELECT * FROM mv_uv_15min WHERE window_start = ...,延迟控制在秒级,比Flink实时计算省下3台Kafka+2台Flink集群。

我在实际用表达式分区三年后最大的体会是:它不是银弹,而是把分区设计从“DBA手工活”变成了“业务语义声明”。当你写下PARTITION BY EXPRESSION (date_trunc("hour", event_time)),你其实在告诉StarRocks:“我的数据,按业务小时切,别替我猜。”——而StarRocks真的做到了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 20:31:15

家用充电桩3C认证新规:选购安装避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 20:30:41

CogVideoX 文生视频完全拆解:5 步跑通 + 显存优化清单

CogVideoX 文生视频完全拆解&#xff1a;5 步跑通 显存优化清单 【免费下载链接】CogVideo text and image to video generation: CogVideoX (2024) and CogVideo (ICLR 2023) 项目地址: https://gitcode.com/GitHub_Trending/co/CogVideo CogVideoX 是智谱开源的视频生…

作者头像 李华