您的位置:首页 > 数据库 > MySQL

mysql-Innodb

2016-01-30 23:41 447 查看
在mysql架构分析一文中,我们提及到了一个非常重要的存储引擎—Innodb。

Innodb采用哈希索引建立内存页索引形成自适应哈希索引,而不是采用B-tree索引,得以加速行记录到内存页的检索。InnoDB存储引擎表总是B+树索引组织的。 B+树索引本身并不能找到具体的一条记录,B+树索引能找到的只是该记录所在的页。数据库把页载入内存,然后通过Page Directory 再次进行二叉查找。只不过二叉查找的时间复杂度很低,同时内存中的查找速度很快,因此通常忽略了这部分查找所用的时间,本文将从以下五个方面分析这个高性能的事务存储引擎。

一、Innodb特性

二、Innodb表

三、Innodb索引与算法

四、Innodb中的锁

五、Innodb中的事务

一、Innodb特性

1、默认7个后台进程:4个IO thread(insert buffer、log、read、write)、1个优先级最高的Master thread、1个lock thread(锁监控线程)、1个错误监控线程。

//通过下面命令可以查看
mysql> show engine innodb status;

| InnoDB |      |
=====================================
160127 21:35:49 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 24 seconds
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 2, signal count 2
Mutex spin waits 0, rounds 0, OS waits 0
RW-shared spins 4, OS waits 2; RW-excl spins 0, OS waits 0
------------
TRANSACTIONS
------------
Trx id counter 0 2304
Purge done for trx's n:o < 0 0 undo n:o < 0 0
History list length 0
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0 0, not started, process no 3568, OS thread id 3068373872
MySQL thread id 3, query id 3 localhost root
show engine innodb status
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (write thread)
Pending normal aio reads: 0, aio writes: 0,
ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 0; buffer pool: 0
41 OS file reads, 3 OS file writes, 3 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2,
0 inserts, 0 merged recs, 0 merges
Hash table size 34679, node heap has 0 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s
---
LOG
---
Log sequence number 0 53337
Log flushed up to   0 53337
Last checkpoint at  0 53337
0 pending log writes, 0 pending chkp writes
8 log i/o's done, 0.00 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 17312964; in additional pool allocated 859008
Dictionary memory allocated 20888
Buffer pool size   512
Free buffers       493
Database pages     19
Modified db pages  0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages read 19, created 0, written 0
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
1 read views open inside InnoDB
Main thread process no. 3568, id 2969426800, state: waiting for server activity
Number of rows inserted 0, updated 0, deleted 0, read 0
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================


2、Innodb存储引擎由三部分组成:缓冲池(buffer pool)、重做日志缓冲池(redo log buffer)、额外的内存池(additional memory pool)

//查看具体配置
mysql> show variables like 'innodb_buffer_pool_size';
+-------------------------+---------+
| Variable_name           | Value   |
+-------------------------+---------+
| innodb_buffer_pool_size | 8388608 |
+-------------------------+---------+

mysql> show variables like 'innodb_log_buffer_size';
+------------------------+---------+
| Variable_name          | Value   |
+------------------------+---------+
| innodb_log_buffer_size | 1048576 |
+------------------------+---------+

mysql> show variables like 'innodb_additional_mem_pool_size';
+---------------------------------+---------+
| Variable_name                   | Value   |
+---------------------------------+---------+
| innodb_additional_mem_pool_size | 1048576 |
+---------------------------------+---------+


3、缓冲池:存放各种数据的缓存(索引页、数据页、undo页、插入缓冲、自适应哈希索引、innodb存储的锁信息、数据字典信息等)。工作方式:将数据库文件按页(16k)读取到缓冲池,然后按lru算法来保留缓冲池中的缓存数据。如果数据库文件需要修改,总是首先修改缓冲池 中的页(修改的页变为脏页),然后按照一定的频率将缓冲池中的脏页刷新到文件。

可以通过以下命令查看:

show engine innodb status;


4、日志缓冲:将重做日志信息先放入日志缓冲区,然后按照一定频率将其刷新到重做日志文件。



5、Master thread

loop主循环每秒一次的操作:

日志缓冲刷新到磁盘,即使这个事务还没有提交(总是执行,再大的事务commit时间也很快)。

合并插入缓冲(innodb当前一秒发生的IO次数小于5次则执行)。

至多刷新100个innodb的缓冲池中的脏页到磁盘(由innodb_max_dirty_pages_pac决定)。

如果当前米有用户活动,则切换到backgroup loop。

loop主循环每10秒一次的操作:

刷新100个脏页到磁盘(过去10sIO操作小于200次则执行)。

合并至多5个插入缓冲(总是)。

将日志缓冲到磁盘(总是)。

删除无用的Undo页(总是)。

刷新100个或者10个脏页到磁盘(有超过70%脏页,刷新100个脏页)。

产生一个检查点。

background loop:若当前没有用户活动(数据库空闲)或数据库关闭时进入该循环

删除无用的Undo页(总是)。

合并20个插入缓冲(总是)。

调回到主循环(总是)。

不刷新100个页,直到符合条件(可能在flush loop中完成)。

如何flush loop中页没什么事情可做,innodb存储引擎就会切换到suspend_loop,将master thread挂起,等待事件的发生。如启用了innodb,确没有使用任何innodb存储引擎的表,那么master 人thread 总是处于挂起状态。

6、插入缓冲:

不是缓冲池的一部分,Insert Buffer是物理页的一个组成部分,它带来InnoDB性能的提高。根据B+算法(下文会提到)的特点,插入数据的时候会主键索引是顺序的,不会造成数据库的随机读取,而对于非聚集索引(即辅助索引),叶子节点的插入不再是顺序的了,这时需要离散地访问非聚集索引,插入性能在这里变低了。InnoDB引入插入缓冲,判断非聚集索引页是否在缓冲池中,如果在则直接插入;不在,则先放在 插入缓冲区中。然后根据上述master thread中介绍的,会有一定的频率将插入缓冲合并。此外,辅助索引不能是唯一的,因为插入到插入缓冲时,并不去查找索引页的情况,否则仍然会造成随机读,失去插入缓冲的意义了。插入缓冲可能会占缓冲池中内存,默认也能会占到1/2,所以可以将这个值调小点,到1/3。通过IBUF_POOL_SIZE_PER_MAX_SIZE来设置,2表示1/2,3表示1/3。

7、两次写:

它带来InnoDB数据的可靠性。如果写失效,可以通过重做日志进行恢复,但是重做日志中记录的是对页的物理操作,如果页本身损坏,再对其进行重做是没有意义的。所以,在应用重做日志前,需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做,这就是doublewire。

恢复数据=页副本+重做日志



8、自适应哈希索引:

InnoDB存储引擎提出一种自适应哈希索引,存储引擎会监控对表上索引的查找,如果观察到建立建立哈希索引会带来速度的提升,则建立哈希索引,所以称之为自适应的。自适应哈希索引只能用来搜索等值的查询,如select * from table where index_col=’*‘, 此外自适应哈希是由InnoDB存储引擎控制的,我们只能通过innodb_adaptive_hash_index来禁用或启用,默认开启。

二、Innodb表

表空间:表空间可看做是InnoDB存储引擎逻辑结构的最高层。

段:表空间由各个段组成,常见的段有数据段、索引段、回滚段等。

区:由64个连续的页组成,每个页大小为16kb,即每个区大小为1MB。

页:每页16kb,且不能更改。常见的页类型有:数据页、Undo页、系统页、事务数据页、插入缓冲位图页、插入缓冲空闲列表页、未压缩的二进制大对象页、压缩的二进制大对象页。

行:InnoDB存储引擎是面向行的(row-oriented),每页最多允许存放7992行数据。

行记录格式:常见两种行记录格式Compact和Redundant,mysql5.1版本后,主要是Compact行记录格式。对于Compact,不管是char型还是varchar型,null型都是不占用存储空间的;对于Redudant,varchar的null不占用空间,char的null型是占用存储空间的。

varchar类型的长度限制是65535,其实达不到,会有别的开销,一般是65530左右,这还跟选取的字符集有关。此外这个长度限制是一整行的,例如:create table test(a varchar(22000), b varchar(22000), cvarchar(22000)) charset=latin1 engine=innodb也会报错。

对于blob类型的数据,在数据页面中只保存了varchar(65535)的前768个字节前缀数据,之后跟的是偏移量,指向行溢出页,也就是Uncompressed BLOB Page。新的InnoDB Plugin引入了新的文件格式称为Barracuda,其有两种新的行记录格式Compressed和Dynamic,两者对于存入Blog字段采用了完全溢出的方式,在数据库页中存放20个字节的指针,实际的数据都存入在BLOB Page中。

数据页结构:数据页结构由以下7个部分组成:

File Header(文件头):记录页的一些头信息,如页偏移量、上一页、下一页、页类型等,固定长度为38个字节。

Page Header(页头):记录页的状态信息,堆中记录数、指向空闲列表的指针、已删除记录的字节数、最后插入的位置等,固定长度共56个字节。

Infimun+Supremum Records:在InnoDB存储引擎中,每个数据页中有两个虚拟的行记录,用来限定记录的边界。

Infimun记录是比该页中任何主键都要小的值,Supermum指比任何可能大的值还要大的值。这两个值在页创建时被建立,并且在任何情况下不会被删除。在Compact行格式和Redundant行格式下,两者占用的字节数各不相同。



User Records(用户记录,即行记录):实现记录的内容。再次强调,InnoDB存储引擎表总是B+村索引组织的。

Free Space(空闲空间):指空闲空间,同样也是个链表数据结构。当一条记录被删除后,该空间会被加入空闲链 表中。

Page Directory(页目录):页目录存放了记录的相对位置,并不是偏移量,有些时候这些记录称为Slots(槽),InnoDB并不是每个记录一个槽,槽是一个稀疏目录,即一个槽中可能属于多个记录,最少属于4条记录,最多属于8条记录。需要牢记的是,B+树索引本身并不能找到具体的一条记录,B+树索引能找到只是该记录所在的页。数据库把页载入内存,然后通过Page Directory再进行二叉查找。只不过二叉查找的时间复杂度低,同时内存中的查找很快,因此通过忽略了这部分查找所用的时间。

File Trailer(文件结尾信息):为了保证页完整地写入磁盘(如写过程的磁盘损坏、机器宕机等),固定长8个字节。

视图:Mysql中的视图总是虚拟的表,本身不支持物化视图。但是通过一些其他技巧(如触发器),同样也可以实现一些简单的物化视图的功能。

分区:Mysql数据库支持RANGE、LIST、HASH、KEY、COLUMNS分区,并且可以使用HASH或KEY来进行子分区。

三、Innodb索引与算法

B+树索引:B+树的数据结构相对较复杂,B代表的是balance最早是从平衡二叉树演化而来,但B+树并不是一个二叉树, 由于B+树索引的高扇出性,因此在数据库中,B+树的高度一般都在2~3层,也就对于查找某一键值的行记录,最多只要2到3次IO,现在一般的磁盘每秒至少可以做100次IO,2~3次的IO意味着查询时间只需0.02~0.03秒。

数据库中的B+索引可以分为聚集索引(clustered index)和辅助聚集索引(secondary index),但其内部都是B+树的,即高度平衡的,叶子节点存放数据。

聚集索引:由于聚集索引是按照主键组织的,所以每一张表只能有一个聚集索引,每个数据页都通过双向链表进行连接,叶子节点存放一整行的信息,所以查询优化器更倾向走聚集索引。此外,对于聚集索引的存储是逻辑上连续的。所以,聚集索引对于主键的排序查找和范围查找速度非常快。

辅助索引:也叫非聚集索引,叶子节点不存全部数据,主要存键值及一个boomark(其实就是聚集索引的键)告诉InnoDB哪里可以找到与索引相对应的行数据,如一个高度为3的辅助索引和一个高度为3的聚集索引,若根据辅助索引来查询行记录,一共需要6次IO。另外辅助索引可以有多个。

索引的使用原则:高选择、取出表中的少部分数据(也称为唯一索引)。一般取出的数据量超过表中数据的20%,优化器不会使用索引,而进行全表扫描。如对于性别等字段是没有意义的。

联合索引: 也称复合索引,是在多列(>=2)上建立的索引。Innodb中的复合索引也是b+ tree结构。索引的数据包含多列(col1, col2, col3…),在索引中依次按照col1, col2, col3排序。如(1, 2), (1, 3),(2,0)…使用复合索引要充分利用最左前缀原则,顾名思义,就是最左优先。如创建索引ind_col1_col2(col1, col2),那么在查询where col1 = xxx and col2 = xx或者where col1 = xxx都可以走ind_col1_col2索引,但where col2=**是走不到索引的。在创建多列索引时,要根据业务需求,where子句中使用最频繁且过滤效果好的的一列放在最左边。

哈希索引:哈希算法也是比较常见的算法,mysql innoDB中使用了比较常见的链地址法进行去重。此外上面已经提及,innoDB中的hash是自适应的,什么时候使用hash是系统决定的,无法进行人工设置。

二分查找法:这个算法比较常见,这里就不多提及了。在InnoDB中,每页Page Directory中的槽是按照主键的顺序存放的,对于某一条具体记录的查询是通过对Page Directory进行二分查找得到的。

四、Innodb中的锁

InnoDB存储引擎锁的实现和Oracle非常类似,提供一致性的非锁定读、行级锁支持、行级锁没有相关的开销,可以同时得到并发性和一致性。

InnoDB存储引擎实现了如下两种标准的行级锁:

共享锁(S Lock):允许事务读一行数据;

排他锁(X Lock):允许事务删除或者更新一行数据。

当一个事务已经获得了行r的共享锁,那么另外的事务可以立即获得行r的共享锁,因为读取没有改变行r的数据,我们称这种情况为锁兼容。但如果有事务想获得行r的排他锁,则它必须等待事务释放行r上的共享锁————这种情况称为锁不兼容。

在InnoDB Plugin之前,只能通过SHOW FULL PROCESSLIST,SHOW ENGINE INOODB STATUS等命令来查看当前的数据库请求,然后再判断当前事务中的锁的情况。新版本的InnoDB Plugin中,在INFORMATION_SCHEMA架构下添加了INNODB_TRX、INNODB_LOCKS、InnoDB_LOCK_WAITS。通过这三张表,可以更简单地监控当前的事务并分析可能存在的锁的问题。

INNODB_TRX由8个字段组成:

trx_id:InnoDB存储引擎内部唯一的事务ID

trx_state:当前事务的状态。

trx_started:事务的开始时间。

trx_requested_lock_id:等待事务的锁ID。如trx_state的状态为LOCK WAIT,那么该值代表当前的等待之前事务占用锁资源的ID.

若trx_state不是LOCK WAIT,则该值为NULL。

trx_wait_started:事务等待开始的时间。

trx_weight:事务的权重,反映了一个事务修改和锁住的行数。在InnoDB存储引擎中,当发生死锁需要回滚时,InnoDB存储会选

择该值最小的进行回滚。

trx_mysql_thread_id:Mysql中的线程ID,SHOW PROCESSLIST显示的结果。

trx_query:事务运行的sql语句。

通过select * from infomation_schema.INNODB_TRX;可查看

INNODB_LOCKS表,该表由如下字段组成:

lock_id:锁的ID。

lock_trx_id:事务ID。

lock_mode:锁的模式。

lock_type:锁的类型,表锁还是行锁。

lock_table:要加锁的表。

lock_index:锁的索引。

lock_space:InnoDB存储引擎表空间的ID号。

lock_page:被锁住的页的数量。若是表锁,则该值为NULL。

lock_rec:被锁住的行的数量。若是表锁,则该值为NULL。

lock_data:被锁住的行的主键值。当是表锁时,该值为NULL。

通过select * from information_schema.INNODB_LOCK;可查看

INNODB_LOCK_WAIT由4个字段组成:
requesting_trx_id:申请锁资源的事务ID。
requesting_lock_id:申请的锁的ID。
blocking_trx_id:阻塞的锁的ID。
通过select * from information_schema.INNODB_LOCK_WAITS;可查看。


一致性的非锁定读:InnoDB存储引擎通过行多版本控制的方式来读取当前执行时间数据库中行的数据。如果读取的行正在执行Delete、update操作,这时读取操作不会因此而会等待行上锁的释放,相反,InnoDB存储引擎会去读取行的一个快照数据。快照数据是指该行之前版本的数据,该实现是通过Undo段来实现。而Undo用来事务中回滚数据,因此快照本身是没有额外开销的。此外,快照数据是不需要上锁的,因为没有必要对历史的数据进行修改。一个行可能有不止一个快照数据,所以称这种技术为行多版本技术。由此带来并发控制,称之为多版本并发控制(Multi VersionConcurrency Control, MVCC)。

事务的隔离级别:Read uncommitted、Read committed、Repeatable read、serializable。在Read Committed和Repeatable Read下,InnoDB存储引擎使用非锁定一致性读。然而,对于快照的定义却不同。在Read Committed事务隔离级别下,对于快照数据,非一致性读总是读取被锁定行的最新一份快照数据。在Repeatable事务隔离级别下,对于快照数据,非一致性读总是读取事务开始时的行数据版本。

锁的算法:

Record Lock:单行记录上的锁

Gap Lock:间隙锁,锁定一个范围,但不包含记录本身

Next-Key Lock:Gap Lock + Record Lock,锁定一个范围,并且锁定记录本身。

锁的问题:

丢失更新:经典的数据库问题,当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。   

例:

事务A和事务B同时修改某行的值,

1.事务A将数值改为1并提交

2.事务B将数值改为2并提交。

这时数据的值为2,事务A所做的更新将会丢失。

解决办法:事务并行变串行操作,对更新操作加排他锁。

脏读:一个事务读到另一个事务未提交的更新数据,即读到脏数据。
例:
1.Mary的原工资为1000, 财务人员将Mary的工资改为了8000(但未提交事务)
2.Mary读取自己的工资 ,发现自己的工资变为了8000,欢天喜地!
3.而财务发现操作有误,回滚了事务,Mary的工资又变为了1000, 像这样,Mary记取的工资数8000是一个脏数据。
解决办法:脏读只有在事务隔离级别是Read Uncommitted的情况下才会出现,innoDB默认隔离级别是Repeatable Read,所以生产环境下不会出现脏读。

不可重复读:在同一个事务中,多次读取同一数据,返回的结果有所不同。换句话说就是,后续读取可以读到另一个事务已提交的更新数据。相反"可重复读"在同一事务多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一事务已提交的更新数据。脏读和不可重复读的主要区别在于,脏读是读到未提交的数据,不可重复读是读到已提交的数据。
例:
1.在事务1中,Mary 读取了自己的工资为1000,操作并没有完成
2.在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.
3.在事务1中,Mary 再次读取自己的工资时,工资变为了2000
解决办法:读到已提交的数据,一般数据库是可接受的,因此事务隔离级别一般设为Read Committed。Mysql InnoDB通过Next-Key Lock算法避免不可重复读,默认隔离级别为Repeatable Read。


五、Innodb中的事务

事务的四个特性:原子性、一致性、隔离性、持久性

隔离性通过锁实现,原子性、一致性、持久性通过数据库的redo和undo来完成。

重做日志记录了事务的行为,通过redo实现,保证了事务的完整性,但事务有时还需要撤销,这时就需要产生undo。undo和redo正好相反,对于数据库进行修改时,数据库不但会产生redo,而且还会产生一定的undo,即使执行的事务或语句由于某种原因失败了,或者如果用一条rollback语句请求回滚,就可以用这些undo信息将数据回滚到修改之前的样子。与redo不同的是,redo存放在重做日志文件中,undo存放在数据库内部的一个特殊段(segment)中,这称为undo段(undo segment),undo段位于共享表空间内。还有一点重要的是,undo记录的是与事务操作相反的逻辑操作,如insert undo 记录一个delete,所以undo只是逻辑地将数据库恢复成事务开始前的样子。如:insert 10万行的数据,可能导致表空间增大,回滚后,表空间不会减小回去。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: