数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
在数据库实战开发的过程中,我们经常会遇到一些这样那样的问题,然后要卡好半天,等问题解决了才发现原来一些细节知识点还是没有掌握好。今天golang学习网就整理分享《数据库写操作弃用“SELECT ... FOR UPDATE”解决方案》,聊聊MySQL、python,希望可以帮助到正在努力赚钱的你。
问题阐述
Mysql Galera集群是迄今OpenStack服务最流行的Mysql部署方案,它基于Mysql/InnoDB,我的OpenStack部署方式从原来的主从复制转换到Galera的多主模式。
Galera虽然有很多好处,如任何时刻任何节点都可读可写,无复制延迟,同步复制,行级复制,但是Galera存在一个问题,也可以说是在实现 真正的多主可写上的折衷权衡,也就是这个问题导致在代码的数据库层的操作需要弃用写锁,下面我说一下这个问题。
这个问题是Mysql Galera集群不支持跨节点对表加锁,也就是当OpenStack一个组件有两个会话分布在两个Mysql节点上同时写入一条数据,其中一个会话会遇到 死锁的情况,也就是得到deadlock的错误,并且该情况在高并发的时候发生概率很高,在社区Nova,Neutron该情况的报告有很多。
这个行为其实是Galera预期的结果,它是由乐观锁并发控制机制引起的,当发生多个事务进行写操作的时候,乐观锁机制假设所有的修改都能 没有冲突地完成。如果两个事务同时修改同一个数据,先commit的事务会成功,另一个会被拒绝,并重新开始运行整个事务。 在事务发生的起始节点,它可以获取到所有它需要的锁,但是它不知道其他节点的情况,所以它采用乐观锁机制把事务(在Galera中叫writes et)广播到所有其他节点上,看在其他节点上是否能提交成功。这个writeset会在每个节点上进行验证测试,来决定该writeset是否被接受, 如果检验失败,这个writeset就会被抛弃,然后最开始的事务也会被回滚;如果检验成功,事务就被提交,writeset也被应用到其他节点上。 这个过程如下图所示:

在Python的SQLAlchemy库中,有一个“with_lockmode('update')”语句,这个代表SQL语句中的“SELECT ... FOR UPDATE”,在我参与过的计费项目和社区的一些项目的代码中有大量的该结构,由于写锁不能在集群中同步,所以这个语句在Mysql集群中就没有得到它应有的效果,也就是在语义上有问题,但是最后Galera会通过报deadlock错误,只让一个commit成功,来保证Mysql集群的ACID性。
一些解决方法
-
把请求发往一个节点,这个在HAProxy中就可以配置,只设定一个节点为master,其余节点为backup,HAProxy会在master失效的时候 自动切换到某一个backup上,这个也
是很多解决方案目前使用的方法,HAProxy配置如下:server xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx:3306 check server xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx:3306 check backup server xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx:3306 check backup 对OpenStack的所有Mysql操作做读写分离,写操作只在master节点上,读操作在所有节点上做负载均衡。OpenStack没有原生支持,但 是有一个开源软件可以使用,maxscale。
终极解决方法
上面的解决方法只是一些workaround,目前情况下最终极的解决方法是使用lock-free的方法来对数据库进行操作,也就是无锁的方式,这就 需要对代码进行修改,现在Nova,Neutron,Gnocchi等项目已经对其进行了修改。
首先得有一个retry机制,也就是让操作执行在一个循环中,一旦捕获到deadlock的error就将操作重新进行,这个在OpenStack的oslo.db中已 经提供了相应的方法叫wrap_db_retry,是一个Python装饰器,使用方法如下:
from oslo_db import api as oslo_db_api
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True,
retry_on_request=True)
def db_operations():
...
然后在这个循环之中我们使用叫做"Compare And Swap(CAS)"的无锁方法来完成update操作,CAS是最先在CPU中使用的,CAS说白了就是先比较,再修改,在进行UPDATE操作之前,我们先SELEC T出来一些数据,我们叫做期望数据,在UPDATE的时候要去比对这些期望数据,如果期望数据有变化,说明有另一个会话对该行进行了修改, 那么我们就不能继续进行修改操作了,只能报错,然后retry;如果没变化,我们就可以将修改操作执行下去。该行为体现在SQL语句中就是在 UPDATE的时候加上WHERE语句,如"UPDATE ... WHERE ..."。
给出一个计费项目中修改用户等级的DB操作源码:
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True,
retry_on_request=True)
def change_account_level(self, context, user_id, level, project_id=None):
session = get_session()
with session.begin():
# 在会话刚开始的时候,需要先SELECT出来该account的数据,也就是期望数据 account = session.query(sa_models.Account).\
filter_by(user_id=user_id).\
one()]
# 在执行UPDATE操作的时候需要比对期望数据,user_id和level,如果它们变化了,那么rows_update就会被赋值为0 ,就会走入retry的逻辑
params = {'level': level}
rows_update = session.query(sa_models.Account).\
filter_by(user_id=user_id).\
filter_by(level=account.level).\
update(params, synchronize_session='evaluate')
# 修改失败,报出RetryRequest的错误,使上面的装饰器抓获该错误,然后重新运行逻辑 if not rows_update:
LOG.debug('The row was updated in a concurrent transaction, '
'we will fetch another one')
raise db_exc.RetryRequest(exception.AccountLevelUpdateFailed())
return self._row_to_db_account_model(account)
数据的一致性问题
该问题在OpenStack邮件列表中有说过,虽然Galera是生成同步的,也就是写入数据同步到整个集群非常快,用时非常短,但既然是分布式系 统,本质上还是需要一些时间的,尤其是在负载很大的时候,同步不及时会很严重。
所以Galera只是虚拟同步,不是直接同步,也就是会存在一些gap时间段,无法读到写入的数据,Galera提供了一个配置项,叫做wsrep_sync_ wait,它的默认值是0,如果赋值为1,就能够保证读写的一致性,但是会带来延迟问题。
Appendix
本篇关于《数据库写操作弃用“SELECT ... FOR UPDATE”解决方案》的介绍就到此结束啦,但是学无止境,想要了解学习更多关于数据库的相关知识,请关注golang学习网公众号!
关于 Overtrue 的拼音库 overtrue/pinyin 为何 travis 为 error
- 上一篇
- 关于 Overtrue 的拼音库 overtrue/pinyin 为何 travis 为 error
- 下一篇
- 好项目 - 收藏集 - 掘金
-
- 数据库 · MySQL | 7小时前 | MySQL · 执行计划 · 慢查询 · EXPLAIN ANALYZE · 性能诊断 · MySQL EXPLAIN ANALYZE MySQL估算行数实际行数 MySQL执行计划耗时 MySQL慢查询诊断 MySQL TREE执行计划
- MySQL EXPLAIN ANALYZE对比估算行数与实际耗时的实现方法
- 262浏览 收藏
-
- 数据库 · MySQL | 8小时前 |
- MySQL CHECK 约束上线前清理不符合约束的旧数据的实现方法
- 475浏览 收藏
-
- 数据库 · MySQL | 9小时前 |
- MySQL 在线 DDL评估加索引时的锁与空间的实现方法
- 202浏览 收藏
-
- 数据库 · MySQL | 10小时前 | MySQL · SQL查询 · 窗口函数 · ROW_NUMBER · 数据分组 · ROW_NUMBER PARTITION BY MySQL 窗口函数 每组最新记录 分组取最新
- MySQL 窗口函数按分组取每组最新记录的实现方法
- 185浏览 收藏
-
- 数据库 · MySQL | 14小时前 | MySQL · 数据库性能 · mysql 临时表 Performance Schema temptable_max_ram tmp_table_size
- MySQL temptable_max_ram 观察临时表内存阈值如何设置
- 409浏览 收藏
-
- 数据库 · MySQL | 15小时前 | MySQL · 索引优化 · mysql Invisible Index 索引可见性
- MySQL invisible index 试验结束后如何恢复可见
- 118浏览 收藏
-
- 数据库 · MySQL | 17小时前 |
- MySQL 角色继承权限后 SHOW GRANTS 如何解读
- 101浏览 收藏
-
- 数据库 · MySQL | 18小时前 |
- MySQL 复制状态中 Retrieved_Gtid_Set 如何辅助定位缺口
- 221浏览 收藏
-
- 数据库 · MySQL | 19小时前 | MySQL · 性能分析 · Performance Schema · SQL耗时 · mysql Performance Schema events_statements 平均耗时
- MySQL Performance Schema events_statements 如何找平均耗时
- 125浏览 收藏
-
- 数据库 · MySQL | 22小时前 |
- MySQL LOAD DATA 导入 TSV 时如何处理字段内制表符
- 253浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 46次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 140次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 78次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 47次使用
-
- CMMLU
- 深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
- 29次使用
-
- MySQL 明明加了索引,为什么查询还是很慢?先查这 6 个点
- 2026-06-27 374浏览
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- golang 基于 mysql 简单实现分布式读写锁
- 2023-01-07 384浏览
-
- 详解如何利用GORM实现MySQL事务
- 2023-01-07 184浏览
-
- Go语言实现操作MySQL的基础知识总结
- 2023-01-23 265浏览

