Data Manipulation Language (DML) is the engine room of most applications—INSERT, UPDATE, DELETE, and MERGE statements that move data between users, services, and storage. Yet as data volumes grow and pipelines become more complex, many teams find their once-clean DML workflows turning into tangled, slow, and error-prone processes. This guide draws on patterns observed across dozens of projects to help you refine your approach. We'll cover the foundations that are often misunderstood, the patterns that hold up under pressure, and the anti-patterns that quietly sink performance. We'll also explore when it's smarter to step away from DML entirely. By the end, you'll have a set of qualitative benchmarks to evaluate your own workflows and a clear direction for your next experiments.
Where DML Workflows Show Up in Real Work
DML isn't just about writing SQL in a query window. It's embedded in every layer of modern data systems: batch ETL jobs that move millions of rows between staging and production tables, real-time stream processors that upsert event data, ORM-generated statements that back web application features, and stored procedures that enforce business rules. The same DML patterns appear in cloud data warehouses like Snowflake and BigQuery, in traditional relational databases like PostgreSQL and SQL Server, and in NoSQL systems that offer SQL-like interfaces.
Consider a typical e-commerce scenario: an order processing system receives thousands of updates per minute—inventory adjustments, shipment status changes, customer profile edits. Each update translates to an UPDATE or INSERT statement. If those statements are poorly structured, locks pile up, transaction logs swell, and response times degrade. Another common case: a data pipeline that merges nightly feeds from multiple sources into a central analytics table. A naive approach might delete all rows and re-insert, causing downtime and wasted resources. A smarter approach uses MERGE or upsert logic, but even that can go wrong if not tuned.
We've seen teams struggle with DML in microservices architectures where each service owns its database but needs to synchronize data with others. The temptation is to use distributed transactions or complex trigger chains, but those often introduce more problems than they solve. The field context for DML is everywhere, and the trends we're seeing point toward simpler, set-based operations with careful isolation levels and error handling.
Real-world example: inventory updates at scale
In a project we observed, a retail company processed inventory changes from 200 stores. Each store sent updates every 30 seconds. The original implementation used individual UPDATE statements in a loop—one per product per store. As the product catalog grew, the loop took over 10 minutes, causing timeouts and stale data. Switching to a single UPDATE with a joined temp table reduced execution time to under 2 seconds. This is the kind of pattern shift that defines modern DML workflow refinement.
The shift toward declarative, set-based logic
Modern DML trends emphasize declarative operations over procedural loops. Databases are optimized for set-based operations; row-by-row processing fights the engine. We'll see why this matters in the next section, but the key takeaway for now is that understanding where DML appears in your system is the first step to refining it.
Foundations Readers Often Confuse
Several core DML concepts are frequently misunderstood, leading to suboptimal workflows. One common confusion is the difference between DELETE and TRUNCATE. DELETE is DML; it removes rows one by one, logs each deletion, and can be rolled back. TRUNCATE is DDL; it deallocates pages, logs only page deallocations, and cannot be rolled back in most databases. Using DELETE when TRUNCATE is appropriate can bloat transaction logs and slow down cleanup jobs. Conversely, using TRUNCATE when you need to filter rows or maintain foreign key constraints will fail.
Another area of confusion is the behavior of UPDATE with joins. Many developers assume that an UPDATE with a JOIN will update each row exactly once, but depending on the join cardinality, it can update the same row multiple times—or not at all. The SQL standard doesn't guarantee a specific order of updates, so if the join produces multiple matches for a source row, the result is unpredictable. This is a classic source of bugs in merge operations.
The MERGE statement itself is a frequent source of confusion. While it seems like a convenient way to perform upserts, it has well-documented issues: it can cause race conditions, it locks more rows than necessary, and in some databases (like SQL Server), it can lead to unexpected deadlocks and incorrect results if not carefully written. Many practitioners now recommend using separate INSERT and UPDATE statements with explicit locking hints instead of MERGE for high-concurrency scenarios.
Isolation levels also trip people up. READ COMMITTED is the default in many databases, but it allows non-repeatable reads and phantom reads. For DML workflows that read then write (like checking inventory before updating), a higher isolation level like REPEATABLE READ or SERIALIZABLE may be necessary to prevent lost updates. However, higher isolation levels increase locking and contention. Understanding the trade-off is crucial for designing workflows that are both correct and performant.
Common mistake: assuming atomicity of multi-statement DML
Unless explicitly wrapped in a transaction, each DML statement is its own implicit transaction. If you run an UPDATE followed by an INSERT and the INSERT fails, the UPDATE is not rolled back. This seems obvious, but we've seen production incidents where developers assumed atomicity across statements. Always use explicit transactions when multiple DML operations must succeed or fail together.
The role of constraints and triggers
Constraints (PRIMARY KEY, UNIQUE, CHECK) are often overlooked during DML design. They enforce data integrity but can cause unexpected failures if not accounted for. Triggers, while powerful, add hidden complexity: they execute within the same transaction, can cascade, and are often a source of performance surprises. Many teams now prefer to enforce business rules in the application layer rather than in triggers, keeping DML simpler and more predictable.
Patterns That Usually Work
Over time, a set of DML patterns has emerged that consistently deliver good results across different systems. The first is the set-based update pattern: instead of updating rows one at a time, build a temporary table or CTE that holds the new values and join it in the UPDATE statement. This leverages the database's optimizer and minimizes round trips.
Another reliable pattern is the batch-insert-with-error-logging approach. When inserting large volumes of data, use a staging table, validate the data, and then insert from staging to the target. Any rows that fail validation can be logged separately. This avoids partial inserts and makes debugging straightforward.
For upserts, the recommended pattern is to first attempt an UPDATE, check the number of affected rows, and if zero, perform an INSERT. This is known as "update-else-insert" and is simpler and safer than MERGE in many databases. Wrap both statements in a transaction with proper error handling to avoid race conditions.
Using window functions with DML is a growing trend. For example, you can use ROW_NUMBER() to deduplicate data before an INSERT, or use LAG() to calculate differences before an UPDATE. This keeps the logic in SQL rather than moving it to application code, which can be more efficient.
Bulk operations with table-valued parameters
In SQL Server and PostgreSQL, you can pass a table-valued parameter (TVP) to a stored procedure and then use it in a single DML statement. This avoids multiple round trips and is much faster than sending individual rows. It's a pattern that scales well for mid-sized batches (hundreds to thousands of rows).
Using CTEs for readability and performance
Common Table Expressions (CTEs) can make complex DML more readable by breaking down the logic into steps. However, note that CTEs are not always materialized; in some databases, they are re-evaluated each time they are referenced. Use them wisely, and consider temporary tables for CTEs that are referenced multiple times in the same statement.
Anti-Patterns and Why Teams Revert
Despite good intentions, many teams fall into DML anti-patterns that degrade performance and maintainability. The most common is the "cursor loop"—using a cursor to iterate over rows and perform DML individually. This is almost always slower than a set-based operation and should be avoided unless there is no other way (e.g., calling a stored procedure for each row). Even then, consider batching the calls.
Another anti-pattern is overusing triggers. Triggers execute for every row affected by a DML statement, which can multiply execution time. They also make debugging difficult because the logic is hidden. Many teams eventually revert to explicit application logic or scheduled jobs.
Using SELECT * in DML statements (like INSERT...SELECT *) is risky because it relies on column order and breaks if the table schema changes. Always specify columns explicitly. Similarly, relying on implicit column lists in INSERT statements is fragile.
Another pattern that leads to reversion is the "single massive transaction." Wrapping millions of DML operations in one transaction can exhaust log space, hold locks for too long, and cause blocking. Break large operations into batches of a few thousand rows, with each batch in its own transaction.
The MERGE debacle
MERGE was supposed to simplify upserts, but many teams have reverted to separate INSERT and UPDATE due to its quirks. In SQL Server, MERGE can cause deadlocks even under read-committed isolation. In PostgreSQL, MERGE was only added in version 15, and its performance is still being tuned. The separate statements pattern is more predictable and easier to optimize.
Ignoring index maintenance
DML operations can fragment indexes, especially if they involve many random updates or deletes. Teams often skip index rebuilds or reorganizes until performance degrades noticeably. A proactive schedule of index maintenance (based on fragmentation thresholds) is part of a healthy DML workflow.
Maintenance, Drift, and Long-Term Costs
DML workflows are not write-once artifacts; they evolve as data volumes, schemas, and business rules change. Without careful maintenance, they drift into inefficiency. One common cost is the accumulation of unused indexes: as DML patterns change, indexes that once helped may become overhead. Regularly review index usage statistics and drop unused ones.
Another long-term cost is the growth of transaction logs. Poorly designed DML (like large updates without batching) can cause logs to grow uncontrollably. Implement log management strategies, such as frequent log backups in full recovery mode or using simple recovery model where appropriate.
Schema changes also impose costs. Adding a column to a large table requires an ALTER TABLE, which can lock the table and block DML. Online schema change tools (like pt-online-schema-change for MySQL or ALTER TABLE ... ALTER COLUMN with ONLINE in SQL Server) can reduce downtime but add complexity to the DML workflow.
Code drift in stored procedures
Stored procedures that contain DML are notorious for drifting out of sync with application code. Version control for database code is essential—treat DML scripts as first-class artifacts. Use migration tools (like Flyway or Liquibase) to manage changes and ensure that the database state matches the codebase.
The hidden cost of implicit conversions
When DML statements compare columns of different data types (e.g., comparing a VARCHAR column to an integer), the database performs an implicit conversion. This can prevent index usage and cause full table scans. Over time, these hidden costs add up. Audit your DML for type mismatches and fix them.
When Not to Use This Approach
Not every data manipulation problem is best solved with DML. There are cases where moving logic to the application layer or using a different tool yields better results. For example, complex business rules that involve multiple external API calls are better handled in application code, not in a stored procedure with DML.
Another scenario is when you need to process data that doesn't fit in a relational model. If you're dealing with unstructured or semi-structured data (like JSON blobs, images, or logs), a document database or object store might be more appropriate. Trying to force such data into DML operations leads to awkward schemas and poor performance.
Real-time streaming scenarios also challenge traditional DML. If you need to process millions of events per second, a database with row-level DML may not keep up. Instead, consider stream processing frameworks (like Apache Kafka Streams or Apache Flink) that can aggregate and transform data before writing to a database in batches.
Finally, if your DML workflow requires distributed transactions across multiple databases, you should question the architecture. Distributed transactions are complex, slow, and error-prone. A better approach is to use eventual consistency with compensating transactions or a saga pattern. In such cases, DML is still used, but the orchestration moves to the application layer.
When to avoid triggers and stored procedures
If your team lacks database expertise or if the logic changes frequently, it's better to keep DML simple (plain INSERT/UPDATE statements) and put business logic in the application. Triggers and stored procedures add a layer of abstraction that can become a bottleneck for development velocity.
Open Questions and FAQ
We often hear the same questions from teams refining their DML workflows. Here are some of the most common, with our take based on observed patterns.
Should we use MERGE or separate INSERT/UPDATE?
For most use cases, separate INSERT and UPDATE statements are safer and more predictable. MERGE can be tempting for its brevity, but its edge cases (deadlocks, incorrect results with concurrent modifications) make it a risky choice for high-concurrency systems. Start with separate statements and only consider MERGE if you have a specific performance need and have tested thoroughly.
How do we handle concurrency in DML?
Use appropriate isolation levels. For most web applications, READ COMMITTED with row versioning (snapshot isolation) is a good default. For critical updates (like inventory), consider using SELECT...FOR UPDATE or optimistic locking with a version column. Avoid long-running transactions.
What's the best way to log DML changes?
Change Data Capture (CDC) features are available in many databases (SQL Server CDC, PostgreSQL logical replication, MySQL binlog). These are more efficient than triggers for auditing. If you need simple logging, consider application-level logging rather than database triggers.
How do we test DML workflows?
Use a staging environment with a copy of production data (anonymized if necessary). Write unit tests for stored procedures, and use integration tests that verify data integrity after DML operations. Consider using test data generators to simulate edge cases.
What about DML in cloud data warehouses?
Cloud warehouses like Snowflake and BigQuery support DML, but they have different performance characteristics. Snowflake's DML is optimized for large batches; small, frequent DML operations can be slow due to micro-partitioning. BigQuery charges for DML statements based on the amount of data processed. Design your workflows accordingly—batch where possible, and avoid frequent small updates.
Summary and Next Experiments
Refining DML workflows is an ongoing process of observing, measuring, and adjusting. The key principles are: prefer set-based operations, avoid cursors and triggers unless necessary, use explicit transactions with appropriate isolation levels, and treat DML code with the same rigor as application code.
For your next experiment, try the following: pick one frequently used DML operation in your system—perhaps an UPDATE that joins multiple tables—and rewrite it using a CTE or temp table pattern. Measure the execution time before and after. Then, examine your slowest query logs and identify any implicit conversions or missing indexes. Fix them and observe the improvement. Finally, review your stored procedures for any that have drifted from the current schema and refactor them with explicit column lists.
These small, targeted experiments will build your team's intuition for what works and what doesn't. Over time, you'll develop a refined set of DML patterns that are both efficient and maintainable, ready to scale with your data.
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!