入门指南
上传没完成,账单还在走:分片上传没有保质期,三条清理路径各缺一个前提
分片上传一旦发起就没有过期时间:没 complete、也没 abort 的分片会一直保留、一直计费。把五种终态、三条清理路径的前提,以及「对象列表里没有它」的四种解释分开,再给一份能自己跑的核对顺序。
没 complete 也没 abort 的分片,不会自己过期。

先看答案
- 适合谁
- 做浏览器直传或大文件上传,重试逻辑只写在「成功」那条分支上的人
- 对着账单发愣,却在对象列表里什么都找不到的人
- 给存储桶配了「过期删除」,以为顺带也清掉了没传完的分片的人
- 你会获得
- 分片上传没有保质期:不清就一直在,也一直计费。
- 发布时间
- 实质更新
- 发布
- AOPC 编辑部
60 秒结论
一分钟看懂这篇文章
没 complete 也没 abort 的分片,不会自己过期。
人负责
- 判断哪些上传路径必须自己 abort
- 决定生命周期规则给多少天
AI 负责
- 分页数完未完成上传
- 核 abort 后分片是否为空
- 分层
- 数残留
- 核路径
- 对清单
直接答案
「上传失败了,那些没传完的分片会自己消失吗?」
不会。S3 用户指南里只有一句,没有例外条款:「After you initiate a multipart upload, there is no expiry; you must explicitly complete or stop the multipart upload.」 没有超时、没有 N 天后自动回收、没有「失败会自动清理」。分片会一直留在那里,而且一直在计费——直到你(或者一条生命周期规则)把它 stop 掉。
一次分片上传从发起到消失,只有五种结局:
| 你停在哪一步 | 服务端状态 | 分片还在不在 | 还计不计分片存储费 |
|---|---|---|---|
| 发了 CreateMultipartUpload,一个分片都没传 | 进行中(in-progress) | 没有分片 | 没有分片可计 |
| 传了一部分分片,进程被杀/网络断/浏览器关掉 | 进行中 | 在 | 在计 |
| 分片全传完,但没发 CompleteMultipartUpload | 进行中 | 在 | 在计 |
| CompleteMultipartUpload 成功 | 变成对象 | 不在(原文:the parts no longer exist) |
按对象计费 |
| AbortMultipartUpload 成功 | 已中止 | 已上传的释放;在途的仍可能落地 | 通常停止,但可能残留 |
这张表里最容易被忽略的是第二行和第三行:「上传失败」在服务端不是一种状态。服务端只知道这次上传还在进行中,分片还在它那里。失败是你的客户端的事,清理也得由你的客户端(或一条规则)来做。
一、先把「一次上传」拆成四个动作
分片上传的 API 面很小,只有四个动作:
| 动作 | 做什么 | 关键返回值 |
|---|---|---|
CreateMultipartUpload |
发起,声明目标 key 与元数据 | upload ID(后续所有动作都要带) |
UploadPart |
传第 N 片 | 每一片的 ETag,必须自己记下来 |
CompleteMultipartUpload |
按分片号升序拼接成对象 | 合并后的 ETag |
AbortMultipartUpload |
中止,释放已上传分片 | 204 |
几个常被忽略的细节,都在这四步里:
- upload ID 是唯一的一把钥匙。 原文:「This upload ID is required when you upload parts, list parts, complete an upload, or stop an upload.」丢了它,你既 complete 不了,也 abort 不了——只能等生命周期规则,或者按 key 去列表里把它找回来。
- 分片号可以从 1 到 10,000,允许跳号(原文举例 1、5、14)。但同一个分片号重复上传会覆盖前一片——「if you upload a new part using the same part number as a previously uploaded part, the previously uploaded part gets overwritten」。所以重试同一片是安全的,重试用错号则会留下两片、最后拼出一个你没预期的对象。
- ETag 必须由客户端记住。 原文明确:列出来的分片清单「use the returned listing only for verification」,并且「Do not use the result of this listing when sending a complete multipart upload request」——complete 要的是你自己记录的分片号与 ETag 对照表。
- 合并后的 ETag 不一定是 MD5。 原文:「This ETag is not necessarily an MD5 hash of the object data.」把 ETag 当内容哈希来做完整性校验,从这里就开始错了。
- 匿名用户不能发起分片上传:「Anonymous users cannot initiate multipart uploads.」
尺寸上的硬规格(来自 limits 页,不是建议):单个对象最大 48.8 TiB;单次上传最多 10,000 片;分片号 1–10,000;每片 5 MiB–5 GiB,最后一片没有最小限制。官方对「该不该用分片上传」的建议阈值是 100 MB(两处措辞略有不同:概览页写「best practice … for objects that are 100 MB or larger」,limits 页写「when your object size reaches 100 MB, you should consider」)。
还有一条只在开了 checksum 时才出现的坑:带校验和的分片上传,「part numbers … must use consecutive part numbers and begin with 1」,若不连续,CompleteMultipartUpload 会返回 HTTP 500。这是「分片都传完了却怎么也合并不了」的一种常见成因——上传是好的,规则不满足。
二、「没有保质期」的四层含义
第一层:没有 TTL。 上面那句 there is no expiry 是字面意思。你不会收到「您的上传已过期」的通知,因为它不会过期。
第二层:只有两个动作能停止计费。 原文:「After you initiate a multipart upload and upload one or more parts, you must either complete or stop the multipart upload to stop incurring charges for storage of the uploaded parts. Only after you complete or stop a multipart upload will Amazon S3 free up the parts storage and stop billing you for the parts storage.」
第三层:stop 之后,这把钥匙作废。 「After stopping a multipart upload, you can’t upload any part using that upload ID again.」仍在重试的客户端会开始收到错误——如果你的重试逻辑不看错误码,它可能会新开一次上传,于是你又多了一个孤儿。
第四层:stop 不是「一键清空」。 这是最反直觉的一条,原文写得很直白:「If part uploads were in progress, they can still succeed or fail even after you stop the upload. To make sure you free all storage consumed by all parts, you must stop a multipart upload only after all part uploads have completed.」
也就是说:在上传还在飞的时候 abort,可能只是让一部分分片消失。Abort API 页把结论说得更狠:「it might be necessary to abort a given multipart upload multiple times in order to completely free all storage consumed by all parts」,并且要求你用 ListParts 复核清单为空——「To verify that all parts have been removed and prevent getting charged for the part storage, you should call the ListParts API operation and ensure that the parts list is empty.」
三、计费跟谁走:一次上传的账单有三个部分
原文对计费的口径是整段的:「Throughout its lifetime, you are billed for all storage, bandwidth, and requests for this multipart upload and its associated parts.」——存储、流量、请求三样都在计,而且是从发起那一刻算起,不是从「对象出现」算起。
存储这部分按上传分片时指定的存储类计,但归档类有一层专门的错位:上传到 Glacier Flexible Retrieval 或 Glacier Deep Archive 的分片「you will not be billed for these parts」,可是在传完之前,这些在途分片按 S3 Standard 费率计「staging storage」;CreateMultipartUpload 与 UploadPart 也都按 S3 Standard 计,只有 CompleteMultipartUpload 按归档类的费率计。所以「我要传到 Glacier,所以传的过程中很便宜」这个直觉是错的。
反过来,清理不额外收钱——这句在两页里各出现一次:「There are no early delete charges for deleting incomplete multipart uploads regardless of storage class specified.」(lifecycle 页的措辞是「you will not incur early delete charges for S3 Lifecycle when you remove any incomplete multipart upload parts」。)
本文不引用任何单价:这几页都没有给价格,只给了计费口径。
四、三条清理路径,各缺一个前提
路径 A:应用自己 abort(唯一能「立刻」生效的一条)
前提不是「写一个 abort 调用」,而是每一条失败路径上都有它。一次上传可能死在这些地方:
| 失败形态 | 谁还活着 | 谁该 abort |
|---|---|---|
| 进程被 kill、容器被回收 | 没人 | 没有代码可执行 → 只能靠规则或对账脚本 |
| 网络中断、上传超时 | 客户端 | 客户端的重试封装层 |
| 浏览器标签页关闭 | 页面没了 | 服务端签发的上传会话要有回收机制 |
| 客户端主动取消 | 客户端 | 取消回调里 |
| 分片传完但 complete 报错(例如分片号不连续导致 500) | 客户端 | 错误处理分支,不是成功分支 |
最后一行值得单独说:complete 失败时最容易忘记 abort,因为代码路径「看起来已经成功了」——分片都传完了。但服务端的状态仍是「进行中」。
路径 B:生命周期 AbortIncompleteMultipartUpload(唯一能收拾「没人活着」的一条)
规则形状很简单,官方示例是 7 天:
<LifecycleConfiguration>
<Rule>
<ID>sample-rule</ID>
<Prefix></Prefix>
<Status>Enabled</Status>
<AbortIncompleteMultipartUpload>
<DaysAfterInitiation>7</DaysAfterInitiation>
</AbortIncompleteMultipartUpload>
</Rule>
</LifecycleConfiguration>
它的六个边界,每一条都影响你怎么配:
- 7 天是示例,不是默认。 原文只说「Any multipart uploads that were initiated and weren’t completed within seven days become eligible for an abort operation」——这是示例规则的效果,不是产品行为。你配 1 天就是 1 天,配 30 天就是 30 天。
- 只作用于未完成的上传。 「The abort action has no effect on completed multipart uploads.」
- 不删对象。 「this action doesn’t apply to objects. No objects are deleted by this lifecycle action.」这条要写进 runbook,否则半年后没人敢开它。
- 对已有和新建的都生效。 「This rule applies to both existing multipart uploads and those that you create later.」——开规则那天,桶里已经躺着的残留会被一起清掉。
- 按发起时间算,不是按最后一片的时间。 判据是
DaysAfterInitiation。一个「昨天发起、今天还在慢慢传」的大文件,在第 8 天会被判为可中止——如果你的上传本来就慢(弱网、几十 GB),天数要给够。 - 是「eligible(符合条件)」,不是「准点执行」。 原文用词是「it becomes eligible for an abort operation. Amazon S3 then stops the multipart upload and deletes the parts」——到点之后被排队处理,不要把它当成秒级定时器。
示例里的 <Prefix></Prefix> 是空的,所以「the rule applies to all objects in the bucket for which you initiated multipart uploads」。想只清某个目录(uploads/tmp/),就在 Prefix 里写它;写窄了,别的路径的残留就永远清不到。
路径 C:一次性对账脚本(唯一能回答「现在到底有多少」的一条)
生命周期规则是往后的保险,不是现在的盘点。要回答「我这个桶里到底堆了多少没传完的东西」,只能自己列:
ListMultipartUploads列进行中的上传,单次最多 1,000 条,而 1,000 也是默认值;超过就返回IsTruncated=true加上NextKeyMarker与NextUploadIdMarker,下一次请求要把这两个值分别塞回key-marker与upload-id-marker(directory bucket 只支持key-marker)。- 返回结果先按 key 升序,同一个 key 再按发起时间升序(「the one that was initiated first will appear before the ones that were initiated later」)。所以同一个 key 的多次失败重试会连在一起出现——这正好是你最需要看到的形态。
ListParts列某一次上传的分片,同样单次最多 1,000、默认 1,000,翻页用NextPartNumberMarker。
「只清一页」是这个脚本最常见的 bug:桶里 3,000 个残留,跑一次只清了 1,000 个,第二周看账单「怎么还有」。翻页条件要写成 IsTruncated 为真就继续,而不是「跑完一次就报成功」。
五、为什么你在对象列表里看不到它
这是「明明在花钱,却什么都找不到」的根源。四个原因,层层叠加:
① 对象列表不是上传列表。 ListObjectsV2 列的是对象;没 complete 的上传还不是对象,永远不会出现在里面。要看残留,只能用 ListMultipartUploads。
② 分片列表不含在途分片。 原文:「the returned list of parts doesn’t include parts that haven’t finished uploading.」所以「ListParts 只返回了 3 片」不等于「只传上去 3 片」,可能还有 5 片正在飞。
③ 任何列表都只给你一页 1,000 条。 控制台、CLI、SDK 都一样受这个上限约束。看到「列表是空的」,先确认你看的是第一页、且没有 IsTruncated。
④ 存储指标里其实有它,但慢一天。 CloudWatch 的 BucketSizeBytes 定义里明确包含「the size of all parts for all incomplete multipart uploads to the bucket」——通用存储桶的容量指标是把未完成分片算进去的。代价是它是「daily storage metrics」,按天投递,所以「今天传失败、明天才在曲线上看到」是正常延迟,不是指标坏了。另外,directory bucket 的同一指标不含未完成分片——「doesn’t include incomplete multipart uploads to the bucket」,两种桶不能互相对照。
顺带一条只有 directory bucket 才会撞上的硬约束:只要桶里还有进行中的分片上传,这个桶就删不掉——必须先 list 出来、再逐个 abort 或 complete。
六、abort 本身也不干脆
把 abort 当成「删除」会踩三个坑:
坑一:它可能要做多次。 见第二节第四层。正确顺序是:先确认没有在途分片(客户端侧停止重试、等上传结束),再 abort,再 ListParts 复核为空。
坑二:重复 abort 会返回 404。 AbortMultipartUpload 成功是 204;upload ID 不存在时返回 NoSuchUpload(404)。对账脚本里这不该算失败——它通常意味着「上一次已经清掉了」,或者「这个 ID 属于别的桶」。把它当异常抛出去,脚本会在第一个已清理的条目上停住。
坑三:权限不一定有。 abort 需要 s3:AbortMultipartUpload。默认情况下「the bucket owner and the initiator of the multipart upload are allowed to perform this action」;但如果走 VPC endpoint policy,「the initiator of the multipart upload doesn’t automatically gain the permission」——一个只被授予了 PutObject 的上传角色,很可能能传不能清,失败路径上的 abort 会静默 403。
七、十个核对项
下面这套顺序是编辑判断(API 名称取自上述文档,命令语法以你所用 CLI/SDK 版本为准),原则是先数清楚,再决定清理方式:
| # | 检查项 | 看什么 | 漏了会怎样 |
|---|---|---|---|
| 1 | 桶里有没有进行中的上传 | ListMultipartUploads,翻完所有页 |
以为没有残留,其实第一页之后全是 |
| 2 | 残留的年龄分布 | 每个 upload 的发起时间 | 规则天数配得比正常上传时长还短 |
| 3 | 单个上传的分片数与在途状态 | ListParts(注意不含在途分片) |
把「还没传完」当成「传完了」 |
| 4 | 生命周期有没有 AbortIncompleteMultipartUpload |
桶的 lifecycle 配置 | 没人活着的那批永远不清 |
| 5 | 规则的 Status 与 Prefix |
是否 Enabled、前缀覆盖哪些路径 | 规则存在但只覆盖了一个目录 |
| 6 | DaysAfterInitiation 是多少、谁定的 |
规则里的数字 | 7 天被当成默认值照抄 |
| 7 | 应用失败路径是否 abort | 超时、取消、complete 报错、进程退出 | 只有成功分支写了清理 |
| 8 | 执行 abort 的角色有没有权限 | 是否含 s3:AbortMultipartUpload |
清理脚本全线 403 |
| 9 | abort 之后是否复核 | 再跑一次 ListParts,确认空 |
中止了,但分片还在计费 |
| 10 | 容量指标是否对得上 | BucketSizeBytes 的趋势(按天、含未完成分片) |
用对象列表的总量去对账,永远差一截 |
一段最小示例(语法未在真实桶上执行,仅示意顺序):
# 1. 数清楚:所有进行中的上传(注意翻页)
aws s3api list-multipart-uploads --bucket example-bucket --max-uploads 1000
# 2. 看某一次上传的分片(空列表才算干净)
aws s3api list-parts --bucket example-bucket --key uploads/big.zip \
--upload-id <upload-id>
# 3. 确认客户端已停止重试后,再中止
aws s3api abort-multipart-upload --bucket example-bucket --key uploads/big.zip \
--upload-id <upload-id>
# 4. 复核:这一步不做,前一步等于没做
aws s3api list-parts --bucket example-bucket --key uploads/big.zip \
--upload-id <upload-id>
# 5. 看规则到底配了什么
aws s3api get-bucket-lifecycle-configuration --bucket example-bucket
第 4 步的复核不是形式主义:Abort 的响应只说明「这次请求被接受了」,分片是否真的清空要看列表。
八、六种常见误读
误读一:「上传失败会自动清理。」
不会。失败是客户端的状态,服务端只知道这次上传还在进行中。原文 there is no expiry 已经把话说死。
误读二:「对象列表里没有,就没在花钱。」 对象列表列的是对象;没 complete 的分片不是对象。它们既不在对象列表里,也照样计存储、流量与请求。
误读三:「配了生命周期,7 天后就会自动删。」
两个错:7 天是示例值,得你自己写;而且这条规则只中止未完成的上传,不删任何对象。反过来,如果你配的是「对象过期」(Expiration),它也不会顺带清掉未完成的分片——两件事不同。
误读四:「abort 一次就干净了。」
在途分片可能仍然落地。文档要求「stop … only after all part uploads have completed」,并要求用 ListParts 复核为空;必要时多次 abort。
误读五:「分片上传一定更快。」 文档把它列在 advantages 里(「You can upload parts in parallel to improve throughput」),但那是「并行上传可以提升吞吐」的机制描述,不是对你这条链路、这个文件、这个网络的承诺。真正确定的是它的失败恢复价值:只要重传坏掉的那一片,不用从头再来。
误读六:「complete 成功之后,分片还占一份空间。」 不占。原文:「After a successful complete request, the parts no longer exist.」合并之后只剩对象本身。真正会「两份都算」的,是同一个 key 的并发上传:在开了版本控制的桶里,「the current version of the object is determined by which upload started most recently (createdDate)」——哪怕先发起的那次后完成,current version 也归后发起的那次。没开版本控制的桶更微妙:文档说在这个窗口里进来的其它请求「might take precedence」,甚至可能出现「complete 返回成功,但你从没看见这个对象」。
九、本周就能做的五件事
- 跑一次全量
ListMultipartUploads,把页翻完。 记下三个数:总数、最老一条的发起时间、涉及几个 key 前缀。多数人第一次跑会发现残留比自己想的多一个数量级。 - 检查生命周期规则有没有
AbortIncompleteMultipartUpload。 没有就加上,天数按你最慢的一次正常上传留足余量;有就核对Prefix覆盖的路径是不是你要的那些。 - 把失败路径画出来。 超时、取消、进程被杀、complete 报错,四条线各自有没有 abort?画完你会发现「进程被杀」这条线根本没有代码可执行——它只能靠规则兜底。
- 给清理脚本加两个断言:翻页直到
IsTruncated为假;abort 之后ListParts为空。少任何一个,脚本都会在「看起来跑完了」的状态下留下残余。 - 在一张纸上写下你的分片上传上限:单片多大、最多几片、失败重试几次、几天后强制中止。四个数字定下来,下一次「上传卡住」时就不用现想。
如果只能记住一件事:分片上传不是「传文件」,是「先开一个账户,再往里存东西,最后结账」。不开 complete、也不开 abort,这个账户就一直开着——而它不会自己关门。
边界说明:本文的机制性陈述全部来自 Amazon S3 用户指南与 API Reference 的七个公开页面(2026-10-30 逐条访问,均 HTTP 200,字节数见「来源」)。不写任何单价与账单金额(原文只给计费口径:存储、带宽、请求三样都计,未在这几页给价格);不把 7 天写成默认值(它是官方示例里的 DaysAfterInitiation);不把 AbortIncompleteMultipartUpload 写成会删对象(原文明确「No objects are deleted by this lifecycle action」);不写「上传失败会自动清理」;不写「分片上传一定更快」;不写「控制台能看到全部未完成上传」(受单页 1,000 条上限约束,且控制台行为未在本文逐一核对)。文中标注为编辑判断的有:第五节「四个原因」的归类顺序、第七节的核对顺序与命令示例(语法未在真实桶上执行)、「进程被杀只能靠规则兜底」的推论、以及第九节的五件事。本文只覆盖 S3 通用存储桶与 directory bucket 在分片上传上的公开语义,不涉及跨区域复制、S3 Transfer Acceleration、多部分拷贝(UploadPartCopy)的细节,也不涉及具体客户端库的实现差异。
来源
以下页面均于 2026-10-30 逐条访问并验证为 HTTP 200;字节数为当日抓取的实际大小。
- Uploading and copying objects using multipart upload in Amazon S3核查于 2026/10/30(200,53 273 B):分片上传是「initiate → upload parts → complete」三步;「After you initiate a multipart upload, there is no expiry; you must explicitly complete or stop the multipart upload.」;「you must either complete or stop the multipart upload to stop incurring charges for storage of the uploaded parts. Only after you complete or stop a multipart upload will Amazon S3 free up the parts storage and stop billing you for the parts storage.」;停止后「you can’t upload any part using that upload ID again」,且「If part uploads were in progress, they can still succeed or fail even after you stop the upload」,因此要「stop a multipart upload only after all part uploads have completed」;complete 之后「the parts no longer exist」,合并 ETag「is not necessarily an MD5 hash of the object data」;
ListParts单次最多 1,000 片、ListMultipartUploads单次最多 1,000 个,「the returned list of parts doesn’t include parts that haven’t finished uploading」,且该清单「only for verification」、不得用于 complete;带 checksum 时分片号必须从 1 连续,否则 complete 返回 HTTP 500;同一 key 并发上传时,版本控制桶的 current version 由「which upload started most recently (createdDate)」决定,未开版本控制的桶「might take precedence」,甚至可能「successful object creation without you ever seeing the object」;计费段:「Throughout its lifetime, you are billed for all storage, bandwidth, and requests for this multipart upload and its associated parts」,Glacier 类的在途分片按 S3 Standard 费率计 staging storage、只有 complete 按归档费率计;「There are no early delete charges for deleting incomplete multipart uploads regardless of storage class specified.」;100 MB 建议阈值;匿名用户不能发起;权限表(abort 需s3:AbortMultipartUpload,VPC endpoint policy 下发起者不自动获得)。 - Configuring a bucket lifecycle configuration to delete incomplete multipart uploads核查于 2026/10/30(200,24 638 B):规则用
AbortIncompleteMultipartUpload+DaysAfterInitiation表达「发起后 N 天未完成即可中止」;示例为 7 天、Prefix为空即覆盖桶内全部「for which you initiated multipart uploads」;「This rule applies to both existing multipart uploads and those that you create later.」;「The abort action has no effect on completed multipart uploads.」;「this action doesn’t apply to objects. No objects are deleted by this lifecycle action.」;「you will not incur early delete charges for S3 Lifecycle when you remove any incomplete multipart upload parts」;完成于期限内的上传不触发该动作。 - Amazon S3 multipart upload limits核查于 2026/10/30(200,13 850 B):最大对象 48.8 TiB;单次上传最多 10,000 片;分片号 1–10,000;分片大小 5 MiB–5 GiB,「There is no minimum size limit on the last part of your multipart upload」;
ListParts与ListMultipartUploads单次最多返回 1,000 条;「when your object size reaches 100 MB, you should consider using multipart uploads」。 - ListMultipartUploads核查于 2026/10/30(200,65 248 B):返回进行中的上传;「returns a maximum of 1,000 multipart uploads … The limit of 1,000 multipart uploads is also the default value」;超过时返回
IsTruncated=true与NextKeyMarker/NextUploadIdMarker,后续请求用key-marker与upload-id-marker续列(directory bucket 不支持upload-id-marker);排序为「key 升序,同 key 按发起时间升序」;directory bucket 在有进行中上传时不能删桶。 - ListParts核查于 2026/10/30(200,52 921 B):列某次上传已上传的分片;「returns a maximum of 1,000 uploaded parts. The limit of 1,000 parts is also the default value」;超过时返回
IsTruncated=true与NextPartNumberMarker,用part-number-marker续列。 - AbortMultipartUpload核查于 2026/10/30(200,32 537 B):成功后返回
204;「the storage consumed by any previously uploaded parts will be freed」;「if any part uploads are currently in progress, those part uploads might or might not succeed. As a result, it might be necessary to abort a given multipart upload multiple times in order to completely free all storage consumed by all parts.」;「To verify that all parts have been removed and prevent getting charged for the part storage, you should call the ListParts API operation and ensure that the parts list is empty.」;upload ID 不存在时返回NoSuchUpload(404)。 - Amazon S3 daily storage metrics for buckets in CloudWatch核查于 2026/10/30(200,79 889 B):
BucketSizeBytes为按天投递的存储指标,计算方式为「summing the size of all objects and metadata … (both current and noncurrent objects), including the size of all parts for all incomplete multipart uploads to the bucket」;对 directory bucket 则「doesn’t include incomplete multipart uploads to the bucket」。