选取书中特别有代表性的点来讲:
一:Mysql逻辑架构
mysql的架构解析:
当一个sql请求从客户端请求过来,先经过连接池,连接池如果有空闲的线程,则可以直接用,如果没有就新创建一个建立连接,如果是查询类的sql,会先检查是否命中缓存,有缓存则直接返回缓存结果给客户端,如果没有命中缓存,则经过解析器解析sql语法,会解析出关键字和非关键字,解析sql是否合法,不合法会直接报错终止线程,合法的话会进入优化器,优化器会为sql生成最优的执行计划,例如选择最合适的索引,生成执行计划后调用存储引擎的API,让存储引擎开始工作,即到文件系统下进行数据的读取和写入,并最终把结果返回给客户端,如果是查询类sql还会把结果缓存下来。
二:事务
事务的ACID特性
原子性(atomicity),事务要么全部提交,要么全部回滚
一致性(consistency),数据库总是从一个一致性的状态转换到另外一个一致性的状态
隔离性(isolation),通常一个事务所做的修改在最终提交以前,对其他事务是不可见的(也有例外,隔离性最低等级未提交读对其他事务是可见的)
持久性(durability),一旦事务提交,则其所做的修改就会永久保存到数据库中。
注:实际应用中,要实现ACID全部特性非常难,一个兼容ACID的数据库系统,需要做很多复杂但可能用户并没有觉察到的工作,才能确保ACID的实现。平时根据你自己业务是否需要事务,来选择合适的存储引擎。innodb支持事务,myisam不支持事务,但大部分场景都是用innodb的,mysql5.5以后的版本都默认innodb引擎
隔离级别
ACID特性中的隔离性分四个等级,由低到高(级别越高,安全性越高,级别越低,并发能力越好,性能开销低):
READ UNCOMMITTED(未提交读)
将事务设置为未提交读等级
事务A
事务B
结论:事务B更新了一条记录,但是没有提交,此时事务A可以查询出未提交记录,造成脏读现象。未提交读是最低的隔离级别,很少应用到
READ COMMITTED(提交读)
将事务设置为提交读等级
事务A
事务B
结论:已提交读隔离级别解决了脏读的问题,但是出现了不可重复读的问题,即事务B更新了数据前后,事务A在两次查询的数据不一致
REPEATABLE READ(可重复读)
实验略。可重复读隔离级别解决了脏读和不可重复读问题,它只允许读取已提交记录,而且在一个事务两次读取一个记录期间,其他事务部的更新该记录。但该事务不要求与其他事务可串行化。例如,当一个事务可以找到由一个已提交事务更新的记录,但是可能产生幻读问题。幻读就是事务A提交事务后再次查询,如果其他事务插入新行,那么会出现新行产生幻读问题。(注意是可能,因为数据库对隔离级别的实现有所差别)。mysql是默认这种隔离级别。
SERIALIZABLE(可串行化)
实验略。它通过强制事务串行执行,比如在事务A执行过程中会禁止其他事务插入数据,避免了前面说的幻读的问题。SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。
事务死锁
死锁是指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。多个事务同时锁定同一个资源时,也会产生死锁。举例:事务A先更新id=1再更新id=2,事务B同时先更新id=2再更新id=1,两个事务互相占有对方资源,然后请求对方资源,产生死锁。
三、数据类型
数据类型选择
更小的通常更好。
应该尽量使用可以正确存储数据的最小数据类型。例如能用smallint尽量不用int,因为更小的数据类型通常更快,它们占用更少的磁盘、内存和CPU缓存,并且处理时需要的CPU周期也更少。
简单就好
简单数据类型的操作通常需要更少的CPU周期,例如不用字符串,而是用更简单的整型存储IP地址
尽量避免NULL
当可为NULL的列被索引时,每个索引记录需要一个额外的字节,因此可为NULL的列会使用更多的存储空间。
几种重要的数据类型
整型
整型取值范围
MySQL可以为整数类型指定宽度,例如INT(11),对于存储和计算来说,INT(1)和INT(5)是相同的,区别在于字段类型加入 zerofill 参数后,值1234在INT(1)显示为1234,在INT(5)显示为01234,即不够宽度在前面补0
实数
FLOAT和DOUBLE类型支持使用标准的浮点运算进行近似计算。
DECIMAL类型用于存储精确的小数。
浮点类型由于取值范围的影响,会导致实际保存与我们期望的不符合,FLOAT类型整数超过6位就会有误差,所以金钱相关的尽量用DECIMAL类型
DECIMAL因为需要额外的空间和计算开销,所以应该尽量只在对小数进行精确计算时才使用DECIMAL。但在数据量比较大的时候,可以考虑使用BIGINT代替DECIMAL,将需要存储的货币单位根据小数的位数乘以相应的倍数即可
字符串类型
VARCHAR类型
优点:用于存储可变长字符串,越短的字符串使用越少的空间,VARCHAR节省了存储空间,所以对性能也有帮助。
缺点:VARCHAR需要使用1或2个额外字节记录字符串的长度,由于行是变长的,在UPDATE时可能使行变得比原来更长,这就导致需要做额外的工作,会产生碎片
使用场景:字符串列的最大长度比平均长度大很多;列的更新很少,所以碎片不是问题;使用了像UTF-8这样复杂的字符集,每个字符都使用不同的字节数进行存储。
CHAR类型
使用场景:CHAR非常适合存储密码的MD5值,因为这是一个定长的值。对于经常变更的数据,CHAR也比VARCHAR更好,因为定长的CHAR类型不容易产生碎片。对于非常短的列,CHAR比VARCHAR在存储空间上也更有效率。例如用CHAR(1)来存储只有Y和N的值,如果采用单字节字符集(5)只需要一个字节,但是VARCHAR(1)却需要两个字节,因为还有一个记录长度的额外字节。
日期和时间类型
DATETIME和TIMESTAMP的比较
TIMESTAMP比DATETIME占空间小。DATETIME是使用8个字节的存储空间,TIMESTAMP是使用4个字节的存储空间。
TIMESTAMP的范围比DATETIME小得多。DATETIME能保存大范围的值,从1001年到9999年,精度为秒。TIMESTAMP只能表示从1970年到2038年,但这种范围已经可以满足大部分日常使用。
TIMESTAMP显示方式比DATETIME更灵活。DATETIME存什么显示什么,TIMESTAMP存储的是时间戳,显示mysql所在时区的时间,如果储存时的时区和检索时的时区不一样,那么拿出来的数据也不一样。
范式反范式的优点和缺点
范式的优点
范式化的更新操作通常比反范式化要快。
当数据较好地范式化时,就只有很少或者没有重复数据,所以只需要修改更少的数据。
范式化的表通常更小,可以更好地放在内存里,所以执行操作会更快。
很少有多余的数据意味着检索列表数据时更少需要DISTINCT或者GROUP BY语句。
范式的缺点
通常需要关联。稍微复杂一些的查询语句在符合范式的schema上都可能需要至少一次关联,也许更多。这不但代价昂贵,也可能使一些索引策略无效
反范式的优点
反范式化的schema因为所有数据都在一张表中,可以很好地避免关联。
如果不需要关联表,则对大部分查询最差的情况——即使表没有使用索引——是全表扫描。当数据比内存大时这可能比关联要快得多,因为这样避免了随机I/O(14)。
单独的表也能使用更有效的索引策略。
反范式的缺点
当MySQL需要扫描表字段的索引,对于每一行找到的数据,将需要到表里检查数据是不是符合条件。如果只有一小部分符合条件是效率低下
通过是范式化和反范式化两者混用
四、索引
索引大大减少了服务器需要扫描的数据量。
索引可以帮助服务器避免排序和临时表。
索引可以将随机I/O变为顺序I/O。
B+tree索引
B-tree是从最简单的二叉树进化到平衡二叉树,再进化到多路平衡查找树,即B-tree,最后进化到B-tree改进版B+tree,innodb采用的就是B+tree
B-tree的非叶子节点只存储键值信息,这样节省了磁盘块的空间,可以实现更多路,使树的高度更小,加快查找速度
所有叶子节点之间都有一个链指针。
数据记录都存放在叶子节点中
索引命中情况
全值匹配
全值匹配指的是和索引中的所有列进行匹配
匹配最左前缀
只使用索引的第一列
匹配列前缀
只匹配索引列的值的开头部分
匹配范围值
只使用了索引的第一列进行范围查找
精确匹配某一列并范围匹配另外一列
索引命中限制
如果不是按照索引的最左列开始查找,则无法使用索引
不能跳过索引中的列
如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找
高性能的索引策略
独立的列,索引列不能是表达式的一部分,也不能是函数的参数。WHERE actor_id + 1 = 5;是一个表达式
选择合适的索引列顺序
以下面的查询为例:
是应该创建一个(staff_id,customer_id)索引还是应该颠倒一下顺序?如下所示,
根据经验法则,应该将索引列customer_id放到前面,因为对应条件值的customer_id数量更小。
聚簇索引
下图展示了聚簇索引中的记录是如何存放的。注意到,叶子页包含了行的全部数据,但是节点页只包含了索引列。
如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引。InnoDB只聚集在同一个页面中的记录。包含相邻键值的页面可能会相距甚远。