MySQL基础架构

MySQL基础架构

简单来说MySQL主要分为Server层和存储引擎层。Server层主要包括连接器、查询缓存、分析器、优化器和执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog。

存储引擎层主要负责数据的存储和读取,采用可以替换的插件式架构,支持InnoDB、MyISAM和Memory等多个存储引擎,其中InnoDB引擎有自有的日志模块redolog。从MySQL5.5.5版本开始被当做默认的存储引擎。

查询语句的执行流程是:权限校验->查询缓存->分析器->优化器->权限校验->执行器->存储引擎

更新语句的执行流程是:查询->分析器->权限校验->执行器->存储引擎->redolog(prepare状态)->binlog->redo(commit状态)

触发器

一触即发 当表上出现特定的事件时, 触发该程序执行update/delete/insert。

触发器对性能有损耗,应该谨慎使用对于事务表,

触发器执行失败则整个语句回滚

Row格式的主从复制,触发器不会再从库上执行

存储过程

存储在数据库端的一组SQL语句集,用户可以通过存储过程名和传参多次调用的程序模块。

特点:

使用灵活,可以完成复杂的业务逻辑提高数据安全性,

屏蔽应用对表的操作,易于审计

减少网络传输

缺点:

提高了代码维护的复杂度

索引

为什么使用索引

  • 索引可以加速查询的效率。
  • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
  • 帮助服务器避免排序和临时表。
  • 将随机IO变为顺序IO
  • 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。

索引的代价

  • 对表中数据增删改时,索引需要动态维护(需要维持平衡),因此降低了数据的维护速度。
  • 索引要占用物理空间。
  • 创建和维护索引需要耗费时间,随着数据量增加而增加

索引的原理

MySQL有哈希索引和BTree索引两种索引结构

哈希索引的底层数据结构是哈希表,在绝大数需求为单条记录查询时,可以选择哈希索引。Hash索引不支持顺序和范围查询是它最大的缺点。

BTree索引的底层数据结构是B+树。

B+树结构的优点

  1. B+树是平衡多路查找树,检索的时间复杂度是O(logn)。
  2. 数据库充分利用了磁盘块的原理(磁盘数据存储是采用块的形式存储的,每个块的大小为4K,每次IO进行数据读取时,同一个磁盘块的数据可以一次性读取出来)把节点大小限制和充分使用在磁盘块大小范围;把树的节点关键字增多后树的层级比原来的二叉树少了,减少数据查找的次数和复杂度。
  3. B+树的非叶子节点不保存关键字记录的指针,只保存数据索引,非叶子节点可以保存的关键字大大增加,树的层级更少,所以查询数据更快。
  4. B+所有关键字数据地址都存在叶子节点上,所以每次查找的次数都相同所以查询速度要比B树更稳定;
  5. B+树的叶子节点的主键数据从小到大有序排列,除叶子节点外的所有节点的关键字,都在它的下一级子树中同样存在,最后所有数据都存储在叶子节点中。左边叶子节点结尾数据都会保存右边叶子节点开始数据的指针,B+树天然具备排序功能,B+树所有的叶子节点数据构成了一个有序链表,在查询大小区间的数据时候更方便,数据紧密性很高,缓存的命中率也会比B树高。
  6. B+树全节点遍历更快,B+树遍历整棵树只需要遍历所有的叶子节点即可,而不需要像B树一样需要对每一层进行遍历,这有利于数据库做全表扫描。

非聚簇索引是指MyISAM存储引擎的BTree索引的B+树的叶子节点的data域存放的是数据记录的地址。

聚簇索引是指InnoDB存储引擎的主键索引的B+树的叶子节点的data域存放的是完整的数据记录。

辅助索引是指InnDB存储引擎的索引的B+树的叶子节点的data域存放的是主键值。唯一索引,普通索引,前缀索引和全文索引等都属于二级索引。

覆盖索引是指一个索引包含所有需要查询的字段的值。(不需要回表操作)

联合索引是指多个字段联合形成的索引,使用时有最左前缀匹配的规则,并且联合索引只能用于查找key是否存在(相等),遇到范围查询(>、,

InnoDB根据索引查询的流程:在根据主键索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主键索引。

索引实践

通过Explain命令可以查看索引是否生效,Explain命令显示的字段的大概解释如下:

type:表示MySQL在表中找到所需行的方式

最为常见的扫描方式有:system > const > eq_ref > ref > range > index > ALL

  • system:系统表,少量数据,往往不需要进行磁盘IO;
  • const:常量连接;命中主键(primary key)或者唯一(unique)索引,并且被连接的部分是一个常量(const)值。
  • eq_ref:主键索引(primary key)或者非空唯一索引(unique not null)等值扫描;对于前表的每一行(row),后表只有一行被扫描。
  • ref:非主键非唯一索引等值扫描;对于前表的每一行(row),后表可能有多于一行的数据被扫描。
  • range:范围扫描;它是索引上的范围查询,它会在索引上扫描特定范围内的值。
  • index:索引树扫描;需要扫描索引上的全部数据。
  • ALL:全表扫描(full table scan);对于前表的每一行(row),后表都要被全表扫描。

possible_keys: 此次查询中可能选用的索引

key: 此次查询中确切使用到的索引

key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度

ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值

rows:表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数

extra: 该列包含MySQL解决查询的详细信息


查询操作非常频繁的字段,可以考虑建立索引。

对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。

被作为WHERE条件查询的字段,应该被考虑建立索引。

被频繁更新的字段应该慎重建立索引。

通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

使用select * 进行范围查询普通索引可能不会生效。

“列类型”与“where值类型”不符,不能命中索引,会导致全表扫描(full table scan)。

相join的两个表的字符编码不同,不能命中索引,会导致笛卡尔积的循环计算(nested loop)。

不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。

mysql在使用不等于(!=或者<>)、is null和is not null的时候可能无法使用索引会导致全表扫描

使用覆盖索引可以解决like以通配符开头(’%abc…’)mysql索引失效会变成全表扫描的操作的问题。

尽量选择区分度高的列作为索引,区分度的公式是 COUNT(DISTINCT col) / COUNT(*)。表示字段不重复的比率,比率越大我们扫描的记录数就越少。

字符串索引,可以创建前缀索引,前缀索引仅限于字符串类型,较普通索引会占用更小的空间,所以可以考虑使用前缀索引。

注意避免冗余索引,尽可能的扩展已有的索引,不要新建立索引。比如表中已经有了a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。

不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。顺序主键也有缺点:对于高并发工作负载,在InnoDB中按主键顺序插入可能会造成明显的争用。主键的上界会成为“热点”。因为所有的插入都发生在这里,所以并发插入可能导致间隙锁竞争。另一个热点可能是auto_increment锁机制;如果遇到这个问题,则可能需要考虑重新设计表或者应用,比如应用层面生成单调递增的主键ID,插表不使用auto_increment机制,或者更改innodb_autonc_lock_mode配置。

大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。

事务

关系数据库中,事务(Transaction),指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务是恢复和并发控制的基本单位。一个事务可以是一条SQL语句,一组SQL语句或整个程序。事务是逻辑上的一组操作,将一组操作在逻辑上抽象成一个操作,要么都执行,要么都不执行。

ACID

原子性(Atomicity):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;

一致性(Consistency):执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

隔离性(Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(Durability): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

事务状态

每一个事务都对应着一个或多个数据库操作,根据这些操作执行的不同阶段,我们可以把事务划分成几个状态:

活动的(active):事务对应的数据库操作正在执行过程中时,我们就说该事务处在活动的状态。

部分提交的(partially committed):当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。刷盘拓展:事务操作不会直接更改物理磁盘,而是先修改内存中的Buffer Pool中的数据,为什么呢?每次刷慢,改动的数据页不连续,随机IO多。记录在Redolog,(Undolog作用),事务提交时,Redolog刷到磁盘。

失败的(failed):当事务处在活动的或者部分提交的状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。

中止的(aborted):如果事务执行了半截而变为失败的状态,就要撤销失败事务对当前数据库造成的影响。这个撤销的过程叫做回滚。当回滚操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事务处在了中止的状态。

提交的(commited):当一个处在部分提交的状态的事务将修改过的数据都同步到磁盘上之后,该事务处在了提交的状态。

事务状态变化

并发事务带来哪些问题?

脏读(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标准,在标准中设立了4个隔离级别:

  • READ UNCOMMITTED:读未提交(读取记录的最新版本)最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • READ COMMITTED:读已提交(每次读取前生成一个ReadView),允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • REPEATABLE READ:可重复读(第一次读取前生成一个ReadView),对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读(幻读?表象上解决了幻读,物理上没有解决幻读,需要forupdate加锁来从根本上解决幻读)。MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。Next-Key Lock 锁算法,因此可以避免幻读的产生。
  • SERIALIZABLE:可串行化。

MVCC原理

MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。

READ COMMITTD、REPEATABLE READ这两个隔离级别的一个很大不同就是:生成ReadView的时机不同,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView。

对于使用InnoDB存储引擎的表来说,它的主键索引记录中都包含两个必要的隐藏列:

  • trx_id:每次一个事务对某条主键索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列。
  • roll_pointer:每次对某条主键索引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

​实际上insert undo只在事务回滚时起作用,当事务提交后,该类型的undo日志就没用了,它占用的Undo Log Segment也会被系统回收(该undo日志占用的Undo页面链表要么被重用,要么被释放)。虽然真正的insert undo日志占用的存储空间被释放了,但是roll_pointer的值并不会被清除,roll_pointer属性占用7个字节,第一个比特位就标记着它指向的undo日志的类型,如果该比特位的值为1时(undo日志也分类型,这里只提一下我们举例子的undo日志类型),就代表着它指向的undo日志类型为insert undo。

每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表。

对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id。

ReadView

只读事务的事务id默认为0;insert, delete, update才会为事务分配事务id。

ReadView中主要包含4个比较重要的内容:

  • m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
  • min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。
  • max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。 这里说一下max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。
  • creator_trx_id:表示生成该ReadView的事务的事务id。

有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:

  • 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值大于或等于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
  • 如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。

Spring事务相关

@Transactional事务不要滥用。事务会影响数据库的QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。

事务传播属性(Propagation)

REQUIRED:(默认属性)如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。 被设置成这个级别时,会为每一个被调用的方法创建一个逻辑事务域。如果前面的方法已经创建了事务,那么后面的方法支持当前的事务,如果当前没有事务会重新建立事务。

MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。

NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。

NESTED:支持当前事务,新增Savepoint点,与当前事务同步提交或回滚。 嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

连接池

为什么需要连接池?

当并发量很低的时候,连接可以临时建立,但当服务吞吐量达到几百、几千的时候,建立连接connect和销毁连接close就会成为瓶颈,此时该如何优化呢?

(1)当服务启动的时候,先建立好若干连接Array[DBClientConnection];

(2)当请求到达的时候,再从Array中取出一个,执行下游操作,执行完放回;

从而避免反复的建立和销毁连接,抵消每次获取资源的消耗,以提升性能。

除了初始化资源,池化设计还包括如下这些特征:池子的初始值、池子的活跃值、池子的最大值等,这些特征可以直接映射到java线程池和数据库连接池的成员属性中。

数据库连接本质就是一个 socket 的连接。数据库服务端还要维护一些缓存和用户权限信息之类的 所以占用了一些内存。我们可以把数据库连接池是看做是维护的数据库连接的缓存,以便将来需要对数据库的请求时可以重用这些连接。为每个用户打开和维护数据库连接,尤其是对动态数据库驱动的网站应用程序的请求,既昂贵又浪费资源。在连接池中,创建连接后,将其放置在池中,并再次使用它,因此不必建立新的连接。如果使用了所有连接,则会建立一个新连接并将其添加到池中。连接池还减少了用户必须等待建立与数据库的连接的时间。

存储引擎

整体架构

Page是整个InnoDB存储的最基本构件,也是InnoDB磁盘管理的最小单位,与数据库相关的所有内容都存储在这种Page结构里。Page分为几种类型,常见的页类型有数据页(B-tree Node),Undo页(Undo Log Page),系统页(System Page)和事务数据页(Transaction System Page)等。单个Page的大小是16K,每个Page使用一个32位的int值来唯一标识,这也正好对应InnoDB最大64TB的存储容量(16Kib * 2^32 = 64Tib)。磁盘数据存储是采用块的形式存储的,每个块的大小为4K,每次IO进行数据读取时,同一个磁盘块的数据可以一次性读取出来。

以下内容整理来自架构师之路公众号

InnoDB整体架构分为三层:

内存结构(In-Memory Structure),这一层在MySQL服务进程内;

OS Cache,这一层属于内核态内存;

磁盘结构(On-Disk Structure),这一层在文件系统上;

InnoDB整体架构

InnoDB内存结构包含四大核心组件,分别是:

缓冲池(Buffer Pool)

MySQL数据存储包含内存与磁盘两个部分;内存缓冲池(buffer pool)以页为单位,缓存最热的数据页(data page)与索引页(index page);InnoDB以变种LRU算法管理缓冲池,并能够解决“预读失效”与“缓冲池污染”的问题;

缓冲池缓存表数据与索引数据,把磁盘上的数据加载到缓冲池,避免每次访问都进行磁盘IO,起到加速访问的作用。

写缓冲(Change Buffer)

目的是提升InnoDB性能,加速写请求,避免每次写入都进行磁盘IO。

自适应哈希索引(Adaptive Hash Index)

目的是提升InnoDB性能,加速读请求,减少索引查询的寻路路径。

日志缓冲(Log Buffer)

目的是提升InnoDB性能,极大优化redo日志性能,并提供了高并发与强一致性的折衷方案。

事务提交后,必须将事务对数据页的修改刷(fsync)到磁盘上,才能保证事务的ACID特性。刷盘是一个随机写操作,随机写性能较低,如果每次事务提交都刷盘,会极大影响数据库的性能。

优化的方法是先写redo log(write log first),将随机写优化为顺序写;将每次写优化为批量写。

redo log是为了保证已提交事务的ACID特性,同时能够提高数据库性能的技术。

redo log是一种顺序写,它有三层架构:MySQL应用层:Log Buffer,OS内核层:OS cache和OS文件:log file。

事务提交时,将redo log写入Log Buffer,就会认为事务提交成功;如果写入Log Buffer的数据,write入OS cache之前,数据库崩溃,就会出现数据丢失;如果写入OS cache的数据,fsync入磁盘之前,操作系统奔溃,也可能出现数据丢失;

策略一:最佳性能(innodb_flush_log_at_trx_commit=0)

每隔一秒,才将Log Buffer中的数据批量write入OS cache,同时MySQL主动fsync。

这种策略,如果数据库奔溃,有一秒的数据丢失。

策略二:强一致(innodb_flush_log_at_trx_commit=1)

每次事务提交,都将Log Buffer中的数据write入OS cache,同时MySQL主动fsync。

这种策略,是InnoDB的默认配置,为的是保证事务ACID特性。

策略三:折衷(innodb_flush_log_at_trx_commit=2)

每次事务提交,都将Log Buffer中的数据write入OS cache;

每隔一秒,MySQL主动将OS cache中的数据批量fsync。 这是高并发业务,行业内的最佳实践。

锁相关

InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁。

表级锁: MySQL中锁定粒度最大的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和 InnoDB引擎都支持表级锁。写时,要加写锁:如果表没有锁,对表加写锁;否则,入写锁队列;读时,要加读锁:如果表没有写锁,对表加读锁;否则,入读锁队列;表锁释放时:如果写锁队列和读锁队列里都有锁,写有更高的优先级,即写锁队列先出列。这么做的原因是,如果有“大查询”,可能会导致写锁被批量“饿死”,而写锁往往释放很快。

自增锁是一种特殊的表级别锁(table-level lock),专门针对事务插入AUTO_INCREMENT类型的列。如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。

行级锁: MySQL中锁定粒度最小的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。

InnoDB支持的行级锁,包括如下几种。

  • Record lock(记录锁):对索引项加锁,锁定符合条件的行。其他事务不能修改和删除加锁项。
  • Gap lock(间隙锁):对索引项之间的“间隙”加锁,锁定记录的范围(对第一条记录前的间隙或最后一条将记录后的间隙加锁),不包含索引项本身。其他事务不能在锁范围内插入数据,这样就防止了别的事务新增幻影行。
  • Next-key lock(临键锁):锁定索引项本身和索引范围。即Record Lock和Gap Lock的结合。可解决幻读问题。InnoDB对于行的查询使用Next-key lock。当查询的索引含有唯一属性时,将next-key lock降级为record key。

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

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

意向共享锁(IS): 表示事务准备给数据行记入共享锁,事务在一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX): 表示事务准备给数据行加入排他锁,事务在一个数据行加排他锁前必须先取得该表的IX锁。

InnoDB的行级锁是基于索引实现的,如果查询语句为命中任何索引,那么InnoDB会使用表级锁. 此外,InnoDB的行级锁是针对索引加的锁,不针对数据记录,因此即使访问不同行的记录,如果使用了相同的索引键仍然会出现锁冲突,还需要注意的是,在通过

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

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

导致双方都在等待,这就产生了死锁。

发生死锁后,InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个则可以获取锁完成事务,我们可以采取以上方式避免死锁:

通过表级锁来减少死锁产生的概率;

多个程序尽量约定以相同的顺序访问表(这也是解决并发理论中哲学家就餐问题的一种思路);

同一个事务尽可能做到一次锁定所需要的所有资源。

通过show engine innodb status; 能够看到很多事务与锁之间的信息,对分析问题十分有帮助。

普通select在读未提交(Read Uncommitted),读提交(Read Committed, RC),可重复读(Repeated Read, RR)这三种事务隔离级别下,普通select使用快照读(snpashot read),不加锁,并发非常高;在串行化(Serializable)这种事务的隔离级别下,普通select会升级为select ... in share mode;

加锁select主要是指:select ... for update,select ... in share mode。如果,在唯一索引(unique index)上使用唯一的查询条件(unique search condition),会使用记录锁(record lock),而不会封锁记录之间的间隔,即不会使用间隙锁(gap lock)与临键锁(next-key lock);其他的查询条件和索引条件,InnoDB会封锁被扫描的索引范围,并使用间隙锁与临键锁,避免索引范围区间插入记录;

update与delete和加锁select类似,如果在唯一索引上使用唯一的查询条件来update/delete,例如:update t set name=xxx where id=1;也只加记录锁;否则,符合查询条件的索引记录之前,都会加排他临键锁(exclusive next-key lock),来封锁索引记录与之前的区间;尤其需要特殊说明的是,如果update的是聚集索引(clustered index)记录,则对应的普通索引(secondary index)记录也会被隐式加锁,这是由InnoDB索引的实现机制决定的:普通索引存储PK的值,检索普通索引本质上要二次扫描聚集索引。

insert和update与delete不同,它会用排它锁封锁被插入的索引记录,而不会封锁记录之前的范围。同时,会在插入区间加插入意向锁(insert intention lock),但这个并不会真正封锁区间,也不会阻止相同区间的不同KEY插入。

主从复制

实现读写分离

MySQL主备复制原理

  • MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)
  • MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
  • MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据。

mysql5.6:按照库并行复制,建议使用“多库”架构;

mysql5.7:按照GTID并行复制;

MySQL并行复制,缩短主从同步时延的方法,体现着这样的一些架构思想:

  • 多线程是一种常见的缩短执行时间的方法;例如,很多crontab可以用多线程,切分数据,并行执行。
  • 多线程并发分派任务时,必须保证幂等性:MySQL提供了“按照库幂等”,“按照commit_id幂等”两种方式,很值得借鉴;例如,群消息,可以按照group_id幂等;用户消息,可以按照user_id幂等。

canal 工作原理

  • canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
  • MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
  • canal 解析 binary log 对象(原始为 byte 流)

binlog查看

show binary logs;

show binlog events in 'binlog.000039';

mysqlbinlog --start-position=234 --stop-position=507 --base64-output="decode-rows" -v /var/lib/mysql/binlog.000039

在binlog落盘之后,MySQL就会认为事务的持久化已经完成(在这个时刻之后,就算数据库发生了崩溃都可以在重启后正确的恢复该事务)。但是该事务产生的数据变更被别的客户端查询出来还需要在commit全部完成之后。MySQL会在binlog落盘之后会立即将新增的binlog发送给订阅者以尽可能的降低主从延迟。但由于多线程时序等原因,当订阅者在收到该binlog之后立即发起一个查询操作,可能不会查询到任何该事务产生的数据变更(因为此时该事务所处线程可能尚未完成最后的commit步骤)。如果应用需要根据binlog作为一些业务逻辑的触发点,还是需要考虑引入一些延时重试机制或者重新考虑合适的实现架构。

分库分表相关

垂直拆分

根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。

简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。

水平拆分

水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。

  1. 确定一个路由算法,例如hash取模;或者根据时间范围
  2. 将单库中的数据,通过这个路由算法迁移到多库中去,以实现单库数据量的减少;
  3. 通过这个路由算法寻找数据(读);
  4. 通过这个路由算法插入数据(写);

分库后将数据分布到不同的数据库实例(甚至物理机器)上,以达到降低数据量,增强性能的扩容目的。可以使用数据冗余这种反范式设计来满足分库后不同维度的查询需求,为了屏蔽“冗余数据”对服务带来的复杂性,可以优化为线下异步双写法(使用canel)。

水平拆分的实现方案

客户端代理: 分库逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。

中间件代理: 在应用和数据中间加了一个代理层。分库逻辑统一维护在中间件服务中。

水平拆分的主键ID

UUID:不适合作为主键,因为太长了,并且无序不可读,查询效率低。比较适合用于生成唯一的名字的标示比如文件的名字。 数据库自增 id : 两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。这种方式生成的 id 有序,但是需要独立部署数据库实例,成本高,还会有性能瓶颈。 利用 redis 生成 id : 性能比较好,灵活方便,不依赖于数据库。但是,引入了新的组件造成系统更加复杂,可用性降低,编码更加复杂,增加了系统成本。 Twitter的snowflake算法 :第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年),然后是5位data center Id和5位worker Id(10位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)。一共加起来刚好64位,为一个Long型(转换成字符串后长度最多19)。snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。经测试snowflake每秒能够产生26万个ID。 美团的Leaf分布式ID生成系统 :Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据库、Zookeeper等中间件。感觉还不错。美团技术团队的一篇文章:https://tech.meituan.com/2017/04/21/mt-leaf.html 。

性能优化的建议

超 100 万行的批量写 (UPDATE,DELETE,INSERT) 操作,要分批多次进行操作

大批量操作可能会造成严重的主从延迟,主从环境中,大批量操作可能会造成严重的主从延迟,大批量的写操作一般都需要执行一定长的时间, 而只有当主库上执行完成后,才会在其他从库上执行,所以会造成主库与从库长时间的延迟情况

binlog 日志为 row 格式时会产生大量的日志,大批量写操作会产生大量日志,特别是对于 row 格式二进制数据而言,由于在 row 格式中会记录每一行数据的修改,我们一次修改的数据越多,产生的日志量也就会越多,日志的传输和恢复所需要的时间也就越长,这也是造成主从延迟的一个原因

避免产生大事务操作,大批量修改数据,一定是在一个事务中进行的,这就会造成表中大批量数据进行锁定,从而导致大量的阻塞,阻塞会对 MySQL 的性能产生非常大的影响。

特别是长时间的阻塞会占满所有数据库的可用连接,这会使生产环境中的其他应用无法连接到数据库,因此一定要注意大批量写操作要进行分批。

拆分复杂的大 SQL 为多个小 SQL

大 SQL 逻辑上比较复杂,需要占用大量 CPU 进行计算的 SQL,MySQL 中,一个 SQL 只能使用一个 CPU 进行计算,SQL 拆分后可以通过并行执行来提高处理效率。

避免使用子查询,可以把子查询优化为 join 操作

通常子查询在 in 子句中,且子查询中为简单 SQL(不包含 union、group by、order by、limit 从句) 时,才可以把子查询转化为关联查询进行优化。子查询性能差的原因,子查询的结果集无法使用索引,通常子查询的结果集会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响。特别是对于返回结果集比较大的子查询,其对查询性能的影响也就越大。由于子查询会产生大量的临时表也没有索引,所以会消耗过多的 CPU 和 IO 资源,产生大量的慢查询。

参考链接

  1. http://www.jiangxinlingdu.com/mysql/2019/06/07/binlog.html
  2. https://www.jiqizhixin.com/articles/2018-12-05-14
  3. https://blog.csdn.net/hao_yunfeng/article/details/82392261
  4. https://www.jianshu.com/p/5dd5993f981b
  5. https://www.bilibili.com/video/av59851676?p=2
  6. https://blog.csdn.net/csdnlijingran/article/details/102309593
  7. https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/database/%E4%B8%80%E6%9D%A1sql%E8%AF%AD%E5%8F%A5%E5%9C%A8mysql%E4%B8%AD%E5%A6%82%E4%BD%95%E6%89%A7%E8%A1%8C%E7%9A%84.md
  8. https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/database/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B4%A2%E5%BC%95.md
  9. 《MySQL 实战45讲》
  10. https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/database/MySQL%20Index.md
  11. https://www.jianshu.com/p/54c6d5db4fe6
  12. https://zhuanlan.zhihu.com/p/27700617
  13. https://blog.csdn.net/u011240877/article/details/80490663
  14. https://blog.csdn.net/lisuyibmd/article/details/53004848
  15. https://juejin.im/post/5b55b842f265da0f9e589e79
  16. https://blog.csdn.net/qq_25188255/article/details/81316498
  17. MySql事务杂谈
  18. https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/database/MySQL.md
  19. https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485185&idx=1&sn=66ef08b4ab6af5757792223a83fc0d45&chksm=cea248caf9d5c1dc72ec8a281ec16aa3ec3e8066dbb252e27362438a26c33fbe842b0e0adf47&token=79317275&lang=zh_CN#rd
  20. https://blog.csdn.net/qq_34337272/article/details/80611486
  21. 架构师之路公众号
  22. https://segmentfault.com/a/1190000006158186
  23. https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485117&idx=1&sn=92361755b7c3de488b415ec4c5f46d73&chksm=cea24976f9d5c060babe50c3747616cce63df5d50947903a262704988143c2eeb4069ae45420&token=79317275&lang=zh_CN#rd

mysql 给几个主键值 批量校验是否存在_MySQL基础知识整理相关推荐

  1. mysql全套基础知识_Mysql基础知识整理

    MySQL的查询过程 (一条sql语句在MySQL中如何执行): 客户端请求 ---> 连接器(验证用户身份,给予权限) ---> 查询缓存(存在缓存则直接返回,不存在则执行后续操作) - ...

  2. mysql插入数据返回主键值_Mysql千万级别数据批量插入只需简单三步!

    第一步:配置my.ini文件 文件中配置 bulk_insert_buffer_size=120M 或者更大 将insert语句的长度设为最大. Max_allowed_packet=1M Net_b ...

  3. mysql插入数据返回主键值_Mysql插入记录后返回该记录ID

    最近和Sobin在做一个精品课程的项目,因为用到一个固定的id作为表间关联,所以在前一个表插入数据后要把插入数据生成的自增id传递给下一个表.研究了一番决定使用Mysql提供了一个LAST_INSER ...

  4. mysql基础知识整理_mysql基础知识整理(一)

    一.数据库基本操作 登录: 开启数据库服务,在cmd中输入指令 mysql -u用户名 -p密码 3退出: 在cmd中输入exit/quit; 启动服务: net start 服务名 停止服务:net ...

  5. mysql基础知识整理_MYSQL基础知识整理

    目录 1.客户端命令 2.服务器端命令 3.常用数据类型 3.1.数值型 3.2.字符型 3.3.日期时间型 3.4.布尔型 4.mysql的执行方式 5.用户管理 1.客户端命令 客户端命令不需要以 ...

  6. datatable如何生成级联数据_通过源码分析Mybatis是如何返回数据库生成的自增主键值?...

    在Mybatis中,执行insert操作时,如果我们希望返回数据库生成的自增主键值,那么就需要使用到KeyGenerator对象. 需要注意的是,KeyGenerator的作用,是返回数据库生成的自增 ...

  7. mybatis mysql usegeneratedkeys_mybatis中useGeneratedKeys用法--插入数据库后获取主键值

    前言:今天无意在mapper文件中看到useGeneratedKeys这个词,好奇就查了下,发现能解决我之前插入有外键表数据时,这个外键获取繁琐的问题,于是学习敲DEMO记录 在项目中经常需要获取到插 ...

  8. MYSQL主键值加1

    设置MYSQL主键值加1 DEMO数据库数据 sql: update su set id=id+1 where id <> 1 ORDER BY id desc; 注意: ORDER BY ...

  9. 为什么 MySQL 的自增主键不单调也不连续

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 转自:真没什么逻辑/Draveness 当我们在使用关系型数据库时 ...

最新文章

  1. Nature:科学家首次实现肉眼可见的量子纠缠
  2. mozilla js 引擎_Mozilla的内容拦截器,新JavaScript引擎以及更多开源新闻
  3. [导入]Replace函数vbTextCompare不支持日文韩文
  4. 极客唐小娟的故事-值得我们思考
  5. Flutter之Stepper源码浅析
  6. 计算机监理培训计划,监理人员培训计划
  7. 解锁视频编码的前世今生:流媒体产业的隐藏剧情
  8. kafka auto.offset.reset / latest / earliest 详解
  9. java(File、IO流)
  10. Linux(1) 概要、安装 、文件系统基本认知
  11. re.search与re.findall的区别
  12. 京东区块链开源底层JD Chain版本升级,获工信部功能测试证书
  13. js原生ajax写法
  14. 中国移动CMPP、联通SGIP和电信SMGP的短信协议
  15. 常见的web中间件java框架漏洞总结
  16. html li内部水平排列,怎样使用li进行水平排列
  17. 二维标准Kalman滤波
  18. android studio
  19. 首届SD-WAN实战特训营
  20. 【成为架构师课程系列】高性能系统设计之分布式缓存

热门文章

  1. 大数据24小时:中国平安推出区块链解决方案“壹账链”,云从科技发布3D结构光人脸识别技术
  2. PDF编辑器中文版的下载方法
  3. <<计算机视觉NeurIPS>>2022:GLIPv2: Unifying Localization and VL Understanding
  4. 2021-3-26 米斯特安全团队视频笔记二(含PHP)
  5. c语言程序设计感触,c语言程序设计报告书体会.doc
  6. 犬夜叉手游觉醒服务器维护,《犬夜叉-觉醒》最详细最容易入门新手指导
  7. poj 1264 hdu 1616 SCUD Busters 凸包+面积计算
  8. 本地MySQL数据库允许用任意ip连接访问
  9. 计算机视觉领域最全汇总
  10. 三星I9220一键刷机 安卓手机