文章目录
数据库及分布式事务
无论是关系型数据库Mysql,Oracle,PostgreSQL,还是非关系型数据库HBase,MongoDB,Cassandra,都针对不同的应用场景解决不同的问题.
引擎
数据库的存储引擎是数据库的底层软件组织,数据库管理系统(DBMS)使用存储引擎创建,查询,更新和删除数据.不同的存储引擎提供不同的存储机制,索引技巧,锁定水平等功能,都有其特定的功能.现在,许多数据库管理系统都支持多种存储引擎,常用的存储引擎有MyISAM,InnoDB,Memory.
1.MyISAM
是Mysql默认的存储引擎,不支持数据库事务,行级锁和外键,因此在插入或更新数据即写操作时要锁定整 张表,效率较低.
MyISAM的特点是执行读取操作的速度快,且占用的内存和存储资源较少.它在设计之初就假设数据被组织成固定长度的记录,并且是按顺序存储的.在查找数据时,MyISAM直接查找文件的OFFSET,定位比InnoDB要快(InnoDB寻址要先映射到块,再映射到行)
2.InnoDB
InnoDB为MySQL提供了事务支持,回滚,崩溃修复能力,多版本并发控制,事务安全的操作.InnoDB的底层数据结构为B+树,B+树的每个结点都对应InnoDB的一个Page,Page的大小是固定的,一般被设为16KB.其中,非叶子结点只有键值,叶子结点包含完整的数据.如下图所示:
InnoDB使用于有以下需求的场景:
- 经常有数据更新的表,适合处理多重并发更新请求
- 支持事务
- 支持灾难恢复(通过bin-log日志等)
- 支持外键约束,只有InnoDB支持外键
- 支持自动增加列属性auto_increment
3.Memory
Memory表使用内存空间创建.每个Memory表实际上都对应一个磁盘文件用于持久化.Memory表因为数据是存放在内存当中的,因此访问速度非常快,通常使用Hash索引来实现数据索引.Memory表的缺点是一旦服务关闭,表中数据就会消失.
Memory还支持散列索引和B树索引.B树索引可以使用部分查询和通配查询,也可以使用不等于和大于等于等操作符方便批量数据访问,散列索引相对于B树索引基于Key的查询效率特别高,但是基于范围查询的查询效率不是很高.
创建索引的原则
创建索引是提高数据库查询的最常用办法,创建索引的原则:
- 选择唯一性索引:唯一性索引一般基于Hash算法实现,可以快速,唯一地定位某条数据.
- 为经常需要排序,分组和联合操作的字段建立索引
- 为经常作为查询条件的字段建立索引
- 限制索引的数量:索引越多,数据更新表越慢,因为在数据更新时会不断计算和添加索引
- 尽量使用数据量少的索引:如果索引的值很长,则占用的磁盘空间变大,查询速度会受到影响
- 尽量使用前缀来索引:如果索引字段的值过长,则不但影响索引的大小,而且会降低索引的执行效率,这时需要使用字段的部分前缀来作为索引
- 删除不再使用或很少使用的索引
- 尽量选择区分度高的列作为索引
- 索引列不能参与运算:带函数的查询不建议参与索引
- 尽量扩展现有索引:联合索引的查询效率比多个独立索引高
数据库三范式
范式是具有最小冗余的表结构
1.第一范式
每列都是不可再分的最小数据单元,确保每列的原子性
2.第二范式
第二范式在第一范式的基础上,规定表中非主键列不存在对主键的部分依赖,即每个表只描述一件事
3.第三范式
满足第一范式和第二范式,并且表中的列不存在对非主键列的传递依赖,即表中的非主键列应与主键列紧密相关
数据库事务
ACID属性
- 原子性(Atomicity):事务是一个完成操作,参与事务的逻辑单元要么都执行,要么都不执行
- 一致性(Consistency):在事务执行完毕时(无论是正常执行完毕还是异常退出),数据都必须处于一致状态
- 隔离性(Isolation):对数据进行修改的所有并发事务都是彼此隔离的,它不应依赖或影响其他事务
- 永久性(Durability):在事务操作完成后,对数据的修改将被持久化到永久存储中
存储过程
存储过程指一组用于完成特定功能的SQL语句集,它被存储在数据库中,经过第一次编译后再次调用时不需再编译,用户通过指定存储过程的名字并给出参数来执行它.存储过程是数据库中的一个重要对象,可以通过存储过程快速完成复杂的计算操作.以下是常见的存储过程的优化思路:
- 尽量利用一些SQL语句代替小循环,例如聚合函数,求平均函数
- 中间结果被存放于临时表中,并加索引
- 少使用游标(Cursors):SQL是种集合语言,对于集合运算运算有较高的性能,而游标是过程运算.比如,对一个50万行数据进行查询时,如果使用游标,则需要对表执行50万次读取请求,会占用大量数据库资源.
- 事务越短越好:SQL Server支持并发操作,如果事务过长或者隔离级别过高,则都会造成并发操作的阻塞,死锁,导致查询速度极慢,CPU占用率高
- 使用try-catch处理异常
- 尽量不要将查询语句放在循环中,防止出现过度消耗系统资源的情况
触发器
触发器是一段能自动执行的程序,和普通存储过程的区别是"触发器在对某一个表或者数据操作时触发",例如进行update,insert,delete时,系统会自动调用和执行与表对应的触发器.触发器一般用于存储操作日志信息.
数据库的并发操作与锁
数据库的并发控制一般采用三种方法实现,分别是乐观锁,悲观锁及时间戳
1.乐观锁
乐观锁在读取数据时,认为别人不会去写所读的数据
2.悲观锁
悲观锁在修改数据时,不允许别人读取该数据,直到自己的整个事务都提交并释放锁,其他用户才能访问该数据.悲观锁又可分为排它锁(写锁)和共享锁(读锁).
3.时间戳
指在数据库中额外添加一个时间戳列TimeStamp.每次读取数据时,都把时间戳也读取出来,在更新数据时把时间戳也加1,在提交之前跟数据库的该字段比较一次,如果比数据库大,则允许保存,否则不允许保存.这种处理方法不适用数据库提供的锁机制,数据库处理的并发量也大大提高.
锁
1.行级锁
在执行以下数据库操作时,数据库会自动应用行级锁
- insert,update,delete,select,…for update[OF columns] [WAIT n|NOWAIT]
- select … for update语句允许用户一次针对多条记录执行更新
- 使用COMMIT或ROLLBACK语句释放锁
2.表级锁
对当前操作的整张表加锁
最常使用的MyISAM与InnoDB都支持表级锁定,表级锁定分为共享锁和排它锁
3.页级锁
表级锁的加锁速度快,但冲突多,行级锁的加锁速度慢,但冲突少,页级锁在两者之间做出了平衡,一次锁定相邻的一组记录
4.基于Redis的分布式锁
数据库锁是基于单个数据库实现的,在我们的业务跨多个数据库时,就要使用分布式锁来保证数据的一致性.下面介绍使用Redis实现一个分布式锁的流程.Redis实现的分布式锁以Redis setnx命令为中心实现,setnx是Redis的写入操作命令,具体语法为setnx(key val).在且仅在key不存在时,则插入一个key 为val的字符串,返回1;若key存在,则什么也不做,返回0.通过setnx实现分布式锁的思路如下.
- 获取锁:在获取锁时,调用setnx,如果返回0,则该锁正在被别人使用,如果返回1,则成功获取锁.
- 释放锁:在释放锁时,判断锁是否存在,如果存在,则执行Redis的delete操作释放锁.
数据库分表
数据库分表有垂直切分和水平切分两种
垂直切分:
将表按照功能模块,关系密切程度划分到不同的数据库中.例如我们会创建数据库workDB,商品数据库payDB,用户数据库userDB,日志数据库logDB等分别用于存储各类表.如图所示:
水平切分:
在一个表中数据量过大时,我们可以把该表按照某种规则如userID散列进行划分,然后将其存储到多个结构相同的表和不同的库上.
数据库分布式事务
CAP
在一个分布式系统中,一致性(Consistency),可用性(Availability)和分区容错性(Partition tolerance)三者不可兼得.
一致性:
在分布式系统的所有数据备份中,在同一时刻是否有同样的值
可用性:
在集群中一部分节点发生故障后,集群整体能否响应客户端的读写请求
分区容错性:
系统不能在时限内达成数据的一致性,就意味着发生了分区,必须就当前操作在C和A之间做出选择,分区相当于对通信的时限要求.
两阶段提交协议
分布式事务指设计多个数据库的事务,在分布式系统中,各个节点之间在物理上相互独立,通过网络进行沟通和协调.
二阶段提交指在计算机网络及数据库领域内,为了使分布式数据库的所有节点在进行数据提交时都保持一致性而设计的一种算法.在分布式系统中,每个节点只知道自己操作是否成功,却无法知道其他节点操作是否成功.
在一个事务跨越多个节点时,为了保证事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果.并最终指示这些节点是否真正提交操作结果(将更新后的数据写入磁盘),因此,二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,在由协调者根据所有参与者的反馈决定各参与者是提交操作还是中止操作.
1.Prepare(准备阶段)
事务协调者(事务管理器)给每个参与者(源管理器)都发送Prepare消息,参与者要么直接返回失败,要么在本地执行事务,写本地的redo和undo日志但不提交,是一种"万事俱备,只欠东风"的状态.
2.Commit(协调阶段)
如果协调者接受到了参与者的失败消息或者超时,则直接给每个参与者都发送回滚消息,否则发送提交消息,如下如所示:
三阶段提交协议
三阶段提交协议是两阶段提交的改进版本,具体改进如下:
- 引入超时机制:在协调者和参与者中引入超时机制,如果协调者长时间接收不到参与者的反馈,则认为参与者执行失败
- 在第一阶段和第二阶段都加入一个预准备阶段,以保证在最后的任务提交之前各参与节点的状态是一致的,也就是说,除了引入超市机制,三阶段提交协议把两阶段提交协议的准备阶段再一份为二,这样三段提交就有CanCommit,PreCommit,DoCommit三个阶段.