mysql 数据库事务性(mysql存储引擎及索引结构)

数据库事务性

并非任意的对数据库的操作序列都是数据库事务。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

  • 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。换句话说,一个事务的影响在该事务提交前对其他事务都不可见。
  • 持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。

并发控制Concurrency control

数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。下面举例说明并发操作带来的数据不一致性问题:
现有两处火车票售票点,同时读取某一趟列车车票数据库中车票余额为 X。两处售票点同时卖出一张车票,同时修改余额为 X -1写回数据库,这样就造成了实际卖出两张火车票而数据库中的记录却只少了一张。 产生这种情况的原因是因为两个事务读入同一数据并同时修改,其中一个事务提交的结果破坏了另一个事务提交的结果,导致其数据的修改被丢失,破坏了事务的隔离性。并发控制要解决的就是这类问题。
封锁时间戳乐观并发控制(乐观锁)悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

当并发事务同时访问一个资源时,有可能导致数据不一致,因此需要一种机制来将数据访问顺序化,以保证数据库数据的一致性。锁就是其中的一种机制。在计算机科学中,锁是在执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足。

锁的分类(oracle)

一、按操作划分,可分为DML锁DDL锁
二、按锁的粒度划分,可分为表级锁行级锁页级锁(mysql)
三、按锁级别划分,可分为共享锁排他锁
四、按加锁方式划分,可分为自动锁显示锁
五、按使用方式划分,可分为乐观锁悲观锁

  • DML锁(data locks,数据锁),用于保护数据的完整性,其中包括行级锁(Row Locks (TX锁))、表级锁(table lock(TM锁))。
  • DDL锁(dictionary locks,数据字典锁),用于保护数据库对象的结构,如表、索引等的结构定义。
  • 其中包排他DDL锁(Exclusive DDL lock)、共享DDL锁(Share DDL lock)、可中断解析锁(Breakable parse locks)

数据隔离级别

如果不对数据库进行并发控制,可能会产生异常情况:

  1. 脏读(Dirty Read)
    • 当一个事务读取另一个事务尚未提交的修改时,产生脏读。
    • 同一事务内不是脏读。 一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的,因为很可能所有的操作都被回滚,也就是说读取出的数据其实是错误的。
  2. 非重复读(Nonrepeatable Read):一个事务对同一行数据重复读取两次,但是却得到了不同的结果。同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。
  3. 幻像读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。
    • 当对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围时,会发生幻像读问题。
  4. 丢失修改(Lost Update)
    • 第一类:当两个事务更新相同的数据源,如果第一个事务被提交,第二个却被撤销,那么连同第一个事务做的更新也被撤销。
    • 第二类:有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。

为了兼顾并发效率和异常控制,在标准SQL规范中,定义了4个事务隔离级别,( Oracle 和 SQL Server 对标准隔离级别有不同的实现 )

  1. 未提交读(Read Uncommitted)
    • 直译就是”读未提交”,意思就是即使一个更新语句没有提交,但是别的事务可以读到这个改变。
    • Read Uncommitted允许脏读。
  2. 已提交读(Read Committed)
    • 直译就是”读提交”,意思就是语句提交以后,即执行了 Commit 以后别的事务就能读到这个改变,只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别。
    • Read Commited 不允许脏读,但会出现非重复读。
  3. 可重复读(Repeatable Read)
    • 直译就是”可以重复读”,这是说在同一个事务里面先后执行同一个查询语句的时候,得到的结果是一样的。
    • Repeatable Read 不允许脏读,不允许非重复读,但是会出现幻象读。
  4. 串行读(Serializable)
    • 直译就是”序列化”,意思是说这个事务执行的时候不允许别的事务并发执行。完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
    • Serializable 不允许不一致现象的出现。

事务隔离的实现——锁

  1. 共享锁(S锁)
    • 用于只读操作(SELECT),锁定共享的资源。共享锁不会阻止其他用户读,但是阻止其他的用户写和修改。
  2. 更新锁(U锁)
    • 用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。
  3. 独占锁(X锁,也叫排他锁)
    • 一次只能有一个独占锁用在一个资源上,并且阻止其他所有的锁包括共享缩。写是独占锁,可以有效的防止“脏读”。

隔离级别如何实现,这部分的详细内容可以参考深入分析事务的隔离级别一文,下表是进行的总结。

隔离级别实现原理
未提交读(Read uncommitted)事务在读数据的时候并未对数据加锁;事务在修改数据的时候只对数据增加行级共享锁。
提交读(Read committed)事务对当前被读取的数据加 行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁;事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放。
可重复读(Repeatable reads)事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加 行级共享锁,直到事务结束才释放;事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。
可序列化(Serializable)事务在读取数据时,必须先对其加 表级共享锁 ,直到事务结束才释放;事务在更新数据时,必须先对其加 表级排他锁 ,直到事务结束才释放。

MySQL 的存储引擎

关系数据库表是用于存储和组织信息的数据结构,可以将表理解为由行和列组成的表格,类似于Excel的电子表格的形式。有的表简单,有的表复杂,有的表根本不用来存储任何长期的数据,有的表读取时非常快,但是插入数据时去很差;而我们在实际开发过程中,就可能需要各种各样的表,不同的表,就意味着存储不同类型的数据,数据的处理上也会存在着差异。

对于MySQL来说,它提供了很多种类型的存储引擎,我们可以根据对数据处理的需求,选择不同的存储引擎,从而最大限度的利用MySQL强大的功能。本节将总结和分析各个引擎的特点,以及适用场合,这里先不会纠结于更深层次的东西,下面就对MySQL支持的存储引擎进行简单的介绍。(本节主要参考MySQL存储引擎介绍一文)

在mysql客户端中,使用以下命令可以查看MySQL支持的引擎。

show engines;

MyISAM

MyISAM 表是独立于操作系统的,这说明可以轻松地将其从 Windows 服务器移植到 Linux 服务器;每当我们建立一个 MyISAM 引擎的表时,就会在本地磁盘上建立三个文件,文件名就是表名。例如,我建立了一个 MyISAM 引擎的 tb_Demo 表,那么就会生成以下三个文件:

  • tb_demo.frm:存储表定义;
  • tb_demo.MYD:存储数据;
  • tb_demo.MYI:存储索引。

有以下特点:

  • 不支持事务:MyISAM存储引擎不支持事务,所以对事务有要求的业务场景不能使用
  • 表级锁定:其锁定机制是表级锁定,这虽然可以让锁定的实现成本很小但是也同时大大降低了其并发性能
  • 读写互相阻塞:不仅会在写入的时候阻塞读取,MyISAM还会在读取的时候阻塞写入,但读本身并不会阻塞另外的读
  • 只会缓存索引:MyISAM可以通过key_buffer缓存以大大提高访问性能减少磁盘IO,但是这个缓存区只会缓存索引,而不会缓存数据

MyISAM 表无法处理事务,这就意味着有事务处理需求的表,不能使用 MyISAM 存储引擎。MyISAM 存储引擎特别适合在以下几种情况下使用:

  • 选择密集型的表。MyISAM存储引擎在筛选大量数据时非常迅速,这是它最突出的优点。
  • 插入密集型的表。MyISAM的并发插入特性允许同时选择和插入数据。例如:MyISAM存储引擎很适合管理邮件或Web服务器日志数据。

InnoDB

InnoDB 是一个健壮的事务型存储引擎,这种存储引擎已经被很多互联网公司使用,为用户操作非常大的数据存储提供了一个强大的解决方案。InnoDB 是默认的存储引擎,InnoDB 还引入了行级锁定和外键约束。

有以下几点特点:

  1. 具有较好的事务支持:支持4个事务隔离级别,支持多版本读
  2. 行级锁定:通过索引实现,全表扫描仍然会是表锁,注意间隙锁的影响
  3. 读写阻塞与事务隔离级别相关
  4. 具有非常高效的缓存特性:能缓存索引,也能缓存数据
  5. 整个表和主键以Cluster方式存储,组成一颗平衡树
  6. 所有Secondary Index都会保存主键信息

在以下场合下,使用 InnoDB 是最理想的选择:

  1. 更新密集的表。InnoDB 存储引擎特别适合处理多重并发的更新请求。
  2. 事务。InnoDB 存储引擎是支持事务的标准 MySQL 存储引擎。
  3. 自动灾难恢复。与其它存储引擎不同,InnoDB 表能够自动从灾难中恢复。
  4. 外键约束。MySQL支持外键的存储引擎只有 InnoDB。
  5. 支持自动增加列AUTO_INCREMENT属性。

一般来说,如果需要事务支持,并且有较高的并发读取频率,InnoDB 是不错的选择。

MEMORY

使用 MySQL Memory 存储引擎的出发点是速度。为得到最快的响应时间,采用的逻辑存储介质是系统内存。虽然在内存中存储表数据确实会提供很高的性能,但当 mysqid 守护进程崩溃时,所有的 Memory 数据都会丢失。获得速度的同时也带来了一些缺陷。它要求存储在 Memory 数据表里的数据使用的是长度不变的格式,这意味着不能使用 BLOB 和 TEXT 这样的长度可变的数据类型,VARCHAR 是一种长度可变的类型,但因为它在 MySQL 内部当做长度固定不变的 CHAR 类型,所以可以使用。

一般在以下几种情况下适用 Memory 存储引擎:

  1. 目标数据较小,而且被非常频繁地访问。在内存中存放数据,所以会造成内存的使用,可以通过参数 max_heap_table_size 控制 Memory 表的大小,设置此参数,就可以限制 Memory 表的最大大小;
  2. 如果数据是临时的,而且要求必须立即可用,那么就可以存放在内存表中。
  3. 存储在 Memory 表中的数据如果突然丢失,不会对应用服务产生实质的负面影响。

Memory 同时支持散列索引和B树索引。B树索引的优于散列索引的是,可以使用部分查询和通配查询,也可以使用<、>和>=等操作符方便数据挖掘。散列索引进行“相等比较”非常快,但是对“范围比较”的速度就慢多了,因此散列索引值适合使用在=和<>的操作符中,不适合在<或>操作符中,也同样不适合用在 order by 子句中。

可以在表创建时利用 USING 子句指定要使用的版本。

create table users(
    id smallint unsigned not null auto_increment,
    username varchar(15) not null,
    pwd varchar(15) not null,
    index using hash (username),
    primary key (id)
)engine=memory;

上述代码创建了一个表,在 username 字段上使用了 HASH 散列索引。下面的代码就创建一个表,使用 BTREE 索引。

create table users(
    id smallint unsigned not null auto_increment,
    username varchar(15) not null,
    pwd varchar(15) not null,
    index using btree (username),
    primary key (id)
)engine=memory;

MERGE

MERGE 存储引擎是一组 MyISAM 表的组合,这些 MyISAM 表结构必须完全相同,尽管其使用不如其它引擎突出,但是在某些情况下非常有用。说白了,Merge 表就是几个相同 MyISAM 表的聚合器;Merge 表中并没有数据,对 Merge 类型的表可以进行查询、更新、删除操作,这些操作实际上是对内部的 MyISAM 表进行操作。

总结

特点适用情况
MyISAM独立于操作系统的;不支持事务;表级锁定;不需要事务支持;并发相对较低; 数据修改相对较少,以读为主;数据一致性要求不是非常高
InnoDB事务性;行级锁定;外键约束;需要事务支持;数据更新较为频繁; 数据一致性要求较高;行级锁定对高并发有很好的适应能力
MEMORY存储内存里,数据容易丢失;性能高;存储的数据表要求数据类型长度不可变数据小,但是可能会被频繁访问;数据是临时,并且立即可用; 可靠性不高的数据

数据库的索引结构

在 MySQL 官方定义中,索引( Index )是帮助 MySQL 高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。

在 MySQL 中,主要有四种类型的索引,分别为: B-Tree 索引, Hash 索引, Fulltext (全文)索引和 R-Tree 索引。

具体可以参考MySQL索引背后的数据结构及算法原理-张洋一文,本文以后会把这部分给补充上。

关于数据库的索引结构还有一下几篇文章推荐:

摘自数据库事务性(MySQL 存储引擎及索引结构)-柳年思水

  • qq_43638135
    妲己再美究为妃: 博主没有想过自己接一些私活干吗?我现在还没毕业,但是我也确实听说外挂市场自动化游戏脚本市场挺火热的,并且报酬也很丰厚,但是具体的我也不是很清楚,求解答。 (1个月前 #47楼) 查看回复(2) 举报 回复
    22