Mysql杂记

1. 缓冲池(buffer pool)总结:

(1)缓冲池(buffer pool)是一种常见的降低磁盘访问的机制;

(2)缓冲池通常以页(page)为单位缓存数据;

(3)缓冲池的常见管理算法是LRU,memcache,OS,InnoDB都使用了这种算法;

(4)InnoDB对普通LRU进行了优化:

将缓冲池分为老生代和新生代,入缓冲池的页,优先进入老生代,页被访问,才进入新生代,以解决预读失效的问题

页被访问,且在老生代停留时间超过配置阈值的,才进入新生代,以解决批量数据访问,大量热数据淘汰的问题

2. (1)MySQL数据存储包含内存与磁盘两个部分;

(2)内存缓冲池(buffer pool)以页为单位,缓存最热的数据页(data page)与索引页(index page);

(3)InnoDB以变种LRU算法管理缓冲池,并能够解决“预读失效”与“缓冲池污染”的问题;

3. 什么是InnoDB的写缓冲:

在MySQL5.5之前,叫插入缓冲(insert buffer),只针对insert做了优化;现在对delete和update也有效,叫做写缓冲(change buffer)。

它是一种应用在非唯一普通索引页(non-unique secondary index page)不在缓冲池中,对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(buffer changes),等未来数据被读取时,再将数据合并(merge)恢复到缓冲池中的技术。写缓冲的目的是降低写操作的磁盘IO,提升数据库性能。

除了数据页被访问,还有哪些场景会触发刷写缓冲中的数据呢?

还有这么几种情况,会刷写缓冲中的数据:

(1)有一个后台线程,会认为数据库空闲时;

(2)数据库缓冲池不够用时;

(3)数据库正常关闭时;

(4)redo log写满时;

什么业务场景,适合开启InnoDB的写缓冲机制?

先说什么时候不适合,如上文分析,当:

(1)数据库都是唯一索引;

(2)或者,写入一个数据后,会立刻读取它;

这两类场景,在写操作进行时(进行后),本来就要进行进行页读取,本来相应页面就要入缓冲池,此时写缓存反倒成了负担,增加了复杂度。

什么时候适合使用写缓冲,如果:

(1)数据库大部分是非唯一索引;

(2)业务是写多读少,或者不是写后立刻读取

可以使用写缓冲,将原本每次写入都需要进行磁盘IO的SQL,优化定期批量写磁盘。

画外音:例如,账单流水业务。

上述原理,对应InnoDB里哪些参数?

有两个比较重要的参数。

参数:innodb_change_buffer_max_size

介绍:配置写缓冲的大小,占整个缓冲池的比例,默认值是25%,最大值是50%。

画外音:写多读少的业务,才需要调大这个值,读多写少的业务,25%其实也多了。

参数:innodb_change_buffering

介绍:配置哪些写操作启用写缓冲,可以设置成all/none/inserts/deletes等。

4. 数据库索引总结

数据库索引用于加速查询

虽然哈希索引是O(1),树索引是O(log(n)),但SQL有很多“有序”需求,故数据库使用树型索引

InnoDB不支持哈希索引

数据预读的思路是:磁盘读写并不是按需读取,而是按页预读,一次会读一页的数据,每次加载更多的数据,以便未来减少磁盘IO

局部性原理:软件设计要尽量遵循“数据读取集中”与“使用到一个数据,大概率会使用其附近的数据”,这样磁盘预读能充分提高磁盘IO

数据库的索引最常用B+树:

(1)很适合磁盘存储,能够充分利用局部性原理,磁盘预读;

(2)很低的树高度,能够存储大量数据;

(3)索引本身占用的内存很小;

(4)能够很好的支持单点查询,范围查询,有序性查询;

5. MyISAM与InnoDB的索引差异总结

MyISAM和InnoDB都使用B+树来实现索引:

MyISAM的索引与数据分开存储

MyISAM的索引叶子存储指针,主键索引与普通索引无太大区别

InnoDB的聚集索引和数据行统一存储

InnoDB的聚集索引存储数据行本身,普通索引存储主键

InnoDB一定有且只有一个聚集索引

InnoDB建议使用趋势递增整数作为PK,而不宜使用较长的列作为PK

总结

在大数据量,高并发量的互联网业务场景下,对于MyISAM和InnoDB

有where条件,count(*)两个存储引擎性能差不多

不要使用全文索引,应当使用《索引外置》的设计方案

事务影响性能,强一致性要求才使用事务

不用外键,由应用程序来保证完整性

不命中索引,InnoDB也不能用行锁

结论

在大数据量,高并发量的互联网业务场景下,请使用InnoDB:

行锁,对提高并发帮助很大

事务,对数据一致性帮助很大

这两个点,是InnoDB最吸引人的地方。

6. InnoDB七种锁中的:

(1)共享/排它锁(Shared and Exclusive Locks)

(2)意向锁(Intention Locks)

(3)记录锁(Record Locks)

(4)间隙锁(Gap Locks)

(5)临键锁(Next-key Locks)

(6)插入意向锁(Insert Intention Locks)

(7)自增锁(Auto-inc Locks)

(1)InnoDB的索引与行记录存储在一起,这一点和MyISAM不一样;

(2)InnoDB的聚集索引存储行记录,普通索引存储PK,所以普通索引要查询两次;

(3)记录锁锁定索引记录;

(4)间隙锁锁定间隔,防止间隔中被其他事务插入;

(5)临键锁锁定索引记录+间隔,防止幻读;

共享/排它锁:

(1)事务拿到某一行记录的共享S锁,才可以读取这一行;

(2)事务拿到某一行记录的排它X锁,才可以修改或者删除这一行;

其兼容互斥表如下:

          S          X

S      兼容      互斥

X      互斥      互斥

即:

(1)多个事务可以拿到一把S锁,读读可以并行;

(2)而只有一个事务可以拿到X锁,写写/读写必须互斥;

二,意向锁(Intention Locks)

InnoDB支持多粒度锁(multiple granularity locking),它允许行级锁与表级锁共存,实际应用中,InnoDB使用的是意向锁。 

意向锁是指,未来的某个时刻,事务可能要加共享/排它锁了,先提前声明一个意向。 

意向锁有这样一些特点:

(1)首先,意向锁,是一个表级别的锁(table-level locking);

(2)意向锁分为:

意向共享锁(intention shared lock, IS),它预示着,事务有意向对表中的某些行加共享S锁

意向排它锁(intention exclusive lock, IX),它预示着,事务有意向对表中的某些行加排它X锁 

举个例子:

select ... lock in share mode,要设置IS锁;

select ... for update,要设置IX锁; 

(3)意向锁协议(intention locking protocol)并不复杂:

事务要获得某些行的S锁,必须先获得表的IS锁

事务要获得某些行的X锁,必须先获得表的IX锁 

(4)由于意向锁仅仅表明意向,它其实是比较弱的锁,意向锁之间并不相互互斥,而是可以并行,其兼容互斥表如下:

          IS          IX

IS      兼容      兼容

IX      兼容      兼容 

(5)额,既然意向锁之间都相互兼容,那其意义在哪里呢?它会与共享锁/排它锁互斥,其兼容互斥表如下:

          S          X

IS      兼容      互斥

IX      互斥      互斥

画外音:排它锁是很强的锁,不与其他类型的锁兼容。这也很好理解,修改和删除某一行的时候,必须获得强锁,禁止这一行上的其他并发,以保障数据的一致性。

三,插入意向锁(Insert Intention Locks)

对已有数据行的修改与删除,必须加强互斥锁X锁,那对于数据的插入,是否还需要加这么强的锁,来实施互斥呢?插入意向锁,孕育而生。 

插入意向锁,是间隙锁(Gap Locks)的一种(所以,也是实施在索引上的),它是专门针对insert操作的。

画外音:有点尴尬,间隙锁下一篇文章才会介绍,暂且理解为,它是一种实施在索引上,锁定索引某个区间范围的锁。 

它的玩法是:

多个事务,在同一个索引,同一个范围区间插入记录时,如果插入的位置不冲突,不会阻塞彼此。

画外音:官网的说法是

Insert Intention Lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap. 

这样,之前挖坑的例子,就能够解答了。 

在MySQL,InnoDB,RR下:

t(id unique PK, name); 

数据表中有数据:

10, shenjian

20, zhangsan

30, lisi 

事务A先执行,在10与20两条记录中插入了一行,还未提交:

insert into t values(11, xxx); 

事务B后执行,也在10与20两条记录中插入了一行:

insert into t values(12, ooo); 

(1)会使用什么锁?

(2)事务B会不会被阻塞呢? 

回答:虽然事务隔离级别是RR,虽然是同一个索引,虽然是同一个区间,但插入的记录并不冲突,故这里:

使用的是插入意向锁

并不会阻塞事务B 

思路总结

(1)InnoDB使用共享锁,可以提高读读并发;

(2)为了保证数据强一致,InnoDB使用强互斥锁,保证同一行记录修改与删除的串行性;

(3)InnoDB使用插入意向锁,可以提高插入并发;

7. 索引的类型:

在MySQL中,索引分为两大类:聚簇索引和非聚簇索引。聚簇索引是按照数据存放的物理位置为顺序的,而非聚簇索引则不同;聚簇索引能够提高多行检索的速度,而非聚簇索引则对单行的检索速度很快。

在这两大类的索引类型下,还可以将索引分成四个小类:

1,普通索引:最基本的索引,没有任何限制,是我们大多数情况下使用到的索引。

2,唯一索引:与普通索引类型,不同的是唯一索引的列值必须唯一,但允许为空值。

3,全文索引:全文索引(FULLTEXT)仅可以适用于MyISAM引擎的数据表;作用于CHAR、VARCHAR、TEXT数据类型的列。

4,组合索引:将几个列作为一条索引进行检索,使用最左匹配原则。

8. 建立索引的原则

1,最左前缀匹配原则。这是非常重要、非常重要、非常重要(重要的事情说三遍)的原则,MySQL会一直向右匹配直到遇到范围查询(>,<,BETWEEN,LIKE)就停止匹配,比如: a = 1 AND b = 2 AND c > 3 AND d = 4,如果建立 (a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引,则都可以用到,a,b,d的顺序可以任意调整。

2,等于(=)和in 可以乱序。比如,a = 1 AND b = 2 AND c = 3 建立(a,b,c)索引可以任意顺序,MySQL的查询优化器会帮你优化成索引可以识别的模式。

3,尽量选择区分度高的列作为索引,区分度的公式是 COUNT(DISTINCT col) / COUNT(*)。表示字段不重复的比率,比率越大我们扫描的记录数就越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度是0。可能有人会问,这个比率有什么经验么?使用场景不同,这个值也很难确定,一般需要JOIN的字段我们要求在0.1以上,即平均1条扫描10条记录。

4,索引列不能参与计算,尽量保持列“干净”。比如,FROM_UNIXTIME(create_time) = '2016-06-06' 就不能使用索引,原因很简单,B+树中存储的都是数据表中的字段值,但是进行检索时,需要把所有元素都应用函数才能比较,显然这样的代价太大。所以语句要写成 : create_time = UNIX_TIMESTAMP('2016-06-06')。

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

6,单个多列组合索引和多个单列索引的检索查询效果不同,因为在执行SQL时,MySQL只能使用一个索引,会从多个单列索引中选择一个限制最为严格的索引。

根据上面这些原则,我们来修改开篇的慢查询:

SELECT

count(*) AS count

FROM trade_bASe AS a

WHERE

a.trade_status = 7

AND a.create_time BETWEEN '2015-09-01' AND '2016-01-14'

AND a.booking_source = '2'

根据这条SQL,应该建立的索引是:trade_status, booking_source,create_time的联合索引;其中,trade_status、booking_source的顺序可以颠倒,而且 create_time 的区间查询放到后面。这就是利用了索引的最左匹配原则。

9. 索引的优化方法:

1,何时使用聚簇索引或非聚簇索引:

2,索引不会包含有NULL值的列:只要列中包含有NULL值,都将不会被包含在索引中,组合索引中只要有一列有NULL值,那么这一列对于此条组合索引就是无效的。所以我们在数据库设计时,不要让索引字段的默认值为NULL。

3,使用短索引:假设,如果有一个数据类型为CHAR(255)的列,在前10个或20个字符内,绝大部分数据的值是唯一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省I/O操作。

4,索引列排序:MySQL查询只使用一个索引,因此如果WHERE子句中已经使用了索引的话,那么ORDER BY中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下,不要使用排序操作;尽量不要包含多个列的排序,如果需要,最好给这些列也创建组合索引。

5,LIKE语句操作:一般情况下,不建议使用LIKE操作;如果非使用不可,如何使用也是一个研究的课题。LIKE "%aaaaa%"不会使用索引,但是LIKE "aaa%"却可以使用索引。

6,不要在索引列上进行运算:在建立索引的原则中,提到了索引列不能进行运算,这里就不再赘述了。

10. like模糊查询:

使用like模糊查询会导致索引失效,在数据量大的时候会有性能问题

(1)尽量少以%或者_开头进行模糊查询

通过explain执行计划,我们发现,使用like模糊查询时,如果不以%和_开头查询的话,索引还是有效的

11. 在线表结构变更:

在《啥,又要为表增加一列属性?》文章的开头,已经说明常见“新表+触发器+迁移数据+rename”方案(pt-online-schema-change),这是业内非常成熟的扩展列的方案(以为大伙都熟悉,没有展开讲,只重点讲了两种新方案,这可能是导致被喷得厉害的源头),今天补充说一下。

以user(uid, name, passwd)

扩展到user(uid, name, passwd, age, sex)为例

基本原理是:

(1)先创建一个扩充字段后的新表user_new(uid, name, passwd, age, sex)

(2)在原表user上创建三个触发器,对原表user进行的所有insert/delete/update操作,都会对新表user_new进行相同的操作

(3)分批将原表user中的数据insert到新表user_new,直至数据迁移完成

(4)删掉触发器,把原表移走(默认是drop掉)

(5)把新表user_new重命名(rename)成原表user

扩充字段完成。

优点:整个过程不需要锁表,可以持续对外提供服务

操作过程中需要注意:

(1)变更过程中,最重要的是冲突的处理,一条原则,以触发器的新数据为准,这就要求被迁移的表必须有主键(这个要求基本都满足)

(2)变更过程中,写操作需要建立触发器,所以如果原表已经有很多触发器,方案就不行(互联网大数据高并发的在线业务,一般都禁止使用触发器)

(3)触发器的建立,会影响原表的性能,所以这个操作建议在流量低峰期进行

pt-online-schema-change是DBA必备的利器,比较成熟,在互联网公司使用广泛。

11. 数据多版本

数据多版本是一种能够进一步提高并发的方法,它的核心原理是:

(1)写任务发生时,将数据克隆一份,以版本号区分;

(2)写任务操作新克隆的数据,直至提交;

(3)并发读任务可以继续读取旧版本的数据,不至于阻塞;

如上图:

1. 最开始数据的版本是V0;

2. T1时刻发起了一个写任务,这是把数据clone了一份,进行修改,版本变为V1,但任务还未完成;

3. T2时刻并发了一个读任务,依然可以读V0版本的数据;

4. T3时刻又并发了一个读任务,依然不会阻塞;

可以看到,数据多版本,通过“读取旧版本数据”能够极大提高任务的并发度。

提高并发的演进思路,就在如此:

普通锁,本质是串行执行

读写锁,可以实现读读并发

数据多版本,可以实现读写并发

12. DB主从一致性架构优化4种方法 总结:

为了解决主从数据库读取旧数据的问题,常用的方案有四种:

(1)半同步复制

(2)强制读主

(3)数据库中间件

(4)缓存记录写key

你可能感兴趣的:(Mysql杂记)