DML, MERGE and transactions

BigQuery · Practical · 12 min

Incremental pipelines upsert data with MERGE, and BigQuery's DML has quirks that differ from OLTP databases.

BigQuery supports full DML: INSERT, UPDATE, DELETE, TRUNCATE TABLE and MERGE.

  • UPDATE and DELETE require a WHERE clause; write WHERE TRUE to affect every row.
  • MERGE matches a source to a target and runs WHEN MATCHED THEN UPDATE | DELETE, WHEN NOT MATCHED [BY TARGET] THEN INSERT and WHEN NOT MATCHED BY SOURCE THEN ... branches, the standard way to upsert a batch of changes.
  • DML is billed like a query: on-demand, by bytes scanned in the tables it reads and modifies.

Performance traits come from the storage design. Data lives in large immutable files, so DML rewrites whole files, and it is built for set-based batches, not row-by-row changes. One MERGE of 100,000 changed rows beats 100,000 single-row UPDATEs by orders of magnitude. Only a small number of mutating DML statements run concurrently on one table and the rest queue, so many parallel small writers collide. Filtering DML on partition and clustering columns limits how much it rewrites.

Multi-statement transactions give atomicity across statements and tables in a script: BEGIN TRANSACTION; ... COMMIT TRANSACTION;, with ROLLBACK TRANSACTION on error. They use snapshot isolation; a conflicting concurrent change makes one transaction fail.

Rows recently written with the legacy insertAll streaming API sit in a streaming buffer and can't be changed by UPDATE, DELETE or MERGE until they're flushed. Prefer the Storage Write API or batch loads for data you'll modify.

Example

-- Upsert a batch of changes from a staging table
MERGE shop.customers AS t
USING shop.customers_staging AS s
ON t.id = s.id
WHEN MATCHED AND s.deleted THEN
  DELETE
WHEN MATCHED THEN
  UPDATE SET email = s.email, plan = s.plan, updated_at = s.updated_at
WHEN NOT MATCHED AND NOT s.deleted THEN
  INSERT (id, email, plan, updated_at) VALUES (s.id, s.email, s.plan, s.updated_at);

-- Atomic multi-table change
BEGIN TRANSACTION;
  INSERT INTO shop.orders_archive SELECT * FROM shop.orders WHERE status = 'closed';
  DELETE FROM shop.orders WHERE status = 'closed';
COMMIT TRANSACTION;

UPDATE shop.customers SET plan = 'free' WHERE TRUE;   -- WHERE is mandatory

Remember

  • MERGE upserts and deletes a batch in one statement
  • DML is batch-oriented: few large statements, not many tiny ones
  • UPDATE/DELETE need a WHERE clause
  • BEGIN/COMMIT TRANSACTION for multi-statement atomicity

Watch out for

  • Updating rows one at a time from an application loop, causing queued DML and huge costs.

Check yourself

  1. You need to apply 50,000 changed customer rows daily. What's the idiomatic approach?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax