浅谈MySQL

今天来系统地学习一下MySQL,主要有以下知识点:

  • InnoDB引擎
  • 事务
  • 索引

InnoDB引擎

MyISAM和InnoDB区别

MyISAM是MySQL的默认数据库引擎(5.5版之前)。虽然性能极佳,而且提供了大量的特性,包括全文索引、压缩、空间函数等,但MyISAM不支持事务和行级锁,而且最大的缺陷就是崩溃后无法安全恢复。不过,5.5版本之后,MySQL引入了InnoDB(事务性数据库引擎),MySQL 5.5版本后默认的存储引擎为InnoDB。

两者的对比:

  1. 是否支持行级锁: MyISAM 只有表级锁(table-level locking),而InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
  2. 是否支持事务和崩溃后的安全恢复: MyISAM强调的是性能,每次查询具有原子性,其执行速度比InnoDB类型更快,但是不提供事务支持。但是InnoDB提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
  3. 是否支持外键:MyISAM不支持,而InnoDB支持。
  4. 是否支持MVCC:仅 InnoDB 支持。应对高并发事务, MVCC比单纯的加锁更高效;MVCC只在READ COMMITTEDREPEATABLE READ两个隔离级别下工作;MVCC可以使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统一。

事务

什么是事务?

事务是逻辑上的一组操作,要么都执行,要么都不执行。

事务的特性(ACID)

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

并发事务带来的问题

在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。

  • 脏读(Dirty read):当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
  • 更新丢失(Lost to modify):指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为更新丢失。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的更新被丢失。
  • 不可重复读(Unrepeatableread):指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
  • 幻读(Phantom read):幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

不可重复读和幻读区别:

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

事务隔离级别

SQL 标准定义了四个隔离级别:

  • READ-UNCOMMITTED(读未提交):最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • READ-COMMITTED(读已提交):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • REPEATABLE-READ(可重复读):对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
  • SERIALIZABLE(可串行化):最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读

MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重复读)

这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。

索引

基础知识

Mysql的基本存储结构是页(记录都存在页里边)

各个数据页可以组成一个双向链表
而每个数据页中的记录又可以组成一个单向链表

每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录

以其他列(非主键)作为搜索条件:只能从最小记录开始依次遍历单链表中的每条记录。
所以说,如果我们写 select * from use where username='wugui' 这样没有进行任何优化的sql语句,默认会这样做:

  1. 定位到记录所在的页
  2. 需要遍历双向链表,找到所在的页
  3. 从所在的页内中查找相应的记录

由于不是根据主键查询,只能遍历所在页的单链表了。
很明显,在数据量很大的情况下这样查找会很慢!

原理

索引底层结构就是B+树,B+树是为磁盘或其它直接存储辅助设备设计的一种平衡二叉树。

那么索引是如何加快检索速度的呢?

总结

  • 非叶子结点不保存数据,只用来索引,所有数据都保存在叶子节点。
  • 所有的数据都在叶子节点,各叶子结点形成了一个有序的双向链表。为什么要有序呢?其实是为了范围查询。比如说 select * from Table where id > 1 and id < 100; 当找到1后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。
  • 一般情况下,数据库的B+树的高度一般在2~4层,这就是说找到某一键值的行记录最多需要2到4次磁盘IO,相当于0.02到0.04s。

哈希索引

除了B+树之外,还有一种常见的是哈希索引

哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快

哈希索引有好几个局限(根据他本质的原理可得):

  • 哈希索引也没办法利用索引完成排序
  • 不支持最左匹配原则
  • 在有大量重复键值情况下,哈希索引的效率也是极低的---->哈希碰撞问题。
  • 不支持范围查询

聚集和非聚集索引

简单概括

  • 聚集索引就是以主键创建的索引
  • 非聚集索引就是以非主键创建的索引

区别

  • 聚集索引在叶子节点存储的是表中的数据
  • 非聚集索引在叶子节点存储的是主键和索引列
  • 使用非聚集索引查询出数据时,拿到叶子上的主键再去查到想要查找的数据

非聚集索引在建立的时候也未必是单列的,可以多个列来创建索引。

覆盖索引

在创建多列索引中也涉及到了一种特殊的索引-->覆盖索引

我们前面知道了,如果不是聚集索引,叶子节点存储的是主键+列值
最终还是要“回表”,也就是要通过主键再查找一次。这样就会比较慢
覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!

比如说:
现在我创建了索引 (username,age),在查询数据的时候: select username,age from user where username = 'wugui' and age = 20。

很明显地知道,我们上边的查询是走索引的,并且,要查询出的列在叶子节点都存在!所以,就不用回表了~

所以,能使用覆盖索引就尽量使用吧~

最左前缀原则

MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city),而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下:

select * from user where name=xx and city=xx ; //可以命中索引
select * from user where name=xx ; // 可以命中索引
select * from user where city=xx ; // 无法命中索引            

这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如city= xx and name =xx,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的。

注意避免冗余索引

冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗余索引如(name,city )(name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。

MyISAM和InnoDB存储引擎使用的锁:

  • MyISAM采用表级锁(table-level locking)。
  • InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁

表级锁和行级锁

  • 表级锁:MySQL中锁定粒度最大的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和 InnoDB引擎都支持表级锁。
  • 行级锁:MySQL中锁定粒度最小的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。

共享锁和排它锁

InnoDB实现了以下两种类型的行锁。

  • 共享锁(S锁):允许一个事务去读一行,阻止其他事务获得相同数据的排他锁。S锁是共享的,多个客户可以同时读取同一个数据,但不允许其他客户修改。
  • 排他锁(X锁):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。X锁是排他的,写锁会阻塞其他的写锁和读锁。

在默认的情况下, select是不加任何行锁的~事务可以通过以下语句显示给记录集加共享锁或排他锁。

共享锁(S): SELECT * FROM table_name WHERE...LOCK IN SHARE MODE

排他锁(X): SELECT * FROM table_name WHERE...FOR UPDATE

乐观锁和悲观锁

  • 乐观锁是一种思想,具体实现是,表中有一个版本字段,第一次读的时候,获取到这个字段。处理完业务逻辑开始更新的时候,需要再次查看该字段的值是否和第一次的一样。如果一样更新,反之拒绝。之所以叫乐观,因为这个模式没有从数据库加锁,等到更新的时候再判断是否可以更新。
  • 悲观锁是数据库层面加锁,都会阻塞去等待锁。

我们使用悲观锁的话其实很简单(手动加行锁就行了):

select * from xxxx for update

在select 语句后边加了 for update 相当于加了排它锁(写锁),加了写锁以后,其他的事务就不能对它修改了!需要等待当前事务修改完之后才可以修改.

乐观锁的话我们可以在数据库方面添加一个version字段,当有事务修改了数据时将version加一,当其它事务想要提交时发现version已被修改,则会取消事务提交。

参考

MySQL教程
数据库两个神器索引和锁

你可能感兴趣的:(mysql)