数据库的事务与索引

猪小花1号2018-09-05 09:49

作者:廖祥俐


数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。

在事务获取数据库当前状态(比如通过读取或修改数据)之前,它必须保护自己不受其他事务对同一数据进行修改的影响,在此通过请求锁定数据块来达到此目的,同时,为了加快事务的处理,采用数据库索引减少io操作。


数据库的锁分类

数据库是一个多用户使用的共享资源。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。

加锁是实现数据库并发控制的一个非常重要的技术。当事务在对某个数据对象进行操作前,先向系统发出请求,对其加锁。加锁后事务就对该数据对象有了一定的控制,在该事务释放锁之前,其他的事务不能对此数据对象进行更新操作。


基本锁类型

行级锁

行级锁是一种排他锁,防止其他事务修改此行;在使用以下语句时,数据库会自动应用行级锁:

INSERT、UPDATE、DELETE、SELECT … FOR UPDATE [OF columns] [WAIT n | NOWAIT];

SELECT … FOR UPDATE语句允许用户一次锁定多条记录进行更新


使用COMMIT或ROLLBACK语句释放锁

表级锁

  • 行共享 (ROW SHARE) – 禁止排他锁定表
  • 行排他(ROW EXCLUSIVE) – 禁止使用排他锁和共享锁
  • 共享锁(SHARE) - 锁定表,对记录只读不写,多个用户可以同时在同一个表上应用此锁
  • 共享行排他(SHARE ROW EXCLUSIVE) – 比共享锁更多的限制,禁止使用共享锁及更高的锁
  • 排他(EXCLUSIVE) – 限制最强的表锁,仅允许其他用户查询该表的行。禁止修改和锁定表


悲观锁

悲观锁,指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

注:要使用悲观锁,必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交(在自动提交模式下,每个SQL语句都是一个独立的事务)。关闭了mysql的autocommit,需要手动控制事务的提交

悲观的缺陷是不论是表锁还是行锁,加锁的时间可能会很长,这样可能会长时间的限制其他用户的访问,也就是说悲观锁的并发访问性不好。


乐观锁

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

乐观锁一般有两种做法:

  • 使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
  • 乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。


数据库索引

倒排索引通过各种数据结构的具体实现,是(值=>行位置)映射.

对于RDBMS(关系数据库管理系统)来说,主要有以下的作用:

  • 帮助更快速的定位和访问特定数据.
  • 帮助实现部分逻辑约束,如主键/唯一键,外键,...
  • 使用索引的首要任务在于减少io

索引通常会对数据增改操作性能产生负面影响:

  • 建立索引意味这一次写入操作需要维护多个物理存储对象.写入性能会受到影响,索引越多影响越大.
  • 插入/修改/删除每一个索引行都变成一个内部事务,索引越多,事务越长.
  • 索引维护通常来说是MySQL写入的主要io成本所在.

此外,由于保存索引将占用磁盘空间,实际通过索引进行数据查询时,需要把索引数据load到内存中,在Mysql生成执行计划时,需要把索引维护相关的操作也放在同一个事务中,索引过多的情况下,执行计划会越复杂,导致最终执行变慢。

创建索引的指导原则:

  • 该列用于频繁搜索
  • 该列用于对数据进行排序
  • 列中仅包含几个不同的值,不要使用该列创建索引

索引的常见用法

  • 根据业务中需要使用WHERE查询的条件建立索引
  • 联合索引中索引字段的顺序根据区分区排,区分度大的放在前面
  • 为ORDER BY,GROUP BY,DISTINCT字段添加索引
  • 给非常长的字符字段建立索引时,可以考虑添加crc32或者MD5 Hash字段,对Hash字段建立索引,或根据前缀创建索引(需要结合业务考虑,唯一索引特别要谨慎)


常见的设计策略

在高并发情况下,各项服务可能会承受高并发的压力,在各项服务设计中,希望能够将压力进行分解,需要考虑的是如何分解压力,避免层层传递,尤其对于数据库层,需要把压力控制。

一般采用的方式如下:

  • 通过均衡策略使得各台服务能够均衡负载

  • 将不必要的请求第一时间进行过滤/丢弃,避免不必要的资源浪费;

  • 业务层与数据层进行解耦,比如对于很多查询请求,如果每次都去数据层取数据,则将业务层的压力传导到数据层,一般会采用一层数据层的子库服务如redis/memcache进行扛高频压力

  • 对于最终数据层的操作,可以采取消费队列的方式,用可控的消费者进行数据层的操作,即数据层操作的压力是相对恒定的。

  • 在数据库操作中避免使用长事务,并且尽量少用事务。事务中避免长时间等待或调用,比如开启数据库事务后,做外部文件操作等



网易云大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者廖祥俐授权发布