尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Java面试——数据库及分布式事务

Java面试——数据库及分布式事务 数据库及分布式事务1、数据库的基本概念及原则1.1、存储引擎1.1.1、MyIASM1.1.2、InnoDB1.1.3、TokuDB1.1.4、Memory1.2、创建索引的原则1.3、数据库三范式1.3.1、第一范式1.3.2、第二范式1.3.3、第三范式1.4、数据库事务1.5、存储过程1.6、触发器2、数据库的并发操作和锁2.1、数据库的并发策略2.1.1、乐观锁2.1.2、悲观锁2.1.3、时间戳2.2、数据库锁2.2.1、行级锁2.2.2、表级锁2.2.3、页级锁2.2.4、基于Redis的分布式锁2.3、数据库分表3、数据库分布式事务3.1、CAP3.2、两阶段提交协议3.2.1、Prepare准备阶段3.2.2、Commit提交阶段3.2.3、两阶段提交的缺点3.3、三阶段提交协议3.3.1、CanCommit阶段3.3.2、PreCommit阶段3.3.3、DoCommit阶段3.4、分布式事务3.4.1、传统事务3.4.2、柔性事务1、数据库的基本概念及原则1.1、存储引擎数据库的存储引擎是数据库的底层软件组织数据库管理系统DBMS使用存储引擎创建、查询、更新和删除数据。不同的存储引擎提供了不同的存储机制、索引技巧、锁定水平等功能都有其特定的功能。现在许多数据库管理系统都支持多种存储引擎常用的存储引擎主要有MyISAM、InnoDB、Memory、Archive和Federated。1.1.1、MyIASMMyIASM是MySQL默认的存储引擎不支持数据库事务、行级锁和外键因此在INSERT插入或UPDATE更新数据即写操作时需要锁定整个表效率较低。MyIASM的特点是执行读取操作的速度快且占用的内存和存储资源较少。它在设计之初就假设数据被组织成固定长度的记录并且是按顺序存储的。在查找数据时MyIASM直接查找文件的OFFSET定位比InnoDB要快InnoDB寻址时要先映射到块再映射到行​。总体来说MyIASM的缺点是更新数据慢且不支持事务处理优点是查询速度快。1.1.2、InnoDBInnoDB为MySQL提供了事务Transaction支持、回滚Rollback​、崩溃修复能力Crash RecoveryCapabilities​、多版本并发控制Multi-versionedConcurrency Control​、事务安全Transaction-safe的操作。InnoDB的底层存储结构为B树B树的每个节点都对应InnoDB的一个Page, Page大小是固定 的一般被设为16KB。其中非叶子节点只有键值叶子节点包含完整的数据如图所示。InnoDB适用于有以下需求的场景。经常有数据更新的表适合处理多重并发更新请求。支持事务。支持灾难恢复通过bin-log日志等​。支持外键约束只有InnoDB支持外键。支持自动增加列属性auto_increment。1.1.3、TokuDBTokuDB的底层存储结构为Fractal Tree。Fractal Tree的结构与B树有些类似只是在Fractal Tree中除了每一个指针key​都需要指向一个child孩子节点child节点带一个Message Buffer这个Message Buffer是一个先进先出队列用来缓存更新操作具体的数据结构如图所示。这样每一次插入操作都只需落在某节点的Message Buffer上就可以马上返回并不需要搜索到叶子节点。这些缓存的更新操作会在后台异步合并并更新到对应的节点上。TokuDB在线添加索引不影响读写操作有非常高的写入性能主要适用于要求写入速度快、访问频率不高的数据或历史数据归档。1.1.4、MemoryMemory表使用内存空间创建。每个Memory表实际上都对应一个磁盘文件用于持久化。Memory表因为数据是存放在内存中的因此访问速度非常快通常使用Hash索引来实现数据索引。Memory表的缺点是一旦服务关闭表中的数据就会丢失。Memory还支持散列索引和B树索引。B树索引可以使用部分查询和通配查询也可以使用不等于和大于等于等操作符方便批量数据访问散列索引相对于B树索引来说基于Key的查询效率特别高但是基于范围的查询效率不是很高。1.2、创建索引的原则创建索引是我们提高数据库查询数据效率最常用的办法也是很重要的办法。下面是常见的创建索引的原则。选择唯一性索引唯一性索引一般基于Hash算法实现可以快速、唯一地定位某条数据。为经常需要排序、分组和联合操作的字段建立索引。为常作为查询条件的字段建立索引。限制索引的数量索引越多数据更新表越慢因为在数据更新时会不断计算和添加索引。尽量使用数据量少的索引如果索引的值很长则占用的磁盘变大查询速度会受到影响。尽量使用前缀来索引如果索引字段的值过长则不但影响索引的大小而且会降低索引的执行效率这时需要使用字段的部分前缀来作为索引。删除不再使用或者很少使用的索引。尽量选择区分度高的列作为索引区分度表示字段值不重复的比例。索引列不能参与计算带函数的查询不建议参与索引。尽量扩展现有索引联合索引的查询效率比多个独立索引高。1.3、数据库三范式范式是具有最小冗余的表结构三范式的概念如下所述。1.3.1、第一范式如果每列都是不可再分的最小数据单元也叫作最小的原子单元​则满足第一范式第一范式的目标是确保每列的原子性。如图所示其中的Address列违背了第一范式列不可再分的原则要满足第一范式就需要将Address列拆分为Country列和City列。1.3.2、第二范式第二范式在第一范式的基础上规定表中的非主键列不存在对主键的部分依赖即第二范式要求每个表只描述一件事情。如图所示Orders表既包含订单信息也包含产品信息需要将其拆分为两个单独的表。1.3.3、第三范式第三范式的定义为满足第一范式和第二范式并且表中的列不存在对非主键列的传递依赖。如图所示除了主键的订单编号顾客姓名依赖于非主键的顾客编号因此需要将该列去除。1.4、数据库事务数据库事务执行一系列基本操作这些基本操作组成一个逻辑工作单元一起向数据库提交要么都执行要么都不执行。事务是一个不可分割的工作逻辑单元。事务必须具备以下4个属性简称ACID属性。原子性Atomicity​事务是一个完整操作参与事务的逻辑单元要么都执行要么都不执行。一致性Consistency​在事务执行完毕时无论是正常执行完毕还是异常退出​数据都必须处于一致状态。隔离性Isolation​对数据进行修改的所有并发事务都是彼此隔离的它不应以任何方式依赖或影响其他事务。永久性Durability​在事务操作完成后对数据的修改将被持久化到永久性存储中。1.5、存储过程存储过程指一组用于完成特定功能的SQL语句集它被存储在数据库中经过第一次编译后再次调用时不需要被再次编译用户通过指定存储过程的名字并给出参数如果该存储过程带有参数来执行它。存储过程是数据库中的一个重要对象我们可以基于存储过程快速完成复杂的计算操作。以下为常见的存储过程的优化思路也是我们编写事务时需要遵守的原则。尽量利用一些SQL语句代替一些小循环例如聚合函数、求平均函数等。中间结果被存放于临时表中并加索引。少使用游标Cursors:SQL是种集合语言对于集合运算有较高的性能而游标是过程运算。比如对一个50万行的数据进行查询时如果使用游标则需要对表执行50万次读取请求将占用大量的数据库资源影响数据库的性能。事务越短越好SQL Server支持并发操作如果事务过长或者隔离级别过高则都会造成并发操作的阻塞、死锁导致查询速度极慢、CPU占用率高等。使用try-catch处理异常。尽量不要将查找语句放在循环中防止出现过度消耗系统资源的情况。1.6、触发器触发器是一段能自动执行的程序和普通存储过程的区别是“触发器在对某一个表或者数据进行操作时触发”​例如进行UPDATE、INSERT、DELETE操作时系统会自动调用和执行该表对应的触发器。触发器一般用于数据变化后需要执行一系列操作的情况比如对系统核心数据的修改需要通过触发器来存储操作日志的信息等。2、数据库的并发操作和锁2.1、数据库的并发策略数据库的并发控制一般采用三种方法实现分别是乐观锁、悲观锁及时间戳。2.1.1、乐观锁乐观锁在读数据时认为别人不会去写其所读的数据悲观锁就刚好相反觉得自己读数据时别人可能刚好在写自己刚读的数据态度比较保守时间戳在操作数据时不加锁而是通过时间戳来控制并发出现的问题。2.1.2、悲观锁悲观锁指在其修改某条数据时不允许别人读取该数据直到自己的整个事务都提交并释放锁其他用户才能访问该数据。悲观锁又可分为排它锁写锁和共享锁读锁​。2.1.3、时间戳时间戳指在数据库表中额外加一个时间戳列TimeStamp。每次读数据时都把时间戳也读出来在更新数据时把时间戳加1在提交之前跟数据库的该字段比较一次如果比数据库的值大就允许保存否则不允许保存。这种处理方法虽然不使用数据库系统提供的锁机制但是可以大大提高数据库处理的并发量。2.2、数据库锁2.2.1、行级锁行级锁指对某行数据加锁是一种排他锁防止其他事务修改此行。在执行以下数据库操作时数据库会自动应用行级锁。INSERT、UPDATE、DELETE、SELECT … FORUPDATE [OF columns] [WAIT n| NOWAIT]​。SELECT … FOR UPDATE语句允许用户一次针对多条记录执行更新。使用COMMIT或ROLLBACK语句释放锁。2.2.2、表级锁表级锁指对当前操作的整张表加锁它的实现简单资源消耗较少被大部分存储引擎支持。最常使用的MyISAM与InnoDB都支持表级锁定。表级锁定分为表共享读锁共享锁与表独占写锁排他锁​。2.2.3、页级锁页级锁的锁定粒度介于行级锁和表级锁之间。表级锁的加锁速度快但冲突多行级冲突少但加锁速度慢。页级锁在二者之间做了平衡一次锁定相邻的一组记录。2.2.4、基于Redis的分布式锁数据库锁是基于单个数据库实现的在我们的业务跨多个数据库时就要使用分布式锁来保证数据的一致性。下面介绍使用Redis实现一个分布式锁的流程。Redis实现的分布式锁以Redis setnx命令为中心实现setnx是Redis的写入操作命令具体语法为setnx(key val)。在且仅在key不存在时则插入一个key为val的字符串返回1若key存在则什么都不做返回0。通过setnx实现分布式锁的思路如下。获取锁在获取锁时调用setnx如果返回0则该锁正在被别人使用如果返回1则成功获取锁。释放锁在释放锁时判断锁是否存在如果存在则执行Redis的delete操作释放锁。简单的Redis实现分布式锁的代码如下注意如果锁并发比较大则可以设置一个锁的超时时间在超时时间到后Redis会自动释放锁给其他线程使用publicclassRedisLock{privatefinalstaticLogloggerLogFactory.getLog(BuilderDemo.class);privateJedisjedis;publicRedisLock(Jedisjedis){this.jedisjedis;}//获取锁publicsynchronizedbooleanlock(StringlockId){//设置锁Longstatusjedis.setnx(lockId,System.currentTimeMillis());if(0status){//有人在使用该锁获取锁失败returnfalse;}else{returntrue;//创建、获取锁成功锁idlockId}}//释放锁publicsynchronizedbooleanunlock(StringlockId){StringlockValuejedis.get(lockId);if(lockValue!null){//释放锁成功jedis.del(lockId);returntrue;}else{returnfalse;//释放锁失败}}publicstaticvoidmain(String[]args){JedisPoolConfigjconnewJedisPoolConfig();JedisPooljpnewJedisPool(jcon,127.0.0.1,6379);Jedisjedisjp.getResource();RedisLocklocknewRedisLock(jedis);StringlockId123;try{if(lock.lock(lockId)){//加锁后需要执行的逻辑代码}}catch(Exceptione){e.printStackTrace();}finally{lock.unlock(lockId);}}}以上代码定义了RedisLock类在该类中定义了一个Redis数据库连接Jedis同时定义了lock方法来获取一个锁在获取锁时首先通过setnx设置锁id获取Redis内锁的信息如果返回信息为0则表示锁正在被人使用锁id存在于Redis中​如果不为0则表示成功在内存中设置了该锁。同时在RedisLock类中定义了unlock方法用于释放一个锁具体做法是在Redis中查找该锁并删除。2.3、数据库分表数据库分表有垂直切分和水平切分两种下面简单介绍二者的区别。垂直切分将表按照功能模块、关系密切程度划分并部署到不同的库中。例如我们会创建定义数据库workDB、商品数据库payDB、用户数据库userDB、日志数据库logDB等分别用于存储项目数据定义表、商品定义表、用户数据表、日志数据表等如图所示。水平切分在一个表中的数据量过大时我们可以把该表的数据按照某种规则如userID散列进行划分然后将其存储到多个结构相同的表和不同的库上如图所示。3、数据库分布式事务3.1、CAPCAP原则又称CAP定理指的是在一个分布式系统中一致性Consistency​、可用性Availability和分区容错性Partition tolerance三者不可兼得。一致性在分布式系统的所有数据备份中在同一时刻是否有同样的值等同于所有节点都访问同一份最新的数据副本​。可用性在集群中一部分节点发生故障后集群整体能否响应客户端的读写请求对数据更新具备高可用性​。分区容错性系统如果不能在时限内达成数据的一致性就意味着发生了分区必须就当前操作在C和A之间做出选择。以实际效果而言分区相当于对通信的时限要求。3.2、两阶段提交协议分布式事务指涉及操作多个数据库的事务在分布式系统中各个节点之间在物理上相互独立通过网络进行沟通和协调。二阶段提交Two-Phase Commit指在计算机网络及数据库领域内为了使分布式数据库的所有节点在进行事务提交时都保持一致性而设计的一种算法。在分布式系统中每个节点虽然都可以知道自己的操作是否成功却无法知道其他节点的操作是否成功。在一个事务跨越多个节点时为了保持事务的ACID特性需要引入一个作为协调者的组件来统一掌控所有节点称作参与者的操作结果并最终指示这些节点是否真正提交操作结果比如将更新后的数据写入磁盘等​。因此二阶段提交的算法思路可以概括为参与者将操作成败通知协调者再由协调者根据所有参与者的反馈决定各参与者是提交操作还是中止操作。3.2.1、Prepare准备阶段事务协调者事务管理器给每个参与者源管理器都发送Prepare消息每个参与者要么直接返回失败如权限验证失败​要么在本地执行事务写本地的redo和undo日志但不提交是一种“万事俱备只欠东风”的状态。3.2.2、Commit提交阶段如果协调者接收到了参与者的失败消息或者超时则直接给每个参与者都发送回滚消息否则发送提交消息参与者根据协调者的指令执行提交或者回滚操作释放在所有事务处理过程中使用的锁资源如图所示。3.2.3、两阶段提交的缺点两阶段提交的缺点如下。同步阻塞问题在执行过程中所有参与者的任务都是阻塞执行的。单点故障所有请求都需要经过协调者在协调者发生故障时所有参与者都会被阻塞。数据不一致在二阶段提交的第2阶段在协调者向参与者发送Commit提交请求后发生了局部网络异常或者在发送Commit请求过程中协调者发生了故障导致只有一部分参与者接收到Commit请求于是整个分布式系统出现了数据不一致的现象这也被称为脑裂。协调者宕机后事务状态丢失协调者在发出Commit消息之后宕机唯一接收到这条消息的参与者也宕机即使协调者通过选举协议产生了新的协调者这条事务的状态也是不确定的没有人知道事务是否已被提交。3.3、三阶段提交协议三阶段提交Three-Phase Commit​也叫作三阶段提交协议Three-Phase Commit Protocol​是二阶段提交2PC的改进版本。具体改进如下。引入超时机制在协调者和参与者中引入超时机制如果协调者长时间接收不到参与者的反馈则认为参与者执行失败。在第1阶段和第2阶段都加入一个预准备阶段以保证在最后的任务提交之前各参与节点的状态是一致的。也就是说除了引入超时机制三阶段提交协议3PC把两阶段提交协议2PC的准备阶段再次一分为二这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。3.3.1、CanCommit阶段协调者向参与者发送Commit请求参与者如果可以提交就返回Yes响应否则返回No响应。3.3.2、PreCommit阶段协调者根据参与者的反应来决定是否继续进行有以下两种可能。假如协调者从所有参与者那里获得的反馈都是Yes响应就预执行事务。假如有任意参与者向协调者发送了No响应或者在等待超时之后协调者都没有接收到参与者的响应则执行事务的中断。3.3.3、DoCommit阶段该阶段进行真正的事务提交主要包括协调者发送提交请求参与者提交事务参与者响应反馈在事务提交完之后向协调者发送Ack响应​协调者确定完成事务如图所示。3.4、分布式事务3.4.1、传统事务传统事务遵循ACID原则即原子性、一致性、隔离性和持久性。原子性事务是包含一系列操作的原子操作事务的原子性确保这些操作全部完成或者全部失败。一致性事务执行的结果必须使数据库从不一致性状态转为一致性状态。保证数据库的一致性指在事务完成时必须使所有数据都有一致的状态。隔离性因为可能在相同的数据集上同时有许多事务要处理所以每个事务都应该与其他事务隔离避免数据被破坏。持久性一旦事务完成其结果就应该能够承受任何系统的错误比如在事务提交过程中服务器的电源被切断等。在通常情况下事务的结果被写入持续性存储中。3.4.2、柔性事务在分布式数据库领域基于CAP理论及BASE理论阿里巴巴提出了柔性事务的概念。BASE理论是CAP理论的延伸包括基本可用Basically Available​、柔性状态Soft State​、最终一致性Eventual Consistency三个原则并基于这三个原则设计出了柔性事务。我们通常所说的柔性事务分为两阶段型、补偿型、异步确保型、最大努力通知型。两阶段型事务指分布式事务的两阶段提交对应技术上的XA和JTA/JTS是分布式环境下事务处理的典型模式。TCC型事务Try、Confirm、Cancel为补偿型事务是一种基于补偿的事务处理模型。如图7-10所示服务器A发起事务服务器B参与事务如果服务器A的事务和服务器B的事务都顺利执行完成并提交则整个事务执行完成。但是如果事务B执行失败事务B本身就回滚这时事务A已被提交所以需要执行一个补偿操作将已经提交的事务A执行的操作进行反操作恢复到未执行前事务A的状态。需要注意的是发起提交的一般是主业务服务而状态补偿的一般是业务活动管理者因为活动日志被存储在业务活动管理中补偿需要依靠日志进行恢复。TCC事务模型牺牲了一定的隔离性和一致性但是提高了事务的可用性。异步确保型事务指将一系列同步的事务操作修改为基于消息队列异步执行的操作来避免分布式事务中同步阻塞带来的数据操作性能下降。如图所示在写业务数据A触发后将执行以下流程。业务A的模块在数据库A上执行数据更新操作。业务A调用写消息数据模块。写消息日志模块将数据库的写操作状态写入数据库A中。写消息日志模块将写操作日志发送给消息服务器。读消息日志模块接收操作日志。读消息数据调用写业务B的模块。写业务B更新数据到数据库B。写业务数据B的模块发送异步消息更新数据库A中的写消息日志状态说明自己已经完成了异步数据更新操作。最大努力通知型事务也是通过消息中间件实现的与前面异步确保型操作不同的是在消息由MQ服务器发送到消费者之后允许在达到最大重试次数之后正常结束事务因此无法保障数据的最终一致性。如图所示写业务数据A在更新数据库后调用写消息日志将数据操作以异步消息的形式发送给读消息日志模块读消息日志模块在接收到数据操作后调用写业务B写数据库。和异步确保型不同的是数据库B在写完之后将不再通知写状态到数据库A如果因为网络或其他原因在如图7-12所示的第4步没有接收到消息则消息服务器将不断重试发送消息到读消息日志如果经过N次重试后读消息日志还是没有接收到日志则消息不再发送这时会出现数据库A和数据库B数据不一致的情况。最大努力型通知事务通过消息服务使分布式事务异步解耦并且模块简单、高效但是牺牲了数据的一致性在金融等对事务要求高的业务中不建议使用但在日志记录类等对数据一致性要求不是很高的应用上执行效率很高。
返回列表