| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 大数据 -> 数据库优化-事务篇 -> 正文阅读 |
|
[大数据]数据库优化-事务篇 |
数据库优化-事务篇说明背景: 日常开发中, 性能的瓶颈往往在于 IO, 尤其是数据库. 了解如何优化数据库, 提高数据库的性能和请求响应速度是有必要的.因此, 我打算梳理和总结数据库优化的相关内容. 本篇为 准备数据:
为什么要有数据库事务数据库事务是数据库中极为重要的一部分. 一个经典的例子就是银行转账. 由于关于事务的讲解在网上已有很多较为优秀的文章了, 本文就不再赘述. 可以参考: 注意: 无论那种事务的隔离级别, 更新的时候都会取最新的已提交的数据. 即为 数据竞争在多个数据库 session 并发执行 sql 时, 会遇到数据竞争 data race. 数据竞争: 多个数据库事务尝试在同一时间更新同一行的数据. 为了保持数据的可靠性, 在更新同一条数据时, 需要获取写锁. 而写锁会阻止其它 session 对数据的修改. 想修改这条数据的其它 session 必须等待. 数据竞争是无法避免的, 只能尽可能地降低其对性能的影响. 下图是一个非常简单的例子. 如何降低数据竞争对性能的影响常见的数据库(例如: Oracle,MySQL,Postgresql) 采用 锁机制数据库的锁机制, 比较复杂, 属于数据库内部的实现, 我们业务开发人员一般不会去主动申请锁的. 以 PG 数据库为例, 锁大致的分类如下: 不同颗粒度的锁
不同种形式的锁
具体的锁, 见 PG 文档: explicit-locking 一条 sql 语句, 可以获取多个锁, 例如
多版本并发控制 (Multiversion concurrency control 简称 MVCC)使用 MVVC 最大的优势是: 读从不阻止写, 写从不阻止读.
详情可以见: 注释: MySQL Oracle 是依靠 undo log 实现 MVCC 的, 而 PG 是直接插入数据到数据库中, 然后根据 tuple 中的 xmax,xmin 来实现的. 不同数据库实现 MVCC 的方式不太相同. 事务相关性能优化MVCC 和锁机制 属于数据库本身的优化, 那么在我们的应用代码, 可以使用什么方式进行性能优化呢? 减少长事务减少长事务, 避免事务持续太长时间(超过 8s 就可以认为是很长的事务了). 长事务带来的不利影响:
可以通过下面的方式, 尽量避免:
例如:
可以优化为:
如果事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。 假设要实现一个电影票在线交易业务,顾客 A 要在影院 B 购买电影票。我们简化一点,这个业务需要涉及到以下操作:
那么, 最为合理的顺序是 原因是: 新增记录是不会锁住任何行的, 因此 3 放到最开始是合适的. 顾客 A 的余额大概率在同一时间不会和其它事务冲突. PG 减少使用子事务具体原因: PostgreSQL Subtransactions Considered Harmful 悲观锁与乐观锁本节部分内容, 摘抄自文章: 悲观锁与乐观锁的实现(详情图解)
悲观锁(Pessimistic Lock): 就是很悲观,每次去拿数据的时候都认为别人会修改。所以每次在拿数据的时候都会上锁。这样别人想拿数据就被挡住,直到悲观锁被释放,悲观锁中的共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程 但是在效率方面,处理加锁的机制会产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,如果已经锁定了一个线程 A,其他线程就必须等待该线程 A 处理完才可以处理 数据库中的行锁,表锁,读锁(共享锁),写锁(排他锁),以及 Java 中的 synchronized 锁均为悲观锁 乐观锁(Optimistic Lock): 就是很乐观,每次去拿数据的时候都认为别人不会修改。所以不会上锁,但是如果想要更新数据,则会在更新前检查在读取至更新这段时间别人有没有修改过这个数据。 如果修改过,则重新读取,再次尝试更新,循环上述步骤直到更新成功(当然也允许更新失败的线程放弃操作),乐观锁适用于多读的应用类型,这样可以提高吞吐量 一般的实现乐观锁的方式就是记录数据版本(version)或者是时间戳来实现,不过使用版本记录是最常用的。 我们看看实际的例子 现在有多个数据库 session, 想修改 iPhone13 的 库存 num, 而库存 num 是不能小于 0 的. 场景: Session A:查询到 num=200,做了库存减量成了 0 对于这种场景,怎么避免减成负值? 首先最为简单有效的是使用悲观锁:
悲观锁固然能够解决并发带来的数据竞争, 但是会较为严重地影响并发量. 根据实际情况, 用户大部分情况会看库存, 而不会真的去买, 从而修改库存, 我们使用乐观锁会更合适一些.
如果更新的行数为0, 则表示没有更新成果, 应用段代码可以重试. 库存的场景, 可以再精简一些, 只要满足有足够的商品给客户即可, 对版本的要求就可以放低.
update 语句的 affected_rows,如果等于 1 那就是符合预期;如果等于 0,那表示库存不够减了,业务要处理一下去,比如提示“库存不足” 死锁和死锁检测本节大量摘抄自: MySQL 实战 45 讲 07 | 行锁功过:怎么减少行锁对性能的影响? 当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。 一旦形成死锁, 会造成事务堆积, 数据库很容易就崩了. 为了消除死锁的影响, 数据库一般会提供死锁探测, 如果发现有死锁, 则会回滚, 使得事务可以正常完成.
举例:
Session 2 被检测出死锁的报错
虽然死锁可以被数据库检测到, 但是我们还是要尽量避免死锁的.
注意: 死锁的检测是比较消耗资源的 场景: iPhone13秒杀活动开始了, 在短暂时间内, 有大量请求执行下面的sql
此时, 会发现数据库撑不住了, 但是查看并发的请求量每秒可能还不到100, CPU却快到100%了. 原因是: 每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n) 的操作, 而整体的死锁检测是O(n^2)级别的。 因此,你就会看到 CPU 利用率很高,但是每秒却执行不了几个事务。 死锁检测是需要保持的, 但是如何减少死锁检测对数据库的影响呢? 主要方向: 控制访问相同资源的并发事务量。 具体的方法有:
你可以考虑通过将一行改成逻辑上的多行来减少锁冲突。还是以影院账户为例,可以考虑放在多条记录上,比如 10 个记录,影院的账户总额等于这 10 个记录的值的总和。这样每次要给影院账户加金额的时候,随机选其中一条记录来加。这样每次冲突概率变成原来的 1/10,可以减少锁等待个数,也就减少了死锁检测的 CPU 消耗。
参考 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 13:34:24- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |