SQL极简教程 · MySQL · MyBatis · JPA 技术笔记 教程 总结MySQLmysql

我认识的MySQL

2018-06-23  本文已影响9人  尘世的鱼

数据库概念

锁分类

锁(按照锁粒度分类)

虽然行级索具有粒度小、并发度高等特点,但是表级锁有时候也是有其适用场景的

  1. 事务更新大表中的大部分数据直接使用表级锁效率更高
  2. 事务比较复杂,使用行级索很可能引起死锁导致回滚

锁(按照读写分类)

表级锁和行级锁可以进一步划分为共享锁(S)和排他锁(X)

读写锁有一定的缺憾,如遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定,所以就有了意向锁。

意向锁解释:

当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是在某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。

InnoDB另外的两个表级锁:

意向锁是表级锁,表示的是一种意向,仅仅表示事务正在读或写某一行记录,在真正加行锁时才会判断是否冲突。意向锁是InnoDB自动加的,不需要用户干预。

IX,IS是表级锁,不会和行级的X,S锁发生冲突,只会和表级的X,S发生冲突。

综上,当一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之如果请求不兼容,则该事物就等待锁释放。

页级锁

锁定粒度介于行级锁和表级锁中间的一种锁。

由于表级锁速度快,但冲突多,行级冲突少,但速度慢。而页级锁进行了折衷,一次锁定相邻的一组记录。开销和加锁时间界于表锁和行锁之间,会出现死锁。锁定粒度界于表锁和行锁之间,并发度一般。

死锁及其解决方法

由于InnoDB的行级锁是针对索引加的锁,不针对数据记录,因此即使访问不同行的记录,如果使用了相同的索引键仍然会出现锁冲突,需要注意的是,在通过以下方式

SELECT ...LOCK IN SHARE MODE;
SELECT ...FOR UPDATE;

使用锁的时候,如果表没有定义任何索引,那么InnoDB会创建一个隐藏的聚簇索引并使用这个索引来加记录锁。

此外,不同于MyISAM总是一次性获得所需的全部锁,InnoDB的锁是逐步获得的,当两个事务都需要获得对方持有的锁,导致双方都在等待,这就产生了死锁。发生死锁后,InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个则可以获取锁完成事务。当然,我们可以自己事先做一些考虑:

缓存查询

实例针对Mysql说明:

my.cnf加入以下配置,重启Mysql开机查询缓存

query_cache_type=1
query_cache_size=600000

Mysql执行以下命令也可以开启查询缓存

set global  query_cache_type=1;
set global  query_cache_size=600000;

如上,开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果。

上面所述的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。因此任何两个在任何字符上有不同的查询都会导致缓存命不中。此外,如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、Mysql库中的系统表,其查询结果也不会被缓存。

当缓存建立后,Mysql的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。

缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁,因此,开启缓存查询要谨慎,尤其对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小,一般来说其大小设置为几十MB比较合适。此外,还可以通过sql_cachesql_no_cache来控制某个查询语句是否需要缓存:

select sql_no_cache count(*) from usr;

事务

事务特性

  1. 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
  2. 一致性: 执行事务前后,数据保持一致;
  3. 隔离性: 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的;
  4. 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

并发事务带来的问题

其中,不可重复读的重点是修改,幻读的重点在于新增或者删除.

事务隔离级别

  1. READ_UNCOMMITTED(未授权读取): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  2. READ_COMMITTED(授权读取): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  3. REPEATABLE_READ(可重复读):对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
  4. SERIALIZABLE(串行): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

MySql

架构设计

architect.jpg

存储引擎

以5.5为例

mysql_engines.png

特点比较

engine_compare.jpg

MyISAM

InnoDB

InnoDB行级锁分类

事务隔离机制的实现基于锁机制并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性

综上:MyISAM更适合读密集的表,而InnoDB更适合写密集的的表。在数据库做主从分离的情况下,可以选择MyISAM作为主库的存储引擎

索引结构

MyISAMInnoDB都使用B+树作为其索引结构

但二者也是有区别的:

  1. InnoDB中,表数据文件是按B+树组织的索引结构,这棵树的叶节点data域保存了完整的数据记录(这种索引叫聚集索引),而MyISAM索引文件和数据文件是分离的(非聚集的),索引文件仅保存数据记录的地址,当定位到具体的数据记录地址后,还需要一次IO才能找到具体的数据记录
  2. InnoDB辅助索引data域存储的是相应记录主键的值,即InnoDB的所有辅助索引都引用主键作为其data域,而MyISAM辅助索引data域存储的是相应记录的地址

综上可知,InnoDB在根据主索引查找数据时,直接找到key所在的节点即可取出数据文件,在根据辅助索引查找数据时,首先需要在key所在的节点的数据文件中找到主键值,再通过主键走一遍主键索引才能找到对应的数据文件(即辅助索引搜索需要检索两遍索引)。当使用InnoDB创建表时,尽量指明主键,当不包含主键时,存储引擎会生成默认的主键索引,其长度为6B。除此之外,不建议使用过长的字段作为主键,因为辅助索引是通过引用主索引找到其真正的数据文件的,过长的主索引会令辅助索引变得过大;也不建议用非单调的字段作为主键,由于InnoDB数据文件是一颗B+树,非单调的主键会造成在插入新记录时数据文件为了维持B+树的特性而频繁的分裂调整,十分低效,建议使用自增值作为主键(这样可使随机insert变为顺序append,不会产生页分裂和碎片,提升写性能)。

事务处理机制

InnoDB事务日志

InnoDB 使用undo,redo log来保证事务原子性、一致性及持久性,同时采用预写日志方式将随机写入变成顺序追加写入,提升事务性能。

下图为事务写操作执行的大致过程,整个过程中只有一次刷盘操作,即事务提交时的redo log的写盘。

tx_proc.png

其实写盘并不一定会立马持久化到磁盘,要看数据库配置,默认情况下Innodb_flush_log_at_trx_commit=1,即一次redo log写盘操作会立即写到磁盘中,是最保险的方案。

InnoDB中多个事务共享一个redo log buffer, 写盘时,会将当前buffer中的多个事务日志持久化,而不管事务有没有commit,而且并不是只有事务commit才会触发redo log写盘,其它操作也会触发redo log写盘操作如:

MVCC

MVCC (Multi-Version Concurrency Control)多版本并发控制协议,将读操作分成两类:快照读当前读读取的是记录的最新版本,会对返回的记录加上锁,确保其他事务不能并发修改

快照读是通过undo log来实现多个版本的控制。如下图,每个数据行:row_id 为行id,trx_id表示最近修改的事务id,db_roll_ptr为指向undo segmentundo log的指针。快照读时,比较当前事务id与trx_id的关系,如果trx_id 小于事务id,则该条数据对当前事务可见,反之不可见,不可见时再通过db_roll_ptr查找历史版本记录,取出可见的最近的历史记录。undo log 的链路不会很深,后台purge线程定期清除无用的历史版本(在没有活动事务依赖时,undo log即可被删除)。

undo_log.jpg

数据恢复机制

数据恢复主要分为以下几个步骤:

  1. 首先检查最近的Checkpoint,在RedoLog中将Checkpoint起始到目前的已提交状态的行数据提交到盘,并将回滚状态的通过在undolog日志中查找相应的行数据恢复
  2. 将未提交或者为回滚的则通过在undolog日志中查找相应的行数据恢复

表优化

当单表记录数过大时,数据库的CRUD性能会明显下降,所以有以下方案来解决。

基础方案

除了以上这些,还有下面两块比较关键的技术。

分表

分表是指数据表列的拆分,把一张列比较多的表拆分为多张表。

分表可以使行数据变小,在查询时减少读取的Block数,减少I/O次数;还可以可以简化表的结构,易于维护。

但是主键会出现冗余,需要管理冗余列,并会引起Join操作,当然可以通过在应用层进行Join来解决,并且会让事务变得更加复杂。

分库

保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 分库(水平拆分)可以支撑非常大的数据量。

分库能够支持非常大的数据量存储,应用端改造也少,但分片事务难以解决,跨界点Join性能较差,逻辑复杂。

PS引用:
http://blog.codinglabs.org/articles/theory-of-mysql-index.html
https://segmentfault.com/a/1190000006158186

写的有点糙,欢迎大家批评指正>-<

上一篇 下一篇

猜你喜欢

热点阅读