
# 只读查询复制冲突
本章节的所有SQL操作都使用root用户执行。
#### 什么是流复制冲突？
PostgreSQL的主库与只读库之间使用流复制进行数据同步，由于主库会不断产生wal日志，当这些wal日志同步到只读库，进行日志应用（Apply）过程中会与只读库的查询（Query）产生冲突。
简单来说，就是只读库查询（Query）与Wal日志应用（Apply）冲突。当只读库的恢复进程无法应用从主库同步过来的wal时，发生流复制冲突。
当发生流复制冲突时，我们会在只读库中看到以下错误日志：
```
ERROR: canceling statement due to conflict with recovery
```
#### 为什么会产生流复制冲突？
当只读库正在执行某个表的查询（可能是业务应用产生，也可能是手动连接执行查询），这时主库执行了**DROP TABLE** 操作，在该操作写入wal日志并同步到只读库后，为了保证数据一致性，只读库会迅速应用**DROP TABLE** 操作，这时**DROP TABLE** 和**SELECT**就会形成冲突。以下几种场景会产生流复制冲突：
1. 只读库中对某个表正在进行查询（ACCESS SHARE），需要应用wal日志中的ACCESS EXCLUSIVE锁的操作。例如：**DROP TABLE** 、**TRUNCATE TABLE** 、大多数**ALTER TABLE**等操作。
2. 主库vacuum清理死元组造成的冲突。若只读在主库执行vacuum之前就启动了一个查询，主库vacuum处理掉了只读库所需要的死元组时，就会产生冲突。
 
#### 发生流复制冲突时怎么办？
**参数控制**
PostgreSQL提供了以下参数用于控制流复制冲突，参数均需要在对应的只读库修改：
- max_standby_streaming_delay 参数说明：参数默认为30秒，表示当只读上正在应用的 WAL 数据与一个正在执行的只读查询发生冲突时，允许 WAL 应用进程（回放）被阻塞的最大累计时间。
  设置为"-1"表示遇到冲突时，直到只读库完成查询后，再继续进行wal回放。注意，"max_standby_streaming_delay"与取消之前一个查询能够运行的最长时间不同，它表示在从主库接收到 wal数据并立刻应用它能够被允许的最长总时间。因此，如果一个查询导致了明显的延迟，后续冲突查询只有更少的时间，直到只读库再次赶上进度。
  缺点：该参数设置时间过长，或设置为"-1"时，若只读存在长事务，则会导致主库与只读库之间存在一定的数据时延。
  

- hot_standby_feedback 参数说明：设置为"on"后只读库执行查询时会通知主库，在只读库执行查询过程中，主库不会清理只读库需要的数据，因此也不会发生因vacuum导致的流复制冲突。
  缺点：设置为"on"时，可以解决因vacuum导致的流复制冲突，**DROP**等操作导致的冲突依然存在。同时若只读中存在长事务，会导致数据库中死元组不能及时清理，造成数据库膨胀。另外，当主库事务ID年龄达到autovacuum_freeze_max_age时，会触发防回卷强制vacuum清理。这类清理会忽略备库反馈，仍可能移除备库查询所需要的旧行版本，进而引发快照冲突。
  
**优化建议**
1. 优化只读实例的SQL，将SQL查询时长控制在"max_standby_streaming_delay"值以内。
2. 监控只读长事务，根据业务需求，超过一定时长（如30min）的长事务要强制终止。
 
