Spanner数据更新 #
一、更新方式概述 #
1.1 两种更新方式 #
text
Spanner数据更新方式
├── DML语句 (UPDATE)
│ ├── 标准SQL语法
│ ├── 支持条件更新
│ ├── 实时执行
│ └── 返回影响行数
│
└── Mutation API
├── 编程接口
├── 批量操作
├── 需要先读取
└── 适合大量数据
1.2 方式对比 #
| 特性 | DML | Mutation |
|---|---|---|
| 条件更新 | 支持 | 不支持 |
| 返回值 | 影响行数 | 无 |
| 批量更新 | 较慢 | 快速 |
| 需要读取 | 不需要 | 需要 |
| 部分列更新 | 支持 | 支持 |
二、UPDATE语句 #
2.1 基本语法 #
sql
UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;
2.2 示例表结构 #
sql
CREATE TABLE users (
user_id INT64 NOT NULL,
name STRING(100) NOT NULL,
email STRING(255),
age INT64,
status STRING(20) DEFAULT 'active',
updated_at TIMESTAMP
) PRIMARY KEY (user_id);
2.3 基本更新 #
sql
-- 更新单个字段
UPDATE users SET name = 'John Smith' WHERE user_id = 1;
-- 更新多个字段
UPDATE users
SET name = 'John Smith',
email = 'john.smith@example.com',
age = 31
WHERE user_id = 1;
-- 使用表达式更新
UPDATE users
SET age = age + 1
WHERE user_id = 1;
-- 使用函数更新
UPDATE users
SET updated_at = CURRENT_TIMESTAMP()
WHERE user_id = 1;
2.4 条件更新 #
sql
-- 使用比较条件
UPDATE users SET status = 'inactive' WHERE age > 60;
-- 使用IN条件
UPDATE users SET status = 'premium'
WHERE user_id IN (1, 2, 3, 4, 5);
-- 使用子查询
UPDATE users SET status = 'vip'
WHERE user_id IN (
SELECT user_id FROM orders
GROUP BY user_id
HAVING SUM(total_amount) > 10000
);
-- 使用LIKE条件
UPDATE users SET status = 'verified'
WHERE email LIKE '%@company.com';
2.5 更新所有行 #
sql
-- 更新所有行(谨慎使用)
UPDATE users SET status = 'active' WHERE TRUE;
-- 或
UPDATE users SET status = 'active';
三、Mutation更新 #
3.1 Update Mutation #
java
// Java Update Mutation
import com.google.cloud.spanner.Mutation;
import com.google.cloud.spanner.DatabaseClient;
// 创建Update Mutation(需要指定主键和所有非NULL列)
Mutation mutation = Mutation.newUpdateBuilder("users")
.set("user_id").to(1L)
.set("name").to("John Smith")
.set("email").to("john.smith@example.com")
.set("age").to(31)
.set("status").to("active")
.build();
// 执行更新
DatabaseClient client = spanner.getDatabaseClient(databaseId);
client.write(Collections.singletonList(mutation));
3.2 InsertOrUpdate Mutation #
java
// InsertOrUpdate: 存在则更新,不存在则插入
Mutation mutation = Mutation.newInsertOrUpdateBuilder("users")
.set("user_id").to(1L)
.set("name").to("John Smith")
.set("email").to("john.smith@example.com")
.build();
3.3 Python Mutation #
python
# Python更新
def update_user(database, user_id, name, email):
with database.batch() as batch:
batch.update(
table="users",
columns=("user_id", "name", "email"),
values=[
(user_id, name, email)
]
)
3.4 Go Mutation #
go
// Go更新
func updateUser(ctx context.Context, client *spanner.Client, userID int64, name, email string) error {
m := spanner.Update("users",
[]string{"user_id", "name", "email"},
[]interface{}{userID, name, email},
)
_, err := client.Apply(ctx, []*spanner.Mutation{m})
return err
}
四、批量更新 #
4.1 DML批量更新 #
sql
-- 使用IN条件批量更新
UPDATE users SET status = 'inactive'
WHERE user_id IN (1, 2, 3, 4, 5);
-- 使用子查询批量更新
UPDATE users SET status = 'premium'
WHERE user_id IN (
SELECT user_id FROM premium_users
);
4.2 Mutation批量更新 #
java
// Java批量更新
List<Mutation> mutations = new ArrayList<>();
for (long userId : userIds) {
Mutation mutation = Mutation.newUpdateBuilder("users")
.set("user_id").to(userId)
.set("status").to("inactive")
.build();
mutations.add(mutation);
}
DatabaseClient client = spanner.getDatabaseClient(databaseId);
client.write(mutations);
python
# Python批量更新
def batch_update_status(database, user_ids, status):
values = [(uid, status) for uid in user_ids]
with database.batch() as batch:
batch.update(
table="users",
columns=("user_id", "status"),
values=values
)
4.3 批量更新限制 #
text
批量更新限制:
├── 单次Mutation数量: 无限制
├── 单次数据大小: 100MB
├── 单行大小: 10GB
└── 建议: 每批1000-10000行
五、事务中的更新 #
5.1 读写事务 #
java
// Java读写事务
DatabaseClient client = spanner.getDatabaseClient(databaseId);
TransactionRunner runner = client.readWriteTransaction();
runner.run(transaction -> {
// 先读取当前值
Struct row = transaction.readRow("users",
Key.of(1L), Arrays.asList("age"));
long currentAge = row.getLong(0);
// 更新
transaction.buffer(Mutation.newUpdateBuilder("users")
.set("user_id").to(1L)
.set("age").to(currentAge + 1)
.build());
return null;
});
5.2 条件更新事务 #
java
// 条件更新: 只有满足条件才更新
TransactionRunner runner = client.readWriteTransaction();
runner.run(transaction -> {
// 读取并检查条件
Struct row = transaction.readRow("users",
Key.of(1L), Arrays.asList("status", "age"));
String status = row.getString(0);
long age = row.getLong(1);
// 只有active用户才能更新年龄
if ("active".equals(status)) {
transaction.buffer(Mutation.newUpdateBuilder("users")
.set("user_id").to(1L)
.set("age").to(age + 1)
.build());
}
return null;
});
5.3 Python事务 #
python
# Python事务更新
def update_user_age(database, user_id):
def transaction_func(transaction):
# 读取当前年龄
row = list(transaction.read(
"users", ["age"],
spanner.KeySet([spanner.Key([user_id])])
))[0]
current_age = row[0]
# 更新年龄
transaction.update(
table="users",
columns=("user_id", "age"),
values=[(user_id, current_age + 1)]
)
database.run_in_transaction(transaction_func)
六、部分列更新 #
6.1 DML部分更新 #
sql
-- 只更新指定列
UPDATE users SET email = 'new@example.com' WHERE user_id = 1;
-- 其他列保持不变
6.2 Mutation部分更新 #
java
// Mutation更新时,未指定的列会被设置为NULL
// 如果需要保持原值,必须先读取
// 方式1: 先读取再更新
Struct row = client.singleUse().readRow("users",
Key.of(1L), Arrays.asList("name", "email", "age"));
Mutation mutation = Mutation.newUpdateBuilder("users")
.set("user_id").to(1L)
.set("name").to(row.getString(0)) // 保持原值
.set("email").to("new@example.com") // 更新
.set("age").to(row.getLong(2)) // 保持原值
.build();
七、更新性能优化 #
7.1 批量更新 #
text
批量更新建议:
├── 使用Mutation批量更新
├── 合理设置批量大小
├── 避免单行更新循环
└── 使用并发更新
7.2 索引利用 #
sql
-- 创建索引加速更新条件的查询
CREATE INDEX idx_users_status ON users(status);
-- 更新时利用索引
UPDATE users SET age = age + 1
WHERE status = 'active'; -- 使用索引
7.3 避免全表扫描 #
sql
-- 不推荐: 全表扫描
UPDATE users SET status = 'inactive' WHERE TRUE;
-- 推荐: 使用条件限制范围
UPDATE users SET status = 'inactive'
WHERE last_login < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 YEAR);
八、更新冲突处理 #
8.1 乐观锁 #
sql
-- 使用版本号实现乐观锁
CREATE TABLE products (
product_id INT64 NOT NULL,
name STRING(100),
stock INT64,
version INT64 DEFAULT 0
) PRIMARY KEY (product_id);
-- 更新时检查版本
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE product_id = 1 AND version = 5;
-- 如果影响行数为0,说明版本不匹配
8.2 重试策略 #
java
// Java重试策略
int maxRetries = 3;
for (int i = 0; i < maxRetries; i++) {
try {
runner.run(transaction -> {
// 读取
Struct row = transaction.readRow("products",
Key.of(1L), Arrays.asList("stock", "version"));
long stock = row.getLong(0);
long version = row.getLong(1);
// 更新
transaction.buffer(Mutation.newUpdateBuilder("products")
.set("product_id").to(1L)
.set("stock").to(stock - 1)
.set("version").to(version + 1)
.build());
return null;
});
break; // 成功则退出
} catch (AbortedException e) {
if (i == maxRetries - 1) {
throw e;
}
// 等待后重试
Thread.sleep(100 * (i + 1));
}
}
九、更新最佳实践 #
9.1 选择更新方式 #
text
选择建议:
├── 条件更新: DML
├── 批量更新: Mutation
├── 需要返回值: DML
└── 高吞吐量: Mutation
9.2 性能建议 #
text
性能优化建议:
├── 使用批量更新
├── 减少事务大小
├── 合理设计索引
├── 避免热点主键
└── 使用并发更新
9.3 数据一致性 #
text
数据一致性建议:
├── 使用事务保证原子性
├── 使用乐观锁处理并发
├── 处理更新冲突
└── 设置合理的重试策略
十、总结 #
更新方式对比:
| 方式 | 适用场景 | 性能 |
|---|---|---|
| DML UPDATE | 条件更新,需要返回值 | 一般 |
| Mutation Update | 批量更新,高吞吐量 | 好 |
| InsertOrUpdate | 不确定是否存在 | 好 |
最佳实践:
text
1. 选择合适的更新方式
└── 根据场景选择DML或Mutation
2. 使用批量更新
└── 减少网络往返次数
3. 处理并发冲突
└── 使用乐观锁和重试
4. 监控更新性能
└── 及时发现瓶颈
5. 保证数据一致性
└── 使用事务
下一步,让我们学习数据删除操作!
最后更新:2026-03-27