将 SQL Server 备份(.bak)中的表导出为 Parquet 格式而无需还原
Exporting tables from a SQL Server backup (.bak) to Parquet without restoring it

原始链接: https://blog.arpe.io/fastbcp-1-6-export-sql-server-backup-to-parquet

FastBCP 1.6 可直接从 SQL Server 完整备份导出表到 Parquet,无需还原、SQL Server 实例或凭据。你只需指向 .bak 文件并指定 schema 和表,它会写入分块 Parquet 文件;支持列子集,但不支持 WHERE 子句。它支持完整备份和仅复制备份、MS_XPRESS/ZSTD 压缩、行存储和列存储表,以及多种 SQL Server 数据类型。不支持加密/TDE、差异/日志以及 QAT_DEFLATE 备份。由于它读取备份页而非重放日志,在表正在写入时拍摄的备份可能不一致。用例包括:无需还原整个数据库即可恢复单表;跨 SQL Server 版本迁移,或通过 FastTransfer 迁移到 PostgreSQL;以及利用现有备份向数据湖供数而不触碰生产。性能依赖一次性完整备份扫描与缓存:320 GB 备份冷打开需 2 分 29 秒,热打开需 0.3 秒;随后 1.75 亿行表在 24 线程下导出耗时 29.6 秒。热导出可能比实时 SQL Server 导出快几倍。输出仅限 Parquet。

黑客新闻 新帖 | 过往 | 评论 | 提问 | 展示 | 工作 | 提交 登录 从 SQL Server 备份 (.bak) 导出表到 Parquet,无需恢复 (arpe.io) 9 分,由 rferraton 发布,1 小时前 | 隐藏 | 过往 | 收藏 | 1 条评论 帮助 hbarka,31 分钟前 [–] 我就是喜欢我的 Microsoft SQL Server。三十年前这东西在哪儿呢。几分钟内就能从 bak 文件找到表而无需恢复,简直是奇迹。 回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

TL;DR

FastBCP 1.6 exports a table from a SQL Server full backup to Parquet, with no restore and no SQL Server instance. On a 320 GB production backup, the first open took 2 min 29 s and later ones 0.3 s. With 24 threads, a 175-million-row table then went to Parquet in 29.6 seconds. FastTransfer can load the files into SQL Server, on any version, or into PostgreSQL.

FastBCP 1.6 reads a table straight out of a SQL Server full backup. You point it at a .bak file, name a table, and get Parquet files. There is no restore, no SQL Server instance, and no credentials to manage.

Parquet is the only output for now. That is enough to close the loop, though: FastTransfer loads those files into the SQL Server instance of your choice, and that instance can run an older version than the one that took the backup. RESTORE cannot do that.

A first export​

The new connection type is backup_mssql.

  • --server is the directory that holds the backups.
  • --database is the backup file, or a pattern that matches it (tpch_*.bak for a striped backup). The stripes can also be listed, separated by ;.

.\FastBCP.exe `

--connectiontype backup_mssql `

--server "D:\Backups\MSSQL" `

--database "tpch_full.bak" `

--sourceschema "dbo" `

--sourcetable "orders" `

--directory "D:\export\orders" `

--fileoutput "orders.parquet" `

--parallelmethod Physical `

--paralleldegree 8

With a backup as the source, --parallelmethod Physical cuts the table into as many slices as --paralleldegree. Each thread reads its own slice and you get one file per chunk (orders_chunk_000.parquet, orders_chunk_001.parquet…).

To export a subset of columns, use --query with a column list: SELECT o_orderkey, o_orderdate FROM dbo.orders. That is the whole grammar, SELECT * | col1, col2, … FROM [schema.]table. A WHERE clause is not accepted.

Here is where the files go next:

What it reads​

The driver behind backup_mssql, ArpeMSBackup, reads the backup file on its own, with no SQL Server component involved.

Backup kinds

  • Full
  • Copy-only full
  • Several backups in one file

Backup compression

  • None
  • MS_XPRESS
  • ZSTD
  • Any block size

Database layout

  • Several data files
  • Several filegroups

Rowstore tables

  • Heaps
  • Clustered indexes
  • Partitions
  • Data compression

Columnstore tables

  • Clustered columnstore
  • Archive compression
  • Open delta store
  • Deleted rows

Data types​

bigint✓

int✓

smallint✓

tinyint✓

bit✓

decimal✓

numeric✓

money✓

smallmoney✓

float✓

real✓

date✓

time✓

datetime✓

datetime2✓

smalldatetime✓

datetimeoffset✓

char✓

varchar✓

varchar(max)✓

nchar✓

nvarchar✓

nvarchar(max)✓

text✓

ntext✓

binary✓

varbinary✓

varbinary(max)✓

image✓

uniqueidentifier✓

rowversion✓

xml✓

sql_variant✓

hierarchyid✓

geography✓

geometry✓

vector(n)✓

vector(n, float16)✗

json✗

The full mapping is in the datatypes conversion page.

Restrictions​

Full backups only

Copy-only included. Differential and log backups are not applied. A file that holds no full backup is refused.

No encryption

Backups of TDE databases and encrypted backups (WITH ENCRYPTION) are refused: their pages cannot be read without the server certificate.

No QAT_DEFLATE

Backups compressed with Intel QuickAssist are not supported. MS_XPRESS and ZSTD are.

Parquet output only

On Windows x64 and Linux x64.

One more thing to keep in mind: a RESTORE replays the part of the transaction log stored in the backup to make the database consistent. FastBCP does not. If a table was being written to while the backup ran, you get its pages as they were copied. For an exact image, back up while the table is quiet.

Use cases​

Restore n-tables​

Someone dropped a table, or needs last Tuesday's version of it. The usual answer is to restore the whole database next to production, which means as much free disk as the database and hours of restore for a 2 TB backup, all to get one table back.

With FastBCP and FastTransfer, you export that one table from the backup and load it wherever you want: a scratch database, the production server under another name, or an instance running an older version. SQL Server cannot restore a backup on an older version than the one that created it, while Parquet files do not care.

The load step uses FastTransfer, one chunk per Parquet file. The target table must exist.

.\FastTransfer.exe `

--sourceconnectiontype duckdbstream `

--sourceserver ":memory:" `

--query "SELECT * EXCLUDE filename FROM read_parquet('D:\export\orders\*.parquet', filename=true)" `

--targetconnectiontype msbulk `

--targetserver "sql2019\restore" `

--targetdatabase "tpch_restore" `

--targettrusted `

--targetschema "dbo" `

--targettable "orders" `

--loadmode Truncate `

--mapmethod Name `

--parallelmethod DataDriven `

--distributekeycolumn "filename" `

--datadrivenquery "SELECT file FROM glob('D:\export\orders\*.parquet')" `

--degree 8

Migrations​

A migration starts with a bulk copy of the existing data. Reading it from last night's full backup puts no load on the source server, and the backup is often already on a share the migration team can reach. If you have to stay on a newer-to-older path, Parquet removes the version constraint too.

Say you are moving from SQL Server to PostgreSQL. FastBCP exports each table from the backup to Parquet, then FastTransfer loads the files through PostgreSQL's COPY protocol:

Run this pair of commands once per table. Create the target tables first: FastTransfer loads into tables that already exist.

.\FastBCP.exe `

--connectiontype backup_mssql `

--server "D:\Backups\MSSQL" `

--database "tpch_full.bak" `

--sourceschema "dbo" `

--sourcetable "orders" `

--directory "D:\export\orders" `

--fileoutput "orders.parquet" `

--parallelmethod Physical `

--paralleldegree 8

.\FastTransfer.exe `

--sourceconnectiontype duckdbstream `

--sourceserver ":memory:" `

--query "SELECT * EXCLUDE filename FROM read_parquet('D:\export\orders\*.parquet', filename=true)" `

--targetconnectiontype pgcopy `

--targetserver "pgserver:5432" `

--targetdatabase "tpch" `

--targetuser "FastUser" `

--targetpassword "FastPassword" `

--targetschema "public" `

--targettable "orders" `

--loadmode Truncate `

--mapmethod Name `

--parallelmethod DataDriven `

--distributekeycolumn "filename" `

--datadrivenquery "SELECT file FROM glob('D:\export\orders\*.parquet')" `

--degree 8

The final cutover can use the same path: stop the application, take a COPY_ONLY full backup, export, load. With the application stopped, the backup is consistent and no log replay is needed. Whether it beats a direct transfer depends on the size of the database and on the cutover window, so measure both.

Feeding a data warehouse or a data lake​

Most production databases are backed up every night, and those backups already sit somewhere outside production. FastBCP can turn them into Parquet files for a data lake, or for a warehouse load, without opening a single connection to production. No read replica, no extraction window, no read locks on busy tables.

Why it is fast​

Straight to the table​

The driver reads the backup as a plain binary file. It understands it well enough to go straight to the data of the requested table and skip everything else in the file. Values are decoded straight into Apache Arrow columns, which FastBCP writes to Parquet.

The first export from a backup pays a one-time cost: the whole file is read once, decompressed if needed, so the driver can find its way in it. FastBCP keeps what it learned in a small local cache. The next exports from the same backup, whatever the table, skip that step. On the 320 GB production backup measured below, the first open took 2 min 29 s and the next ones 0.3 s. A new backup file gets a fresh read.

Parallel reads, when you want them​

With --parallelmethod Physical, each thread takes its own share of the table. All threads work on the same opened backup, so it is opened once whatever the degree. For a compressed backup, they also share the decompressed data.

No locks, no latches​

There is no lock manager, no buffer latch, no other session, and no SQL Server process. A thread reads its pages and nothing waits on anything. The production server does not see the export at all.

Memory does not grow with the backup​

The backup is read in place and is never loaded into memory. Data goes once from the file to an Arrow batch. What stays in memory is a small amount of metadata, one batch per thread and, for a compressed backup, a shared decompression cache (256 MB by default). Exporting a 15-million-row rowstore table with 8 threads peaked at 674 MB for the whole FastBCP process from the uncompressed backup, 979 MB from the ZSTD one.

Columnstore tables are the exception, and today the weak spot: a rowgroup of up to a million rows is decoded whole, so memory grows with the number of threads and the number of columns. On the real tables below, 24 threads peaked at 26 GB for 62 columns and 44 GB for 124 columns, where the live export of the same table stayed at 4.5 GB. We are working on it. Until then, lower --paralleldegree on a machine with less memory.

FastBCP 1.6.5 keeps memory bounded on columnstore tables. Exporting a wide clustered columnstore table with 8 threads peaked at 42 GB before this version and peaks at 5.9 GB with it. The figures in this article were measured with 1.6.3 and 1.6.4. See the release notes.

The speed depends on two things: how fast your disk reads, and how fast the export tool decodes and writes. A local NVMe drive delivers several GB/s and a decent SSD several hundred MB/s. A USB key, or a small cloud disk whose throughput is tied to its size, will be the bottleneck. On a good disk, the limit is the export tool, and that is the part FastBCP is built for.

Performance​

Real columnstore tables, FastBCP 1.6.3​

These runs come from a real production database, not a benchmark schema: two clustered columnstore tables, one with 62 columns and one with 124, both well over 100 million rows. The source is the nightly full backup of that database: 383 tables, 320 GB striped over 16 files of 20 GB. The server is an Intel Xeon Gold 6430 machine with 128 logical processors and 1 TB of RAM, on Windows Server 2022.

Every run is FastBCP 1.6.3 writing Zstd Parquet with 24 threads: --parallelmethod Physical for the backup, --parallelmethod Ntile on an integer key for the live export through adbc_mssql, from the SQL Server 2022 instance that holds the same database. The first backup run is the cold one, the first time this backup was opened. The second one finds the backup in the cache.

Export with FastBCP 1.6.3, 24 threads (real columnstore tables)
RunRows × columnsOpenTotal timeRows/sCells/sPeak memory
Backup, cold, table A175.1 M × 62148.6 s179.1 s0.98 M61 M26.2 GB
Backup, warm, table B220.9 M × 1240.3 s71.8 s3.07 M381 M43.6 GB
Live adbc_mssql, table B220.9 M × 124–155.9 s1.42 M176 M4.5 GB
Cold vs warm backup, export speed (cells/sec)

Higher is better

Cold: first open + export, table A60.6 M

Cold: export only, table A366.4 M

Warm: cached open + export, table B381.3 M

Backup vs live database, same 124-column table (cells/sec)

Higher is better

Backup, warm381.3 M

Live database175.7 M

  • On the same 124-column table, the backup export takes 72 seconds and the live export 156: 2.2 times faster, and SQL Server did no work for it.
  • The cold open cost 2 min 29 s, once for the whole 320 GB backup, about 2.2 GB/s read from disk. After it, the 175-million-row table A went to Parquet in 29.6 seconds, 5.9 M rows/s or 366 M cells/s. Every later export from this backup starts in 0.3 s.
  • Wide tables are where cells per second tells more than rows per second: 381 million values decoded and written each second for table B.
  • Memory was the price with 1.6.3: 44 GB at the peak against 4.5 GB for the live export. FastBCP 1.6.5 lowers it, see Memory does not grow with the backup.

TPC-H SF10 on a laptop​

I ran these numbers on my laptop: an Intel i9-13950HX (8 performance cores, 16 efficiency cores), 64 GB of RAM, one NVMe SSD, Windows 11. The test database is TPC-H shaped, with one 60-million-row lineitem table (16 columns) and three 15-million-row orders tables (9 columns) that differ only in storage. I backed it up three times: uncompressed (9.9 GB), ZSTD (5.7 GB) and MS_XPRESS (3.8 GB).

Each run is FastBCP 1.6.4 writing Parquet with --parallelmethod Physical --paralleldegree 8. For each table and each backup, a cold run comes first: the driver's cache is cleared, so the export opens the backup for the first time. Three warm runs follow, and the tables give their median. For comparison, the same tables are exported from the live database on a local SQL Server 2025 instance, through adbc_mssql (the ArpeMSSQL driver) and mssql (SqlClient). Figures are cells per second (rows × columns), wall clock of the whole FastBCP process.

Warm runs: export speed with FastBCP 1.6.4 in million cells/sec (TPC-H SF10 db)
TableStorageUncompressed backupZSTD backupMS_XPRESS backupLive, adbc_mssqlLive, mssql
orders_ciclustered index88.8 M84.4 M78.0 M37.9 M15.9 M
orders_pageclustered, PAGE compression83.3 M81.3 M66.8 M35.7 M15.4 M
orders_heapheap83.3 M81.8 M75.4 M34.8 M14.4 M
lineitem_cciclustered columnstore148.8 M149.8 M145.5 M52.6 M23.8 M
Cold runs: first export after clearing the cache, in million cells/sec (open time)
TableUncompressed backupZSTD backupMS_XPRESS backup
orders_ci66.5 M (0.6 s)25.7 M (3.6 s)27.6 M (3.1 s)
orders_page60.3 M (0.6 s)42.7 M (1.5 s)37.8 M (1.6 s)
orders_heap61.4 M (0.6 s)40.2 M (1.6 s)39.0 M (1.7 s)
lineitem_cci124.2 M (0.7 s)119.3 M (1.5 s)116.9 M (1.6 s)
orders_ci (15 M rows × 9 columns), cold vs warm (cells/sec)

Higher is better

Uncompressed backup, cold66.5 M

Uncompressed backup, warm88.8 M

ZSTD backup, cold25.7 M

ZSTD backup, warm84.4 M

MS_XPRESS backup, cold27.6 M

MS_XPRESS backup, warm78.0 M

Live, adbc_mssql37.9 M

Live, mssql15.9 M

lineitem_cci (60 M rows × 16 columns), cold vs warm (cells/sec)

Higher is better

Uncompressed backup, cold124.2 M

Uncompressed backup, warm148.8 M

ZSTD backup, cold119.3 M

ZSTD backup, warm149.8 M

MS_XPRESS backup, cold116.9 M

MS_XPRESS backup, warm145.5 M

Live, adbc_mssql52.6 M

Live, mssql23.8 M

  • Warm, an orders table (15 million rows, 135 million cells) goes to Parquet in 1.5 to 2 seconds from any of the three backups. From the uncompressed backup that is 2.3 to 2.4 times the live adbc_mssql source and 5.4 to 5.8 times mssql.
  • The columnstore reaches about 149 M cells/s whatever the backup format: 60 million rows in 6.4 seconds, 2.8 times adbc_mssql and 6.3 times mssql.
  • Once the backup is open, compression costs little: ZSTD stays within 5% of the uncompressed backup, MS_XPRESS within 2 to 20%.
  • The cold run pays the open once per backup: 0.6 s for the uncompressed backup, about 1.5 s for a compressed one. orders_ci was the first table read from each compressed backup, so its cold run also read the file from disk: 3.1 to 3.6 s. After that, any table of the same backup opens in 20 to 30 ms.
  • The live figures include the work of SQL Server, which ran on the same laptop. The backup runs touched no server at all.

The three backups fit in the laptop's 64 GB of memory, so apart from that first read, the cold runs measure the open by the driver, not the disk. With a backup much larger than memory, the open is bound by the disk, as on the 320 GB backup above.

Resources​

联系我们 contact @ memedata.com