이 페이지에서는 GoogleSQL 언어 데이터베이스 및 PostgreSQL 언어 데이터베이스에 DML 및 Partitioned DML을 사용하는 권장사항을 설명합니다.
WHERE 절을 사용하여 잠금 범위 축소
읽기-쓰기 트랜잭션 내에서 DML 문을 실행합니다. Spanner에서 데이터를 읽을 때 읽은 행 범위의 제한된 부분에서 공유 읽기 잠금을 획득합니다. 구체적으로는 액세스하는 열에만 이러한 잠금을 획득합니다. 잠금에는 WHERE 절의 필터 조건을 충족하지 않는 데이터가 포함될 수 있습니다.
Spanner에서 DML 문을 사용하여 데이터를 수정하면 수정 중인 특정 데이터에서 배타적인 잠금을 획득합니다. 또한 데이터를 읽을 때와 같은 방법으로 공유 잠금을 획득합니다. 요청에 큰 행 범위나 전체 테이블이 포함된 경우 공유 잠금으로 인해 다른 트랜잭션을 동시에 진행하지 못할 수 있습니다.
데이터를 최대한 효율적으로 수정하려면 Spanner에서 필요한 행만 읽을 수 있도록 하는 WHERE 절을 사용합니다. 이러한 경우 기본 키 또는 보조 색인 키의 필터를 사용하면 됩니다. WHERE 절은 공유 잠금 범위를 제한하고 Spanner에서 업데이트를 더욱 효율적으로 처리할 수 있게 합니다.
예를 들어, Singers 테이블의 뮤지션 중 한 명이 이름을 변경하는 경우 데이터베이스에서 해당 이름을 업데이트해야 합니다. 다음 DML 문을 실행할 수 있지만 이 문은 Spanner가 전체 테이블을 강제로 스캔하도록 하며 전체 테이블을 포함하는 공유 잠금을 획득합니다. 그 결과 Spanner에서 데이터를 필요 이상으로 읽어야 하며 동시 트랜잭션은 데이터를 동시에 수정할 수 없습니다.
-- ANTI-PATTERN: SENDING AN UPDATE WITHOUT THE PRIMARY KEY COLUMN-- IN THE WHERE CLAUSEUPDATESingersSETFirstName="Marcel"WHEREFirstName="Marc"ANDLastName="Richards";
업데이트를 더 효율적으로 수행하려면 WHERE 절에 SingerId 열을 포함합니다. SingerId 열은 Singers 테이블의 유일한 기본 키 열입니다.
-- ANTI-PATTERN: SENDING AN UPDATE THAT MUST SCAN THE ENTIRE TABLEUPDATESingersSETFirstName="Marcel"WHEREFirstName="Marc"ANDLastName="Richards"
FirstName 또는 LastName에 색인이 없는 경우 전체 테이블을 스캔하여 대상 가수를 찾아야 합니다. 업데이트를 더 효율적으로 만들기 위해 보조 색인을 추가하지 않으려면 WHERE 절에 SingerId 열을 포함합니다.
SingerId 열은 Singers 테이블의 유일한 기본 키 열입니다. 이를 찾으려면 업데이트 트랜잭션 전에 별도의 읽기 전용 트랜잭션에서 SELECT를 실행합니다.
Spanner는 서버 측에서 DML 문을 사용하여 수행된 삽입, 업데이트, 삭제를 버퍼링하며, 동일 트랜잭션 내 후속 SQL 및 DML 문에서 결과를 확인할 수 있습니다. 이 동작은 Spanner가 클라이언트 측에서 변형을 버퍼링하고 커밋 작업의 일부로 변형을 서버 측에 보내는 변형 API와 다릅니다. 그 결과 커밋 요청의 변형은 동일 트랜잭션 내의 SQL 또는 DML 문에 보이지 않습니다.
같은 트랜잭션에서 DML 문과 변형을 모두 사용하지 마세요. 동일한 트랜잭션에서 둘 다 사용하는 경우 클라이언트 라이브러리 코드에서 실행 순서를 고려해야 합니다. 트랜잭션이 동일한 요청에 DML 문과 변형을 모두 포함하는 경우, Spanner는 변형 전에 DML 문을 실행합니다.
변형을 사용해서만 지원되는 작업의 경우 같은 트랜잭션에서 DML 문과 변형을 결합하는 것이 좋습니다.(예: insert_or_update).
그런 다음 이 문을 반복하여 연속으로 호출하는 Spanner DML 배치를 전송합니다. 반복은 문의 3개 쿼리 매개변수에 바인딩한 값에서만 다릅니다.
Spanner는 구조적으로 동일한 이러한 DML 문을 실행하기 전에 단일 서버 측 작업으로 최적화합니다.
동시에 쓰기 실행
Spanner는 데이터 종속 항목을 위반하지 않는 경우 동시에 실행하여 DML 문의 연속된 그룹을 자동으로 최적화합니다. 이 최적화는 DML 문 유형(INSERT, UPDATE, DELETE)과 파라미터화되거나 파라미터화되지 않은 DML 문 모두에 적용될 수 있으므로 보다 광범위한 일괄 처리 DML 문 집합에 성능 이점을 제공합니다.
예를 들어 샘플 스키마에는 Singers, Albums, Accounts 테이블이 있습니다. Albums은 Singers 내에서 인터리브 처리되며 Singers의 앨범에 관한 정보를 저장합니다. 다음의 연속된 문 그룹은 여러 테이블에 새 행을 작성하며 복잡한 데이터 종속 항목이 없습니다.
[[["이해하기 쉬움","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,["# Data Manipulation Language best practices\n\nThis page describes best practices for using Data Manipulation Language (DML)\nand Partitioned DML for GoogleSQL-dialect databases and PostgreSQL-dialect databases.\n\nUse a `WHERE` clause to reduce the scope of locks\n-------------------------------------------------\n\n\nYou execute DML statements inside read-write transactions. When Spanner reads data, it\nacquires shared read locks on limited portions of the row ranges that you read. Specifically, it\nacquires these locks only on the columns you access. The locks can include data that does not\nsatisfy the filter condition of the `WHERE` clause.\n\n\nWhen Spanner modifies data using DML statements, it acquires exclusive locks on the\nspecific data that you are modifying. In addition, it acquires shared locks in the same way as\nwhen you read data. If your request includes large row ranges, or an entire table, the shared\nlocks might prevent other transactions from making progress in parallel.\n\n\nTo modify data as efficiently as possible, use a `WHERE` clause that enables\nSpanner to read only the necessary rows. You can achieve this goal with a filter on the\nprimary key, or on the key of a secondary index. The `WHERE` clause limits the scope of\nthe shared locks and enables Spanner to process the update more efficiently.\n\n\nFor example, suppose that one of the musicians in the `Singers` table changes their\nfirst name, and you need to update the name in your database. You could execute the following DML\nstatement, but it forces Spanner to scan the entire table and acquires shared locks that\ncover the entire table. As a result, Spanner must read more data than necessary, and\nconcurrent transactions cannot modify the data in parallel: \n\n -- ANTI-PATTERN: SENDING AN UPDATE WITHOUT THE PRIMARY KEY COLUMN\n -- IN THE WHERE CLAUSE\n\n UPDATE Singers SET FirstName = \"Marcel\"\n WHERE FirstName = \"Marc\" AND LastName = \"Richards\";\n\n\nTo make the update more efficient, include the `SingerId` column in the\n`WHERE` clause. The `SingerId` column is the only primary key column for\nthe `Singers` table: \n\n -- ANTI-PATTERN: SENDING AN UPDATE THAT MUST SCAN THE ENTIRE TABLE\n\n UPDATE Singers SET FirstName = \"Marcel\"\n WHERE FirstName = \"Marc\" AND LastName = \"Richards\"\n\n\nIf there is no index on `FirstName` or `LastName`, you need to\nscan the entire table to find the target singers. If you don't want to add a secondary\nindex to make the update more efficient, then include the `SingerId` column\nin the `WHERE` clause.\n\n\nThe `SingerId` column is the only primary key column for the\n`Singers` table. To find it, run `SELECT` in a separate,\nread-only transaction prior to the update transaction: \n\n\n SELECT SingerId\n FROM Singers\n WHERE FirstName = \"Marc\" AND LastName = \"Richards\"\n\n -- Recommended: Including a seekable filter in the where clause\n\n UPDATE Singers SET FirstName = \"Marcel\"\n WHERE SingerId = 1;\n\n\u003cbr /\u003e\n\nAvoid using DML statements and mutations in the same transaction\n----------------------------------------------------------------\n\nSpanner buffers insertions, updates, and deletions performed\nusing DML statements on the server-side, and the results are visible to\nsubsequent SQL and DML statements within the same transaction. This behavior is\ndifferent from the [Mutation API](/spanner/docs/modify-mutation-api), where\nSpanner buffers the mutations on the client-side and sends the\nmutations server-side as part of the commit operation. As a result, mutations in\nthe commit request aren't visible to SQL or DML statements within the same\ntransaction.\n\nAvoid using both DML statements and mutations in the same transaction. If you\nuse both in the same transaction, you need to account for the order of execution\nin your client library code. If a transaction contains both DML statements and\nmutations in the same request, Spanner executes the DML\nstatements before the mutations.\n\nFor operations that are only supported using mutations, you might want to\ncombine DML statements and mutations in the same transaction---for example,\n[`insert_or_update`](/spanner/docs/reference/rpc/google.spanner.v1#google.spanner.v1.Mutation).\n\nIf you use both, the buffer writes only at the very end of the transaction.\n\nUse the `PENDING_COMMIT_TIMESTAMP` function to write commit timestamps\n----------------------------------------------------------------------\n\n### GoogleSQL\n\n\nYou use the [PENDING_COMMIT_TIMESTAMP](/spanner/docs/reference/standard-sql/timestamp_functions#pending_commit_timestamp) function to write the commit\ntimestamp in a DML statement. Spanner selects the commit timestamp when the transaction\ncommits.\n\n| **Note:** After you call the `PENDING_COMMIT_TIMESTAMP` function, the table and any derived index is unreadable to any future SQL statements in the transaction. Because of this, the change stream can't extract the previous value for the column that has a pending commit timestamp, if the coloumn is modified again later in the same transaction. You must write commit timestamps as the last statement in a transaction to prevent the possibility of trying to read the table. If you try to read the table, then Spanner produces an error.\n\n\u003cbr /\u003e\n\n### PostgreSQL\n\n\nYou use the `SPANNER.PENDING_COMMIT_TIMESTAMP()` function to write the commit\ntimestamp in a DML statement. Spanner selects the commit timestamp when the transaction\ncommits.\n\n| **Note:** After you call the `SPANNER.PENDING_COMMIT_TIMESTAMP()` function, the table and any derived index is unreadable to any subsequent SQL statements in the transaction. You must write commit timestamps as the last statement in a transaction to prevent the possibility of trying to read the table. If you try to read the table, then Spanner returns an error.\n\n\u003cbr /\u003e\n\nPartitioned DML and date and timestamp functions\n------------------------------------------------\n\nPartitioned DML uses one or more transactions that might run and commit at\ndifferent times. If you use the [date](/spanner/docs/reference/standard-sql/date_functions) or\n[timestamp](/spanner/docs/reference/standard-sql/timestamp_functions) functions, the modified rows might\ncontain different values.\n\nImprove latency with Batch DML\n------------------------------\n\nTo reduce latency, use [batch DML](/spanner/docs/dml-tasks#use-batch) to send\nmultiple DML statements to Spanner within a single client-server\nround trip.\n\nBatch DML can apply optimizations to groups of statements within a batch to\nenable faster and more efficient data updates.\n\n- **Execute writes with a single request**\n\n Spanner automatically optimizes contiguous groups of similar\n `INSERT`, `UPDATE`, or `DELETE` batched statements that have different\n parameter values, if they don't violate data dependencies.\n\n For example, consider a scenario where you want to insert a large set of new\n rows into a table called `Albums`. To let Spanner optimize\n all the required `INSERT` statements into a single, efficient server-side\n action, begin by writing an appropriate DML statement that uses SQL query\n parameters: \n\n INSERT INTO Albums (SingerId, AlbumId, AlbumTitle) VALUES (@Singer, @Album, @Title);\n\n Then, send Spanner a DML batch that invokes this statement\n repeatedly and contiguously, with the repetitions differing only in the\n values you bind to the statement's three query parameters.\n Spanner optimizes these structurally identical DML\n statements into a single server-side operation before executing it.\n- **Execute writes in parallel**\n\n Spanner automatically optimizes contiguous groups of DML\n statements by executing in parallel when doing so doesn't violate data\n dependencies. This optimization brings performance benefits to a wider set\n of batched DML statements because it can apply to a mix of DML statement\n types (`INSERT`, `UPDATE` and `DELETE`) and to both parameterized or\n non-parameterized DML statements.\n\n For example, our sample schema has the tables `Singers`, `Albums`, and\n `Accounts`. `Albums` is interleaved within `Singers` and stores information\n about albums for `Singers`. The following contiguous group of statements\n writes new rows to multiple tables and doesn't have complex data\n dependencies. \n\n INSERT INTO Singers (SingerId, Name) VALUES(1, \"John Doe\");\n INSERT INTO Singers (SingerId, Name) VALUES(2, \"Marcel Richards\");\n INSERT INTO Albums(SingerId, AlbumId, AlbumTitle) VALUES (1, 10001, \"Album 1\");\n INSERT INTO Albums(SingerId, AlbumId, AlbumTitle) VALUES (1, 10002, \"Album 2\");\n INSERT INTO Albums(SingerId, AlbumId, AlbumTitle) VALUES (2, 10001, \"Album 1\");\n UPDATE Accounts SET Balance = 100 WHERE AccountId = @AccountId;\n\n Spanner optimizes this group of DML statements by executing\n the statements in parallel. The writes are applied in order of the\n statements in the batch and maintains batch DML semantics if a statement\n fails during execution."]]