MySQL架构与历史

137 阅读7分钟

MySQL架构与历史

1.1 MySQL架构

1674983155772.png

1.2 并发与控制

MySQL的并发控制包括两个层面:服务器层和存储引擎层

  • 读写锁(也叫共享锁和排他锁)

  • 锁粒度

    • 表锁(一般是服务器层控制,例如alter table语句)
    • 行级锁(服务器层完全不了解,行级锁由各自的存储引擎层实现)

1.3 事务

事务就是一组原子性的SQL查询,或者说一个独立的工作单元。

  • 事务的ACID概念:

    • 原子性(atomicity):事务不可分割,在执行过程中,要么全部成功,要么全部失败;
    • 一致性(consistency):数据库总是从一个一致性状态转换到另外一个一致性的状态;
    • 隔离性(isolation):通常来说,一个事务所做的修改在最终提交以前,对其他的事务是不可见的;
    • 持久性(durability):一旦事务提交,则其所做的修改就会永久保存到数据库中;
  • 隔离级别:

    隔离性其实比想象的要复杂。在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事物内和事物间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。

    • READ UNCOMMITTED(未提交读):一个事务可以读到另一个事务未提交修改的数据,这也被称为脏读(Dirty Read)
    • READ COMMITTED(已提交读):一个事务开始时,只能“看见”已提交事物所做的修改。因为两次查询可能会得到不一样的结果,所以会产生不可重复读问题。例如a事务第一次读的数据是1,此时b事务将值修改为a+1,此时当a再次去读数据的时候变成了a+1,两次读取的数据不一样。
    • REPEATABLE READ(可重复读):该级别保证了在同一个事务中多次读取同样记录的结果是一致的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读的问题。例如a事务第一次读取的范围数据1,3,此时b事务插入了一条数据2,此时当a再次去读范围内的数据的时候变成了1,2,3,会产生幻行(Phantom Row)
    • SERIALIZABLE(可串行化):是最高的隔离级别。它通过强制事物串行执行,避免了前面说的幻读问题。
隔离级别脏读可能性不可重复读可能性幻读可能性
READ UNCOMMITTEDYesYesYes
READ COMMITTEDNoYesYes
REPEATABLE READNoNoYes
SERIALIZABLENoNoNo
  • 死锁:死锁是指两个或多个事物在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。如下面两个事务,如果两个事务恰巧都更新了第一条数据,同时也锁定了该行数据,接着每个事务都尝试去执行第二条update语句,却发现该行为已被锁定,然后两个事务都在等对方释放锁,同时又持有对方需要的锁,则陷入死循环。

    • 事物1:

      start transaction;
      update stock_price set close = 45.50 where stock_id = 4 and date = '2002-05-01';
      update stock_price set close = 19.80 where stock_id = 3 and date = '2002-05-02';
      commit;
      
    • 事物2:

      start transaction;
      update stock_price set close = 20.12 where stock_id = 3 and date = '2002-05-02';
      update stock_price set close = 47.20 where stock_id = 4 and date = '2002-05-01';
      commit;
      

    InnoDB存储引擎,能检测到死锁的循环依赖,并立即返回一个错误。还有一种解决方式,就是当查询时间达到锁等待超时的设定后放弃锁请求。InnoDB目前处理死锁的方法是,将持有最少行级排它锁的事务进行回滚(这是相对比较简单的死锁回滚算法)。

  • 事务日志

    事务日志可以帮助提高事务的效率。使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。事务日志持久化后,内存中被修改的数据在后台可以慢慢地回刷到磁盘。目前大多数存储引擎都是这样实现的,我们通常称之为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘。

  • MySQL中的事务

    MySQL事务型存储引擎有两种:InnoDB和NDB Cluster。

    • 自动提交(AUTOCOMMIT):mysql默认采用自动提交模式,也就是说,如果不显示地开始一个事务,则每个查询都被当作一个事务执行提交操作,对于非事务型存储引擎,修改autocommit不会有任何影响。一些DDL和LOCK TABLES等语句还会强制执行commit提交当前的活动事务。
    • 为每张表选择合适的存储引擎(在同一个事务中使用多种存储引擎是不可靠的)
    • 隐式和显式锁定:通常InnoDB会根据隔离级别在需要的时候自动隐式加锁,另外InnoDB也支持显式锁定(应尽量避免使用)

1.4 多版本并发控制

  • 由于MySQL行级锁并发性能不高,为了提升并发性能,大多数事务型存储引擎一般都实现了多版本并发控制(MVCC),可以认为MVCC是行级锁的一个变种,但是它在很多情况下都避免了加锁操作,因此开销更低

  • 在InnoDB的MVCC中,是通过在每行记录后面保存两个隐藏列来实现的。一个是创建时间,一个是过期时间(或删除时间)。当然存储的不是实际的时间值,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。在RR隔离级别下,MVCC具体操作如下:

    • select:

      InnoDB会根据以下两个条件检查每行记录:

      a、InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。

      b、行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前都未被删除。

      只有复合上述两个条件的记录,才能返回作为查询结果。

    • insert:

      InnoDB为新插入的每一行保存当前系统版本号作为行版本号。

    • delete:

      InnoDB为删除的每一行保存当前系统版本号作为删除标识。

    • update:

      InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。

 优点:大多数读操作都可以不用加锁,使得读操作简单,性能好

 缺点:需要额外的存储空间,需要更多的行检查工作以及一些额外的维护工作。

1.5 MySQL的存储引擎

  • InnoDB
    • MySQL默认引擎,支持事务、行级锁,MVCC
  • MyISAM
    • 不支持事务和行级锁、宕机后无法安全恢复
  • ....