1 原理和SQL

1.1 什么是事务?事务基本特性ACID?

事务指的是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。
image.png

  • 事务基本特性ACID

    • A原子性(atomicity) 指的是一个事务中的操作要么全部成功,要么全部失败。
    • C一致性(consistency) 指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设中间sql执行过程中系统崩溃A也不会损失100块,因为事务没有提交,修改也就不会保存到数据库。
    • I隔离性(isolation) 指的是一个事务的修改在最终提交前,对其他事务是不可见的。
    • D持久性(durability) 指的是一旦事务提交,所做的修改就会永久保存到数据库中。

      1.2 数据库中并发一致性问题?

      在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。
  • 丢失修改

T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。
image.png

  • 读脏数据

T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。
image.png

  • 不可重复读

T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。
image.png

  • 幻影读

T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。
image.png

1.3 事务的隔离等级?

  • 未提交读(READ UNCOMMITTED) 事务中的修改,即使没有提交,对其它事务也是可见的。
  • 提交读(READ COMMITTED) 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。
  • 可重复读(REPEATABLE READ) 保证在同一个事务中多次读取同样数据的结果是一样的。
  • 可串行化(SERIALIZABLE) 强制事务串行执行。 | 隔离级别 | 脏读 | 不可重复读 | 幻影读 | | —- | —- | —- | —- | | 未提交读 | √ | √ | √ | | 提交读 | × | √ | √ | | 可重复读 | × | × | √ | | 可串行化 | × | × | × |

1.4 ACID靠什么保证的呢?

  • A原子性(atomicity) 由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql
  • C一致性(consistency) 一般由代码层面来保证
  • I隔离性(isolation) 由MVCC来保证
  • D持久性(durability) 由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,事务提交的时候通过redo log刷盘,宕机的时候可以从redo log恢复

    1.5 SQL 优化的实践经验?

    1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
    2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
    select id from t where num is null
    最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库.
    备注、描述、评论之类的可以设置为 NULL,其他的,最好不要使用NULL。
    不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL也包含在内),都是占用 100个字符的空间的,如果是varchar这样的变长字段, null 不占用空间。
    可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
    select id from t where num = 0
    3.应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。
    4.应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描,如:
    select id from t where num=10 or Name = ‘admin’
    可以这样查询:
    select id from t where num = 10
    union all
    select id from t where Name = ‘admin’
    5.in 和 not in 也要慎用,否则会导致全表扫描,如:
    select id from t where num in(1,2,3)
    对于连续的数值,能用 between 就不要用 in 了:
    select id from t where num between 1 and 3
    很多时候用 exists 代替 in 是一个好的选择:
    select num from a where num in(select num from b)
    用下面的语句替换:
    select num from a where exists(select 1 from b where num=a.num)
    6.下面的查询也将导致全表扫描:
    select id from t where name like ‘%abc%’
    若要提高效率,可以考虑全文检索。
    7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
    select id from t where num = @num
    可以改为强制查询使用索引:
    select id from t with(index(索引名)) where num = @num
    .应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
    select id from t where num/2 = 100
    应改为:
    select id from t where num = 1002
    9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
    select id from t where substring(name,1,3) = ’abc’ -–name以abc开头的id
    select id from t where datediff(day,createdate,’2005-11-30′) = 0 -–‘2005-11-30’ —生成的id
    应改为:
    select id from t where name like ‘abc%’
    select id from t where createdate >= ‘2005-11-30’ and createdate < ‘2005-12-1’
    10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
    11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
    12.不要写一些没有意义的查询,如需要生成一个空表结构:
    select col1,col2 into #t from t where 1=0
    这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
    create table #t(…)
    13.Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。
    14.对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差。
    15.select count(
    ) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。
    16.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。
    17.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
    18.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连 接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
    19.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
    20.任何地方都不要使用 select from t ,用具体的字段列表代替“”,不要返回用不到的任何字段。
    21.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
  1. 避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件, 最好使用导出表。

23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
29.尽量避免大事务操作,提高系统并发能力。
30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

1.6 Buffer Pool、Redo Log Buffer 和undo log、redo log、bin log 概念以及关系?

  • Buffer Pool 是 MySQL 的一个非常重要的组件,因为针对数据库的增删改操作都是在 Buffer Pool 中完成的
  • Undo log 记录的是数据操作前的样子
  • redo log 记录的是数据被操作后的样子(redo log 是 Innodb 存储引擎特有)
  • bin log 记录的是整个操作记录(这个对于主从复制具有非常重要的意义)

    1.7 从准备更新一条数到事务的提交的流程描述?

    image.png
  1. 首先执行器根据 MySQL 的执行计划来查询数据,先是从缓存池中查询数据,如果没有就会去数据库中查询,如果查询到了就将其放到缓存池中
  2. 在数据被缓存到缓存池的同时,会写入 undo log 日志文件
  3. 更新的动作是在 BufferPool 中完成的,同时会将更新后的数据添加到 redo log buffer 中
  4. 完成以后就可以提交事务,在提交的同时会做以下三件事
  • 将redo log buffer中的数据刷入到 redo log 文件中
  • 将本次操作记录写入到 bin log文件中
  • 将 bin log 文件名字和更新内容在 bin log 中的位置记录到redo log中,同时在 redo log 最后添加 commit 标记

    2 MySQL

    2.1 说下myisam 和 innodb的区别吗?

    myisam引擎是5.1版本之前的默认引擎,支持全文检索、压缩、空间函数等,但是不支持事务行级锁,所以一般用于有大量查询少量插入的场景来使用,而且myisam不支持外键,并且索引和数据是分开存储的。
    innodb是基于B+Tree索引建立的,和myisam相反它支持事务、外键,并且通过MVCC来支持高并发,索引和数据存储在一起。

    2.2 说下MySQL的索引有哪些吧?

    索引在什么层面?
    首先,索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。
    有哪些?

  • B+Tree 索引

    • 是大多数 MySQL 存储引擎的默认索引类型。
  • 哈希索引
    • 哈希索引能以 O(1) 时间进行查找,但是失去了有序性;
    • InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
  • 全文索引
    • MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。
    • 全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。
    • InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。
  • 空间数据索引

    • MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。

      2.3 什么是B+树?为什么B+树成为主要的SQL数据库的索引实现?

  • 什么是B+Tree?

B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。在 B+ Tree 中,一个节点中的 key 从左到右非递减排列,如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1,且不为 null,则该指针指向节点的所有 key 大于等于 keyi 且小于等于 keyi+1。
image.png

  • 为什么是B+Tree?
    • 为了减少磁盘读取次数,决定了树的高度不能高,所以必须是先B-Tree;
    • 以页为单位读取使得一次 I/O 就能完全载入一个节点,且相邻的节点也能够被预先载入;所以数据放在叶子节点,本质上是一个Page页;
    • 为了支持范围查询以及关联关系, 页中数据需要有序,且页的尾部节点指向下个页的头部;
  • B+树索引可分为聚簇索引和非聚簇索引?
  1. 主索引就是聚簇索引(也称聚集索引,clustered index)
  2. 辅助索引(有时也称非聚簇索引或二级索引,secondary index,non-clustered index)。

image.png
如上图,主键索引的叶子节点保存的是真正的数据。而辅助索引叶子节点的数据区保存的是主键索引关键字的值
假如要查询name = C 的数据,其搜索过程如下:a) 先在辅助索引中通过C查询最后找到主键id = 9; b) 在主键索引中搜索id为9的数据,最终在主键索引的叶子节点中获取到真正的数据。所以通过辅助索引进行检索,需要检索两次索引。
之所以这样设计,一个原因就是:如果和MyISAM一样在主键索引和辅助索引的叶子节点中都存放数据行指针,一旦数据发生迁移,则需要去重新组织维护所有的索引。

2.4 那你知道什么是覆盖索引和回表吗?

覆盖索引指的是在一次查询中,如果一个索引包含或者说覆盖所有需要查询的字段的值,我们就称之为覆盖索引,而不再需要回表查询。
而要确定一个查询是否是覆盖索引,我们只需要explain sql语句看Extra的结果是否是“Using index”即可。
比如:
explain select * from user where age=1; // 查询的name无法从索引数据获取
explain select id,age from user where age=1; //可以直接从索引获取

2.5 什么是MVCC? 说说MySQL实现MVCC的原理?

  • 什么是MVCC?

MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
在Mysql的InnoDB引擎中就是指在已提交读(READ COMMITTD)和可重复读(REPEATABLE READ)这两种隔离级别下的事务对于SELECT操作会访问版本链中的记录的过程。
这就使得别的事务可以修改这条记录,反正每次修改都会在版本链中记录。SELECT可以去版本链中拿记录,这就实现了读-写,写-读的并发执行,提升了系统的性能。

  • MySQL的InnoDB引擎实现MVCC的3个基础点
  1. 隐式字段

image.png
如上图,DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键;DB_TRX_ID是当前操作该记录的事务ID; 而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本;delete flag没有展示出来。

  1. undo log

image.png
从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录

  1. ReadView

已提交读和可重复读的区别就在于它们生成ReadView的策略不同。
ReadView中主要就是有个列表来存储我们系统中当前活跃着的读写事务,也就是begin了还未提交的事务。通过这个列表来判断记录的某个版本是否对当前事务可见。假设当前列表里的事务id为[80,100]。
a) 如果你要访问的记录版本的事务id为50,比当前列表最小的id80小,那说明这个事务在之前就提交了,所以对当前活动的事务来说是可访问的。
b) 如果你要访问的记录版本的事务id为90,发现此事务在列表id最大值和最小值之间,那就再判断一下是否在列表内,如果在那就说明此事务还未提交,所以版本不能被访问。如果不在那说明事务已经提交,所以版本可以被访问。
c) 如果你要访问的记录版本的事务id为110,那比事务列表最大id100都大,那说明这个版本是在ReadView生成之后才发生的,所以不能被访问。
这些记录都是去undo log 链里面找的,先找最近记录,如果最近这一条记录事务id不符合条件,不可见的话,再去找上一个版本再比较当前事务的id和这个版本事务id看能不能访问,以此类推直到返回可见的版本或者结束。

  • 举个例子 ,在已提交读隔离级别下:

比如此时有一个事务id为100的事务,修改了name,使得的name等于小明2,但是事务还没提交。则此时的版本链是
image.png
那此时另一个事务发起了select 语句要查询id为1的记录,那此时生成的ReadView 列表只有[100]。那就去版本链去找了,首先肯定找最近的一条,发现trx_id是100,也就是name为小明2的那条记录,发现在列表内,所以不能访问。
这时候就通过指针继续找下一条,name为小明1的记录,发现trx_id是60,小于列表中的最小id,所以可以访问,直接访问结果为小明1。
那这时候我们把事务id为100的事务提交了,并且新建了一个事务id为110也修改id为1的记录,并且不提交事务
image.png
这时候之前那个select事务又执行了一次查询,要查询id为1的记录。
已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView

  1. 如果你是已提交读隔离级别,这时候你会重新一个ReadView,那你的活动事务列表中的值就变了,变成了[110]。按照上的说法,你去版本链通过trx_id对比查找到合适的结果就是小明2。
  2. 如果你是可重复读隔离级别,这时候你的ReadView还是第一次select时候生成的ReadView,也就是列表的值还是[100]。所以select的结果是小明1。所以第二次select结果和第一次一样,所以叫可重复读!

这就是Mysql的MVCC,通过版本链,实现多版本,可并发读-写,写-读。通过ReadView生成策略的不同实现不同的隔离级别。

2.6 MySQL 锁的类型有哪些呢?

说两个维度

  • 共享锁(简称S锁)和排他锁(简称X锁)
    • 读锁是共享的,可以通过lock in share mode实现,这时候只能读不能写。
    • 写锁是排他的,它会阻塞其他的写锁和读锁。从颗粒度来区分,可以分为表锁和行锁两种。
  • 表锁和行锁
    • 表锁会锁定整张表并且阻塞其他用户对该表的所有读写操作,比如alter修改表结构的时候会锁表。
    • 行锁又可以分为乐观锁和悲观锁
      • 悲观锁可以通过for update实现
      • 乐观锁则通过版本号实现。

两个维度结合来看

  • 共享锁(行锁):Shared Locks
    • 读锁(s锁),多个事务对于同一数据可以共享访问,不能操作修改
    • 使用方法:
      • 加锁:SELECT * FROM table WHERE id=1 LOCK IN SHARE MODE
      • 释锁:COMMIT/ROLLBACK
  • 排他锁(行锁):Exclusive Locks
    • 写锁(X锁),互斥锁/独占锁,事务获取了一个数据的X锁,其他事务就不能再获取该行的读锁和写锁(S锁、X锁),只有获取了该排他锁的事务是可以对数据行进行读取和修改
    • 使用方法:
      • DELETE/ UPDATE/ INSERT — 加锁
      • SELECT * FROM table WHERE … FOR UPDATE — 加锁
      • COMMIT/ROLLBACK — 释锁
  • 意向共享锁(IS)

    • 一个数据行加共享锁前必须先取得该表的IS锁,意向共享锁之间是可以相互兼容的 意向排它锁(IX) 一个数据行加排他锁前必须先取得该表的IX锁,意向排它锁之间是可以相互兼容的 意向锁(IS、IX)是InnoDB引擎操作数据之前自动加的,不需要用户干预; 意义: 当事务操作需要锁表时,只需判断意向锁是否存在,存在时则可快速返回该表不能启用表锁
    • 意向共享锁(IS锁)(表锁):Intention Shared Locks
      • 表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁 前必须先取得该表的IS锁。
    • 意向排它锁(IX锁)(表锁):Intention Exclusive Locks
      • 表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他 锁前必须先取得该表的IX锁。

        2.7 你们数据量级多大?分库分表怎么做的?

        首先分库分表分为垂直和水平两个方式,一般来说我们拆分的顺序是先垂直后水平。
  • 垂直分库

基于现在微服务拆分来说,都是已经做到了垂直分库了

  • 垂直分表

垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。
在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库、用户数据库等。
image.png

  • 水平分表

首先根据业务场景来决定使用什么字段作为分表字段(sharding_key),比如我们现在日订单1000万,我们大部分的场景来源于C端,我们可以用user_id作为sharding_key,数据查询支持到最近3个月的订单,超过3个月的做归档处理,那么3个月的数据量就是9亿,可以分1024张表,那么每张表的数据大概就在100万左右。
比如用户id为100,那我们都经过hash(100),然后对1024取模,就可以落到对应的表上了。
image.png

2.8 那分表后的ID怎么保证唯一性的呢?

因为我们主键默认都是自增的,那么分表之后的主键在不同表就肯定会有冲突了。有几个办法考虑:

  • 设定步长,比如1-1024张表我们分别设定1-1024的基础步长,这样主键落到不同的表就不会冲突了。
  • 分布式ID,自己实现一套分布式ID生成算法或者使用开源的比如雪花算法这种
  • 分表后不使用主键作为查询依据,而是每张表单独新增一个字段作为唯一主键使用,比如订单表订单号是唯一的,不管最终落在哪张表都基于订单号作为查询依据,更新也一样。

    2.9 分表后非sharding_key的查询怎么处理呢?

  • 可以做一个mapping表,比如这时候商家要查询订单列表怎么办呢?不带user_id查询的话你总不能扫全表吧?所以我们可以做一个映射关系表,保存商家和用户的关系,查询的时候先通过商家查询到用户列表,再通过user_id去查询。

  • 打宽表,一般而言,商户端对数据实时性要求并不是很高,比如查询订单列表,可以把订单表同步到离线(实时)数仓,再基于数仓去做成一张宽表,再基于其他如es提供查询服务。
  • 数据量不是很大的话,比如后台的一些查询之类的,也可以通过多线程扫表,然后再聚合结果的方式来做。或者异步的形式也是可以的。

List>> taskList = Lists.newArrayList();
for (int shardingIndex = 0; shardingIndex < 1024; shardingIndex++) {
taskList.add(() -> (userMapper.getProcessingAccountList(shardingIndex)));
}
List list = null;
try {
list = taskExecutor.executeTask(taskList);
} catch (Exception e) {
//do something
}

public class TaskExecutor {
public List executeTask(Collection<? extends Callable> tasks) throws Exception {
List result = Lists.newArrayList();
List> futures = ExecutorUtil.invokeAll(tasks);
for (Future future : futures) {
result.add(future.get());
}
return result;
}
}

2.10 MySQL主从复制?

主要涉及三个线程: binlog 线程、I/O 线程和 SQL 线程。

  • binlog 线程 : 负责将主服务器上的数据更改写入二进制日志中。
  • I/O 线程 : 负责从主服务器上读取二进制日志,并写入从服务器的中继日志中。
  • SQL 线程 : 负责读取中继日志并重放其中的 SQL 语句。

image.png
全同步复制
主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。
半同步复制
和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。

2.11 MySQL主从的延迟怎么解决呢?

这个问题貌似真的是个无解的问题,只能是说自己来判断了,需要走主库的强制走主库查询。

2.12 MySQL读写分离方案?

主服务器处理写操作以及实时性要求比较高的读操作,而从服务器处理读操作。
读写分离能提高性能的原因在于:

  • 主从服务器负责各自的读和写,极大程度缓解了锁的争用;
  • 从服务器可以使用 MyISAM,提升查询性能以及节约系统开销;
  • 增加冗余,提高可用性。

读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器。