mysql 性能调优

转自:http://suhuanzheng7784877.iteye.com/blog/2047209

 

Mysql高性能学习笔记-01

 

刘岩

 

Blog:suhuanzheng7784877.iteye.com

 

1.Mysql的架构

Mysql整体的架构模块如下图所示

 


mysql 性能调优_第1张图片
 

模块1:连接客户端,这个一般是指mysql基于mysql通讯协议的驱动程序。比如mysql的Java的JDBC驱动程序——mysql-connector-java-5.1.26-bin.jar。再比如.net平台的操作数据库的规范ADO。

 

模块2:连接池,Mysql自身有个连接池,为了连接线程的复用,Mysql将其进行了池化。将连接资源进行了池化,使得连接线程可以复用。(mysql 5.5以后)

 

模块3:SQL接口,接受SQL命令后,调用SQL接口,执行并返回相应的结果集。

 

模块4:SQL解析引擎,SQL语句需要进行解析。将SQL语句解析成为抽象语法树。从中也要识别SQL语句是否合法。

 

模块5:优化引擎,抽象成语法树后,就会对该命令进行一系列的对用户来说透明的优化措施——查询语句可能重新构造;目标表的读取顺序;

 

模块6:缓存以及缓冲引擎,对于select语句,解析sql语句之前。mysqld先检查查询缓存区域,如果能够在其中找到对应的查询,服务器不会继续解析,而是直接返回查询缓存的内容直接返回。

 

模块7:可插拔的存储引擎,常用的存储引擎:innodb、myisam、memroy。存储引擎可以根据业务需求,随意进行变更。

 

2.Mysql中的锁机制

 

表锁:

 

表锁是读写锁的一种实现,而mysqll的表锁是在服务层,也就是在存储引擎的上层,也是mysql最基本的锁策略。读锁之间不会相互阻塞,但是一旦遇到了写锁,所有其他的锁请求都会被阻塞住。而且写锁的执行优先级会高于读锁(双向队列可以实现非FIFO的场景)。

 

行锁:

 

行锁是存储引擎对于mysql基本锁策略的扩展,行锁定是存储引擎才具备的特性。最典型的行级锁存储引擎是innodb。而此时就又引出了innodb对于行锁的实现,也就是该引擎实现了MVCC——多版本并发控制。

 

在mysql的MVCC实现中,基本原则就是实现非阻塞的读取操作;写操作的时候仅仅锁定必要的目标行。

 

从MVCC的目标就可以基本猜出,它使用空间换取的时间。

 

简要概述:innodb的MVCC机制是在每个表每条记录的后面隐藏两个字段,第一个隐藏字段是该行的创建时间,第二个字段代表改行的删除时间。注意:这里的时间是指的系统版本号码。新的事务开启,该行版本号自动递增。事务开始的系统版本号需要和每行记录的版本号进行对比。在innodb中只有隔离级别是:repeatable read和read committed两个隔离级别才支持MVCC机制。

默认事务隔离级别——repeatable read对于CRUD的MVCC实现机制是

插入操作:为每一个新增的行记录,新增的记录隐藏字段-创建时间保存当前事务。

删除操作:为每一条目标记录保存为当前事务系统版本号,存储到删除时间字段中。

更新操作:插入一条新记录,保存当前事务版本号作为创建字段,并且将事务版本号保存到原来的行的删除字段中。

最麻烦的查询操作:事务的每次读取会读取创建字段中<=自身事务版本的记录,确保读取的行是自己创建的或者是事务开始前已经存在的。对于删除字段呢,本身事务会读取大于自身事务或者该删除字段并未有任何值的记录,确保在本身事务开始之前未被删除的事务被读取到。

 

 

 

我们将查询操作事务和其他操作的事务进行一个简单的结合来进行分析,即:查询->插入、查询->删除、查询->更新。

 

查询->插入:首先说明,若是先进行插入事务(id为1),那么新增记录的创建字段为1,删除字段为NULL。后执行的查询事务(id为2),那么该条记录被查询事务作为有效记录返回给结果集,因为该记录的创建字段满足<=查询事务的id,而该条记录的删除字段又是空。

 

若是查询事务先执行,而插入事务后执行的话,那么。根据MVCC的规则,此新增的记录,并不会返回给结果集,因为查询事务的id要小于插入事务的id。

 

查询->删除:删除事务先于查询事务执行,那么删除事务的id将保存到该目标记录的删除字段上,而查询事务查询到此记录时,因为,该记录的删除事务小于查询事务,那么此信息不会被作为有效地结果返回给结果集。比较麻烦的是查询事务优于删除事务,先执行查询事务,而删除事务又很慢,还未改变该记录的删除字段的值,就执行了查询事务,那么查询事务会将此记录返回给结果集,因为该记录的删除字段的值小于该查询事务的id。那么返回到客户端后,客户端程序又想对此无效的记录做操作,而后续执行的删除事务,很给力,执行完毕后,该记录就是“幽灵记录”。那么此时应当在客户端程序中,对“幽灵记录”进行严密的防范措施——比如无效记录的异常信息提示等等。

 

查询->修改:这个结合了查询->新增、查询->删除这两个用例。当查询事务优于更新事务的时候,自不必说,查询出来的记录保持着原有的数据。当更新事务优于查询事务执行的时候,过程如下。更新事务将一条新的纪录插入到表中,将新纪录的创建字段赋值为自身事务id,并将自身事务id赋值给原记录,那么查询事务执行的时候,会查找低于自身事务id的记录,新增记录符合条件的,原始记录的删除字段的值小于查询事务自身的id,不会作为结果集记录返回的。

下图是每种操作事务类型对于那两个隐藏字段的关注点示意图。


mysql 性能调优_第2张图片
 

 

 

3.Mysql的MyISAM与InnoDB的大致对比

 

因为后续还会对Mysql这两个重要的引擎继续讨论,那么咱们在这里先大致看一下这两个引擎的大致区别。后续更加细粒度化的区别,还是继续会讨论的。


mysql 性能调优_第3张图片

 

Mysql高性能学习笔记2

刘岩

[email protected]

Blog:suhuanzheng7784877.iteye.com

 

1.前言

高性能Mysql中的第二章-基准测试和第三章-服务器性能剖析是需要全局考虑的问题,不同的应用场景,基准测试的方式和输入数据是不太一样的。所以我们后续再讨论这两个问题,先放过去,直接进行优化schema和数据类型的这一话题。

2.优化数据类型

优化数据类型,基本上是用在建表和修改表的场景上,整个优化数据类型这一话题说下来,基本上都是集中于:对于DB数据的高效存储和高效查询。在原生的Mysql中,数据类型大体上分为以下几种:整数类型、实数类型、字符串类型、日期时间类型、位数类型、特殊类型。

优化数据类型基本上参照以下几个原则:

1):使用小类型的数据类型,能用int的别用long。小数据类型在磁盘寻址的时候占用更少的资源,也减少了CPU的运算时间,这样在iowait的时候就不会因为大字段而消耗过多资源。

2):简单类型优先,这个就需要结合应用层语言的知识来阐述了,比如Java中的int类型和Integer类型,哪个更耗资源,答案肯定是Integer,在《Java代码优化》中就曾经提出过,使用原始类型表述属性值。在Mysql也是如此,能使用最简单的类型代表字段的,尽量使用简单类型。这样更贴近于CPU原生支持的计算类型。比如使用整型存储时间戳;用整型存储IP地址;用整型存储货币浮点,在应用层,再用乘除法换算小数点的精度。

3):不是必要时刻,不要使用NULL,让所有字段哪怕是有默认的值,也要非空。对于优化索引,如果字段是NULL,无法对其NULL进行索引排列。不过InnoDB对于NULL是做了特殊的bit位存储。

3.整型类型

Tinyint(8位)

范围:无符号(0~256)、有符号(-128~127

场景:一般用于存储数字字典,常量表的id,因为数据量十分有限,又是常量表,所以可以用它存储

 

Smallint(16位)

范围:无符号(0~65536)、有符号(-32768~32767

场景:Tinyint的替代品,若常量表数据比较多,比如中国的省--自治区-区县-村镇,到这个范围下,基本够用了。中国有65536个村镇(区县)吗?

 

Mediumint(24位)

范围:无符号(0~16777216)、有符号(-8388608~8388607

场景:1000w以内的数据,这个若是日志表,又是在一段时间内数据量可控,定时清理,Mediumint不失为是轻量级的int的一种id选择。

 

Int(32位):大多数场景,一般Javaint也支持不了这么长的整数位!

范围:无符号(0~4294967296)、有符号(-2147483648~2147483647

场景:大多数的自增id场景,基本够用了。无符号40多亿数据,一般的中小型,互联网,基本够用。

 

Bigint(64位)范围:天文数字,在Java中必须特殊处理该数字类型——BigDecimal进行处理。

范围:无符号(0~18446744073709551616)、有符号(-922337203685478~922337203685477)。

场景:使用关系型数据库存储海量数据的id。千万大一位是亿,亿大一位是兆,兆在大一位是什么????不过数据量在这个范围,很难想象还用RDBMS进行管理。

 

有符号与无符号的最大区别就是是否支持负数。Unsigned一旦被选择上了,表示不允许负数,也就是存储无符号数。一般情况下无符号int类型的字段几乎可以满足系统要求了,就算是自增id类型。40多亿的mysql数据量也已经比较不小了。日交易量记录上千万比记录,一个月也就区区3亿记录。如果大于这个数量级的数据,又是实时数据,应该考虑分表分库。或者借助NoSQL,将数据量散列拆分开。扯远了,这里就是告诉大家,数值类型字段支持的范围。

4.实数类型

其实基本上也就是指含有小数的数,也就是浮点类型的数据类型。

Float:4个字节存储

Double:8个字节存储

Decimal:允许65个数字

这里有位仁兄总结的浮点型和定点型计算的文章,很不错http://www.163ns.com/zixun/post/5226.html

基本上float可以用作百分比,有点误差没关系,double精确度比float大。而Decimal是完全金额类型计算。有的非敏感的,金额不是特别精确的系统业务场景,笔者也见过也有人使用double的。(你说那些不精确的,被四舍的钱都哪去了,都归谁了?100个人也就算了,如果涉及到1000w个人,每个人被四舍了的几厘钱,甚至到分钱误差,加起来够买房子了吧?)

存储以及计算消耗代价:Float<Double<Decimal

而原生的浮点类型,CPU可以直接参与计算,好像评价CPU的性能就是看待CPU每秒可以运行多少浮点型运算。

对于支持浮点类型计算的CPU厂商如下:AMD&Intel,至于谁的浮点计算能力更强,不同的产品系列,随时代而变吧。笔者个人倾向于因特尔的至强处理器。

5.字符串类型

字符串类型主要分为varcharcharblobtext之间的PK了。

一定要将字符串类型的字段调优到极致,因为数据库中,我们面对最多的类型也就是字符串,而我们每天面对的最多的场景也就是对文字的处理。

varchar类型:用于存储可变长的字符串,比定长char类型节省空间(在通常情况下)。除非设置row_format=fixed,每一行是定长存储。varchar额外需要1~2个字节存储字符串的长度。当列的最大长度<=255字节,用1个字节存储长度。否则采取2个字节。而且在Mysql5以后,varchar字段不会将末尾的空格剔除了。

char类型:char是定长类型,那么在存取过程中,会根据字符串长度老老实实分配足够的空间。定长字符串类型不容易产生磁盘碎片,对于定长短列,charvarchar更有效。比如存储MD5或者SHA1值。


mysql 性能调优_第4张图片
 

Blob类型:

存储二进制类型的大字段数据,没有排序规则以及字符集。

类型成员有:tinyblobblobmediumbloblongblob

一般情况下存储图片、文档文件,用之。存储引擎在blob很大时借助外部存储(操作系统FS接口)进行特殊处理。

Text类型-对应于Oracleclob

存储字符方式存储大字段类型数据,有排序规则和字符集。

类型成员有:tinytexttextmediumtextlongtext

一般情况下存储文章,html页面内容。同理,在text很大时借助外部存储,进行特殊处理。

经验:

1)一般获取blob或者text记录的时候,将原始记录值进行截断——substring(字段名,大小)函数。之后再转换成为相应的字符串。这样可以使用到Mysql的内存临时表了,而避免了从磁盘上去取数据的IO

2)临时表的大小超过配置的max_ heap_table_sizetmp_table_size)的时候内存临时表将使用磁盘临时表。(也就是说将内存密集型的case负载到了IO密集型)

1.枚举类型

Mysql存取枚举,紧凑。一般代替常用的字符串类型。Mysql将枚举列表的个数将其压缩位1~2个字节存储。之后,再将每一个枚举值保存为一个整数数字,将整数数字与枚举字符串的值做键值对儿的映射。也就是说,实际上表中引用枚举的字段值存储的是数字。

实验证明,着实如此。

CREATE TABLE `user2` (

  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,

  `type` enum('魏','','') CHARACTER SET utf8 DEFAULT '',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB

执行查询的时候将type字段都加上一个数字,得出来的结果居然是数字,证明枚举底层使用的是数值类型进行的存取枚举。而且若是非要枚举做外键,那么基于基准测试给出的结果,枚举与枚举之间的外键关联QPS是最高的。Mysql内部对枚举的数值做了相应的排序优化。

场景:能够使用枚举做常量时,尽量不要用字符串类型。

2.日期和时间

日期和时间类型有以下几种:datetimeyeartimestampdatetime

date:相当于截取了datetimedate,范围时从公元011日,可以到公元99991231日。

time:相当于截取了datetimetime,范围就是一天的24小时。

year:比较尴尬,临界值是6970,输入69,基本上代表2069年。70就是代表1970年。范围值是0~99,分别代表,0~69:2000~206970~99:1970~1999。不是特殊情况,基本弃用。

最常用的应该是datetimetimestamp

datetime:使用8个字节存储日期与时间,那么可以得出结论,date使用4个字节,time也是4个字节。精确到秒级别,与时区无关。范围是从1000年到9999年的日期和时间。

timestamp:使用4个字节存储日期与时间,不过范围只能表示从1970~2038年。如果没有什么意外,看到这篇文章的同志们,大多数都能活到那一年,之后会不会出现timestamp2这种类型来扩大时间戳的范围,那就得看是不是有支持更大整型数值的类型出现了。在应用层使用long类型插入该字段的值,最后可以存储正确的日期时间,而且该字段依赖于时区。做国际化产品的时候需要特别注意!

3.SET类型

用于存储集合类型的集合类,集合元素里面基本上存储的是常量值,书中举了一个比较贴切的列子,就是权限控制的权限集合。其实也是代表一个人的聚合元素。但是呢,其实权限控制完全用整形也可以表示,就是类似于linux的权限数字,比如777代表该文件夹无任何限制可以被其他用户使用,访问,修改。

对于SET类型(mysql数据库中),在Java应用层获取该类型的值,使用字符串就可以,不过获取的值还需要另外处理,拆解字符串为字符串数组(使用,进行拆分)。

4.特殊字段-ipv4地址的存取

存取ip地址可以使用mysql中的两个函数将ipv4字符串转换成为整数,整数的存取比字符串快。两个特殊的函数是:

Ip地址转成数字:select inet_aton("192.168.1.1");

结果

+--------------------------+

| inet_aton("192.168.1.1") |

+--------------------------+

|  3232235777 |

+--------------------------+

数字转换成为ip地址

select inet_ntoa(3232235778);

结果为:

+-----------------------+

| inet_ntoa(3232235778) |

+-----------------------+

| 192.168.1.2       |

+-----------------------+

5.主键的类型选择

其实这是一个老生常谈的话题了。中国人,尤其他妈的某些拿着纳税人钱的传统IT企业,唉~总是对这些要命的细节,趋之若鹜!

基本上在单点使用的时候使用自增类型的无符号整型。它是单点最好的选择。而使用字符串,尤其是随即字符串——UUID,读写性能下降都比较大。

而在集群中就要根据场景视情况而决定是用整型还是UUID。整型比UUID多的步骤就是需要知道全局的主键标示是什么,也就是需要加锁(无论是排他还是读写锁),免不了锁的开销。其他多余的逻辑开销基本没有,而从底层存取来说,随着数据量的增大,整型的优势明显,如果并发量很大,而又是追求吞吐量,UUID优势略微明显。如果数据量也很大,并发量有很大,读多写少的情况,基本上采用整型。如果读写频率相当一致,就该考虑要牺牲一下数据的一致性和准确性保证吞吐量了,那么UUID和整型,基本上在此场景下都差不多了。

6.设计表结构的一些原则

1)字段个数尽量少:

很久以前..久以前..久以前,老师也是这么告诉我们的。表的字段别太多啊,字段多了,把它拆成两个表,拆分后的表如果字段还是很大,继续拆。那么什么是大表,怎么就叫做字段过多?笔者想,这个应该没有标准答案,读者可能处于不同的行业,不同的业务场景。这里给出笔者的经验和笔者的一家之言吧~姑妄言之姑妄听之。那些谩骂者,可以随时拍砖!!!

行业

场景

主表的平均字段个数

备注

传统IT

MIS系统

16~25

OA最具代表性

 

财务系统

20~25

出纳、工资

 

资产管理系统

8~14

如果需要资源拓扑,表可以很多,但是每个表字段需要尽量少

 

组织机构管理

6~12

需要树形自关联,就是像递归

 

功能菜单

4~8

功能树,或者tab页那种

 

BOSS综合业务处理系统

12~40

因为BOSS系统的业务相当复杂,以业主用户作为主表,其他的围绕着业主的种种增值性的业务也相当于主表了,所以这个区间相当大

 

工作流系统

16~24

这个存疑,开源的工作流,如:JBPM,主表基本维持在12个左右。

 

银行系统-核心的用户信息

16~25

开户的时候,填写的信息比较多,但是呢,它是分别存储的,而且各银行表结构这个不统一,但大体上和BOSS差不多

互联网

SNS

10~15

主表应该将用户的profile也算上,此时参考的是apacheshindig框架规范。但是它用了JPA的规范,生成的表,外键较多

 

微博

12~16

社交里面的从表基本上就有微博的功能,那么从表在此case中变为了主表,主要还是在处理外键关系,基本上多出的字段都是外键关联

 

内容发布-新闻

8~14

可以参考一下openCMS里的主表。

 

论坛BBS

10~16

如果不在这个区间内,想想是否有表拆分的余地

……

……

……

需要大家的补充与修正了,一个人的精力有限,涉猎再广,也有没接触过的盲点!

 

 

 

 

存储引擎要想将数据返回给Mysql服务层需要经过数据行缓冲,服务器层要将缓冲的内容解码(因为数据是从存储引擎返回的)为固定的各个列。

<!--EndFragment-->


mysql 性能调优_第5张图片
 

如果列的个数很多,那么呢,这个数据行转换的代价,比较高!尤其是变长结构——特指myisaminnodb的变长结构,最具代表性,使用频率最高的的——varchar

1)关联尽量少:

这个嘛,笔者是觉得,要根据自己的业务特点来!Mysql每个主表关联的操作,只能深到61个从表深度(记住,这里是深度,有点像Java的栈深度,不是广度哦)。笔者就在想,哪个应用能复杂到关联到这么深度?除非是这么个实际场景——你查到李tian yi(哦,现在媒体称李某某)的各种身份、年龄是假的,一旦有确凿的证据!先挖到他老子,他妈身上,之后再深度关联挖掘,挖出一堆有裙带性的贪官,顺着这些贪官再继续挖下去,估计不止61层的深度能明细得了吧,这就是“大国”的特色,水很深,别问,别想,别听,这世界不公平的事情多了去了。

书归正传,《高性能Mysql》的作者们提倡一般关联深度是12层以内。

2)枚举嘛

这个不必说了,大家在真实项目中用几次就知道枚举的实际场景,还有就是什么时候用枚举,什么时候用外键关联常量表(数据字典)。是、否;男、女、性别不明,这几个备选基本上几万年不会变吧!

一个人哪个国家的,这个还是常量表比较合适!万一呢,哪天因为战争,某大国(你懂得……)不复存在了,咱们的数据库枚举还得用alert去阻塞应用修正。

还有一点就是如果使用SET,那么要知道SET里面的元素是没有互斥性的,如果具有互斥性,那么还是使用枚举比较适合。

3)是否反范式化?

符合范式的优点:

1:查询的时候,只查询你所关心的表的局部字段数据,而不需要关联的多次IO,去别的表查询关联数据

2:对于更新操作,只需要关心更改的数据,而不是将整个大表中的某个记录进行行锁定进行更新,其实这里面已经蕴含着分桶,分堆管理的读写分离思想了。

3:基本上表都是小表,如果不是全局数据集的话,基本上可以放置在数据库的查询缓存中,也就是内存中进行缓存了。

4:范式化的表,基本上都是按照某种外键进行了分组,相当于在表设计的时候就进行了group by操作。那么查询SQL语句的复杂度,就可以简简单单的根据一个where 外键 某值,进行查询。

符合范式的缺点:

1:其实也是第一个优点的反面case,当一个查询需求需要全字段的数据的时候,不得不多次地随机IO的去关联表去查询整体的实体信息,比如SNS中的用户的profile信息。而反范式的话,那么都在一个表内,基本上都是(小部分不是)顺序IO,磁盘读取数据很快。

2:关联表越多,索引的工地不过关,那么可能造成关联索引,或者聚合索引失效。

不符合范式的优点与缺点正好是符合范式的颠倒,在此不赘述。

实际开发中基本是:根据项目业务场景,范式+反范式=混合范式使用。在产品、项目的不同阶段、数据量不同、用户量不同、并发量不同,会采取不同的混合策略。

1.牺牲数据时效性,换来高性能

如果各位朋友读过笔者的《Web站点单点压力优化》那几篇文章的话,应该记得其中有一个优化的环节。

在此再重复一下,当时是查询一个表的多条记录,用于grid列表展示,那么每个业务基本包含了两个查询事务,一个是用于分页的查询select count记录;另一个用于查询记录的普通select * from操作。随着记录增加,两次查询成本过高,虽然在同一个业务,但是却是两个事务,那么此时数据隐含着已经是有不一致的情况了,所以笔者干脆将count的记录放到了缓存表——memroy中,表名tableinfo,里面仅仅记录了表名,总记录数两个字段,定时任务定时执行select count语句,将结果赋值给该表的记录。

CREATE TABLE `tableinfo` (

  `tablename` varchar(40) NOT NULL,

  `datacount` int(10) DEFAULT NULL,

  PRIMARY KEY (`tablename`),

  UNIQUE KEY `tablename` (`tablename`) USING HASH

) ENGINE=MEMORY DEFAULT CHARSET=utf8;

该引擎是memroy,索引键是表名,索引类型,hash。每次启动应用的时候,也会扫描一遍业务表的总记录数,将最新的总记录数赋值给该表的记录。这样,每次执行count就可以从该表进行查询了,只要数据库服务不重启,该汇总表都是有效的。需要说明的是,该汇总信息肯定是牺牲数据的时效性的。

2.分桶的思想

其实分桶的思想在程序界处处可见,比如Java并发包的并发HashMap。将此思想应用到数据库理论中呢,其实就是读写分离的思想。

《高性能Mysql》里面的那个demo,实在已经很经典了。咱们呢,用一张图来阐述作者的意图吧。

 

<!--EndFragment-->


mysql 性能调优_第6张图片
 

大家看一下,这种思想无论是代码中还是数据库,都是可以互相移植,互相借鉴的。

1.总结

最近有点忙,这第二篇的总结有点仓促,还好,后续章节慢慢还会继续总结。

其实,根据经验来看啊~优化schema的收益,却是比优化其他方便,带来的收益比较大。缺点也很明显,表结构变了,你的SQL有可能也要随之修改。这也是为什么在很多互联网公司,普通的研发人员,没有设计,修改表结构的权限,只有DBA才有这个权利。如果要修改表的结构,需要严格的审批流程。DBA对表结构有严格的控制权,没有说服力的或者拍脑袋就做表设计的团队,后续数据量上来了,再去修正schema,尤其是在很多个集群库的场景下,代价比较大。

 

PS:大家推荐一款比较好的画图工具吧,笔者总感觉画个图比较费时间,还画的比较难看!哪位朋友的审美是“苍老师”教的?怎么画出好看的图,希望不吝赐教!后续我们会讨论重点,索引

<!--EndFragment--><!--EndFragment-->

你可能感兴趣的:(mysql)