使用pg包更新Postgres数据后查询返回旧值(仅Heroku环境异常)
问题:Postgres更新后立即查询返回旧数据(仅Heroku云环境偶发)
环境与问题现象
- 技术栈:Angular + Node.js(pg npm包@8.7.1),微服务架构,每个服务节点均通过pg连接Postgres数据库
- 问题:执行
update查询后立即调用getList查询,返回旧数据而非更新后的数据;添加5秒setTimeout后查询结果正常 - 特殊情况:本地开发环境完全正常,仅在Heroku云Postgres环境中偶发(有时能获取更新后数据,有时不能)
相关代码
客户端(Angular)代码
async filter({ value }) { const list: any = await this.getList() const [myData]: any = await this.updateData(this.value) const list: any = await this.getList() // 问题出在这里!! } // API调用方法 getList(): Promise<any> { return this.http.get<any>(`${ENV.BASE_API}/doGetApiCalls`).toPromise(); } updateData(value: any): Promise<any> { return this.http.put<any>(`${ENV.BASE_API}/doUpdateApiCalls`, value).toPromise(); }
服务端业务逻辑层
async function updateData(description, id) { let query = updateDataQuery(description, id); let results = await postgressQuery(query); return getDataResults; // 注:此处疑似笔误,应为results? }
服务端数据访问层
function updateDataQuery(description: string, id:number) { const query = `UPDATE public.books SET description='${description}', WHERE book =${id} RETURNING *` return query; }
数据库连接代码
const DATABASE_URL = process.env.DATABASE_URL; const pool = new Pool({ connectionString:DATABASE_URL, ssl:{rejectUnauthorized: false} }) let openConnect = async () => { await pool.connect(); } let postgressQuery = async (q) => { try { const result = await pool.query(q); return await result.rows; } catch (e) { console.log(e); } }
临时解决方案(添加延时)
async filter({ value }) { const list: any = await this.getList() const [myData]: any = await this.updateData(this.value) // 服务端返回正确的更新后数据 await new Promise(resolve => setTimeout(resolve, 5000)) // 添加5秒等待 const list: any = await this.getList() // 此时查询结果正常 }
问题分析与解决建议
1. SQL语法错误与注入风险
你的update查询存在两个明显问题:
- SET语句后多了一个冗余逗号(
SET description='${description}',),会触发SQL语法错误,虽然你提到服务端返回正确数据,大概率是代码笔误,但必须修正 - 使用字符串拼接生成SQL,存在严重的SQL注入风险,同时可能因为特殊字符导致查询异常
修正方案:改用参数化查询
// 数据访问层修改 function updateDataQuery(description: string, id:number) { const query = `UPDATE public.books SET description=$1 WHERE book=$2 RETURNING *`; return { text: query, values: [description, id] }; } // 数据库查询方法修改 let postgressQuery = async (queryObj) => { try { const result = await pool.query(queryObj.text, queryObj.values); return result.rows; } catch (e) { console.log(e); throw e; // 抛出错误让上层处理,避免静默失败 } }
2. Heroku Postgres主从同步延迟
Heroku Postgres默认可能配置了只读副本,getList查询可能被路由到副本节点,而主库的更新同步到副本需要时间,这是偶发旧数据的核心原因(本地是单实例数据库,没有同步延迟问题)。
解决措施:
- 强制查询路由到主库:在Heroku控制台调整Postgres的连接设置,或在
getList的数据库查询中指定主库连接(部分云数据库支持通过连接参数指定) - 前端复用更新结果:更新操作完成后,服务端返回最新数据,前端直接用该数据刷新列表,避免二次查询,彻底绕过同步延迟问题
3. 微服务节点的本地缓存
如果服务端对getList的结果做了内存缓存,更新操作仅在单个节点执行,其他节点的缓存未失效,会导致请求打到缓存未更新的节点时返回旧数据。
解决措施:
- 更新操作完成后,通过消息队列广播缓存失效事件,触发所有服务节点清理对应缓存
- 移除不必要的本地缓存,或调整缓存过期时间为较短值
4. 事务提交确认
虽然pg的pool.query默认自动提交事务,但在云环境中可能存在网络波动导致提交延迟,可显式使用事务包裹更新操作,确保提交完成后再返回结果:
async function updateData(description, id) { const client = await pool.connect(); try { await client.query('BEGIN'); const queryObj = updateDataQuery(description, id); const results = await client.query(queryObj.text, queryObj.values); await client.query('COMMIT'); return results.rows; } catch (e) { await client.query('ROLLBACK'); throw e; } finally { client.release(); } }
内容的提问来源于stack exchange,提问作者itsik goldberg
相关产品推荐
相关产品推荐

