Spanner에서 DML(Data Manipulation Language)과 변형은 데이터를 수정하는 데 사용할 수 있는 두 가지 API입니다. 각각 비슷한 데이터 조작 기능을 제공합니다. 이 페이지에서는 두 접근 방식을 비교합니다.
DML(Data Manipulation Language)이란 무엇인가요?
Spanner의 DML(Data Manipulation Language)을 사용하면 INSERT, UPDATE, DELETE 문을 사용하여 데이터베이스 테이블의 데이터를 조작할 수 있습니다. 클라이언트 라이브러리, Google Cloud 콘솔, gcloud spanner를 사용하여 DML 문을 실행할 수 있습니다.
원자적 트랜잭션이 필요하지 않은 읽기 작업 없이 다수의 쓰기 작업에 일괄 쓰기를 사용할 수 있습니다. 자세한 내용은 일괄 쓰기를 사용하여 데이터 수정을 참조하세요.
변형이란 무엇인가요?
변형이란 Spanner가 데이터베이스의 여러 행과 테이블에 원자적으로 적용하는 일련의 삽입, 업데이트, 삭제입니다.
여러 행 또는 다른 테이블에 적용되는 작업을 변형에 포함할 수 있습니다. 하나 이상의 쓰기가 포함된 변형을 하나 이상 정의한 후에는 변형을 적용하여 쓰기를 커밋해야 합니다. 각 변경사항은 변형에 추가된 순서에 따라 적용됩니다.
다음 표에는 일반적인 데이터베이스 작업 및 기능에 대한 DML 및 변형 지원이 요약되어 있습니다.
작업
DML
변형
데이터 삽입
지원됨
지원됨
데이터 삭제
지원됨
지원됨
데이터 업데이트
지원됨
지원됨
데이터 삽입 또는 무시
지원됨
지원되지 않음
작성한 항목 읽기(RYW)
지원됨
지원되지 않음
데이터 삽입 또는 업데이트(Upsert)
지원됨
지원됨
SQL 구문
지원됨
지원되지 않음
제약조건 확인
모든 문 뒤
커밋 시
DML 및 변형은 다음 기능에 대한 지원에서 차이가 있습니다.
작성한 항목 읽기: 활성 트랜잭션 내에서 커밋되지 않은 결과를 읽습니다. DML 문을 사용하여 변경한 내용은 동일 트랜잭션의 후속 문에서 볼 수 있습니다. 이는 트랜잭션이 커밋될 때까지 같은 트랜잭션에서 수행된 읽기를 포함하여 변경사항이 모든 읽기에 표시되지 않는 변형을 사용하는 것과 다릅니다.
트랜잭션의 변형이 클라이언트 측의 로컬에서 버퍼링되어 커밋 작업의 일부로 서버에 전송되기 때문입니다. 그 결과 커밋 요청의 변형은 동일 트랜잭션 내의 SQL 또는 DML 문에 보이지 않습니다.
제약조건 확인 - Spanner는 모든 DML 문 뒤에 제약조건을 검사합니다. 이 검사는 Spanner가 커밋 때까지 클라이언트에서 변형을 버퍼링하고 커밋 시점에 제약조건을 검사하는 변형을 사용하는 것과 다릅니다. 각 DML 문 다음에 제약조건을 평가하면 Spanner는 동일한 트랜잭션에서 후속 쿼리가 반환하는 데이터가 스키마와 일치하는 데이터를 반환하도록 보장할 수 있습니다.
SQL 문법 - DML은 데이터를 조작하는 일반적인 방법을 제공합니다. SQL 기술을 재사용하여 DML API를 통해 데이터를 변경할 수 있습니다.
권장사항 - DML과 변형을 같은 트랜잭션에서 혼합하지 마세요.
트랜잭션이 커밋 요청에 DML 문과 변형을 모두 포함하는 경우, Spanner는 변형 전에 DML 문을 실행합니다. 클라이언트 라이브러리 코드에서 실행 순서를 고려하지 않으려면 DML 문이나 변형을 단일 트랜잭션에서 사용해야 하며 둘 다 사용하면 안 됩니다.
다음 자바 예시는 예상할 수 있는 동작을 보여줍니다. 이 코드는 Mutation API를 사용하여 2개 행을 Albums에 삽입합니다. 그런 다음 스니펫은 executeUpdate()를 호출하여 새로 삽입된 행을 업데이트하고 executeQuery()를 호출하여 업데이트된 앨범을 읽습니다.
staticvoidupdateMarketingBudget(DatabaseClientdbClient){dbClient.readWriteTransaction().run(newTransactionCallable<Void>(){@OverridepublicVoidrun(TransactionContexttransaction)throwsException{transaction.buffer(Mutation.newInsertBuilder("Albums").set("SingerId").to(1).set("AlbumId").to(1).set("AlbumTitle").to("Total Junk").set("MarketingBudget").to(800).build());transaction.buffer(Mutation.newInsertBuilder("Albums").set("SingerId").to(1).set("AlbumId").to(2).set("AlbumTitle").to("Go Go Go").set("MarketingBudget").to(200).build());// This UPDATE will not include the Albums inserted above.Stringsql="UPDATE Albums SET MarketingBudget = MarketingBudget * 2"+" WHERE SingerId = 1";longrowCount=transaction.executeUpdate(Statement.of(sql));System.out.printf("%d records updated.\n",rowCount);// Read a newly updated record.sql="SELECT SingerId, AlbumId, AlbumTitle FROM Albums"+" WHERE SingerId = 1 AND MarketingBudget < 1000";ResultSetresultSet=transaction.executeQuery(Statement.of(sql));while(resultSet.next()){System.out.printf("%s %s\n",resultSet.getString("FirstName"),resultSet.getString("LastName"));}returnnull;}});}
이 코드를 실행하면 0개의 레코드가 업데이트됨이 표시됩니다. 왜일까요? 그 이유는 Mutation을 사용하여 변경한 사항이 트랜잭션이 커밋될 때까지 후속 문에 표시되지 않기 때문입니다. 트랜잭션의 맨 끝에만 쓰기 버퍼링이 있는 것이 이상적입니다.
[[["이해하기 쉬움","easyToUnderstand","thumb-up"],["문제가 해결됨","solvedMyProblem","thumb-up"],["기타","otherUp","thumb-up"]],[["이해하기 어려움","hardToUnderstand","thumb-down"],["잘못된 정보 또는 샘플 코드","incorrectInformationOrSampleCode","thumb-down"],["필요한 정보/샘플이 없음","missingTheInformationSamplesINeed","thumb-down"],["번역 문제","translationIssue","thumb-down"],["기타","otherDown","thumb-down"]],["최종 업데이트: 2025-09-05(UTC)"],[],[],null,["# Compare DML and Mutations\n\nData Manipulation\nLanguage (DML) and Mutations are two APIs in Spanner that you can use\nto modify data. Each offer similar data manipulation features. This page\ncompares both approaches.\n\nWhat is Data Manipulation Language (DML)?\n-----------------------------------------\n\nThe Data Manipulation Language (DML) in Spanner lets you\nmanipulate data in your database tables using `INSERT`, `UPDATE`, and `DELETE`\nstatements. You can run DML statements using the\n[client libraries](/spanner/docs/reference/libraries), the\n[Google Cloud console](/spanner/docs/create-query-database-console#run_a_query), and [gcloud spanner](/sdk/gcloud/reference/spanner#execute_sql_statements).\n\nSpanner offers the following two implementations of DML execution, each with different\nproperties.\n\n- **Standard DML** - suitable for standard [Online Transaction Processing (OLTP)](https://en.wikipedia.org/wiki/Online_transaction_processing)\n workloads.\n\n For more information, including code samples, see [Using DML](/spanner/docs/dml-tasks#using-dml)\n- **Partitioned DML** - designed for bulk updates and deletes as in the following\n examples.\n\n - Periodic cleanup and garbage collection. Examples are deleting old rows or\n setting columns to NULL.\n\n - Backfilling new columns with default values. An example is using an UPDATE\n statement to set a new column's value to False where it is NULL.\n\n For more information, including code samples, see [Using Partitioned DML](/spanner/docs/dml-tasks#partitioned-dml).\n\n You can use batch writes for a large number of write operations without read\n operations that don't require atomic transactions. For more information,\n see [Modify data using batch writes](/spanner/docs/batch-write).\n\nWhat are mutations?\n-------------------\n\nA mutation represents a sequence of inserts, updates, and deletes that Spanner applies atomically to different rows and tables in a database.\nYou can include operations that apply to different rows, or different tables, in\na mutation. After you define one or more mutations that contain one or more\nwrites, you must apply the mutation to commit the write(s). Each change is\napplied in the order in which they were added to the mutation.\n\nFor more information, including code samples, see\n[Inserting, updating, and deleting data using mutations](/spanner/docs/modify-mutation-api).\n\nFeature comparison between DML and mutations\n--------------------------------------------\n\nThe following table summarizes DML and mutation support of common database\noperation and features.\n\nDML and mutations diverge in their support for the following features:\n\n- **Read Your Writes**: Reading uncommitted results within an active\n transaction. Changes you make using DML statements are visible to\n subsequent statements in the same transaction. This is\n different from using mutations, where changes are not visible in any reads\n (including reads done in the same transaction) until the transaction commits.\n This is because mutations in a transaction are buffered client-side (locally)\n and sent to the server as part of the commit operation. As a result, mutations\n in the commit request are not visible to SQL or DML statements within the same\n transaction.\n\n- **Constraint Checking**: Spanner checks constraints after every\n DML statement. This is different from using mutations, where Spanner\n buffers mutations in the client until commit and checks constraints at commit\n time. Evaluating constraints after each DML statement allows Spanner\n to guarantee that the data returned by a subsequent query in the same\n transaction returns data that is consistent with the schema.\n\n- **SQL Syntax**: DML provides a conventional way to manipulate data. You can\n reuse SQL skills to alter the data using the DML API.\n\nBest practice - avoid mixing DML and mutation in the same transaction\n---------------------------------------------------------------------\n\nIf a transaction contains both DML statements and mutations in the commit\nrequest, Spanner executes the DML statements before the mutations. To avoid\nhaving to account for the order of execution in your client library code, you\nshould use either DML statements or the mutations in a single transaction, but\nnot both.\n\nThe following Java example illustrates potentially surprising behavior. The code\ninserts two rows into Albums using the Mutation API. The snippet, then calls\n`executeUpdate()` to update the newly inserted rows and calls `executeQuery()`\nto read updated albums. \n\n static void updateMarketingBudget(DatabaseClient dbClient) {\n dbClient\n .readWriteTransaction()\n .run(\n new TransactionCallable\u003cVoid\u003e() {\n @Override\n public Void run(TransactionContext transaction) throws Exception {\n transaction.buffer(\n Mutation.newInsertBuilder(\"Albums\")\n .set(\"SingerId\")\n .to(1)\n .set(\"AlbumId\")\n .to(1)\n .set(\"AlbumTitle\")\n .to(\"Total Junk\")\n .set(\"MarketingBudget\")\n .to(800)\n .build());\n transaction.buffer(\n Mutation.newInsertBuilder(\"Albums\")\n .set(\"SingerId\")\n .to(1)\n .set(\"AlbumId\")\n .to(2)\n .set(\"AlbumTitle\")\n .to(\"Go Go Go\")\n .set(\"MarketingBudget\")\n .to(200)\n .build());\n\n // This UPDATE will not include the Albums inserted above.\n String sql =\n \"UPDATE Albums SET MarketingBudget = MarketingBudget * 2\"\n + \" WHERE SingerId = 1\";\n long rowCount = transaction.executeUpdate(Statement.of(sql));\n System.out.printf(\"%d records updated.\\n\", rowCount);\n\n // Read a newly updated record.\n sql =\n \"SELECT SingerId, AlbumId, AlbumTitle FROM Albums\"\n + \" WHERE SingerId = 1 AND MarketingBudget \u003c 1000\";\n ResultSet resultSet =\n transaction.executeQuery(Statement.of(sql));\n while (resultSet.next()) {\n System.out.printf(\n \"%s %s\\n\",\n resultSet.getString(\"FirstName\"),\n resultSet.getString(\"LastName\"));\n }\n return null;\n }\n });\n }\n\nIf you were to execute this code, you'd see *0 records updated*. Why? This\nhappens because the changes we made using Mutations are not visible to\nsubsequent statements until the transaction commits. Ideally, we should have\nbuffered writes only at the very end of the transaction.\n\nWhat's next?\n------------\n\n- Learn how to modify data [Using DML](/spanner/docs/dml-tasks#using-dml).\n\n- Learn how to modify data [Using mutations](/spanner/docs/modify-mutation-api).\n\n- To find the mutation count for a transaction, see\n [Retrieving commit statistics for a transaction](/spanner/docs/commit-statistics).\n\n- Learn about [Data Manipulation Language (DML) best practices](/spanner/docs/dml-best-practices)."]]