存储引擎
Attribute |
MyISAM |
Heap |
BDB |
InnoDB |
Transactions |
No |
No |
Yes |
Yes |
Lock granularity |
Table |
Table |
Page (8 KB) |
Row |
Storage |
Split files |
In-memory |
Single file per table |
Tablespace(s) |
Isolation levels |
None |
None |
Read committed |
All |
Portable format |
Yes |
N/A |
No |
Yes |
Referential integrity |
No |
No |
No |
Yes |
Primary key with data |
No |
No |
Yes |
Yes |
MySQL caches data records |
No |
Yes |
Yes |
Yes |
Availability |
All versions |
All versions |
MySQL-Max |
All Versions |
To make it easier to follow the unique characteristics of each storage engine, I created this magic quadrant diagram:
Below are some examples of using the best storage engine for different tasks:
Search Engine - NDBCluster
Web stats logging - Flat file for the logging with an offline processing demon processing and writing all stats into InnoDB tables.
Financial Transactions - InnoDB
Session data - MyISAM or NDBCluster
Localized calculations - HEAP
Dictionary - MyISAM
MyISAM设计为处理读频率远大于写频率的情况,查询性能非常好;不支持事务,没有REDO、UNDO日志;支持表锁;
每个表使用独立的文件,索引和数据都存放在不同文件中,文件的存储以file block(页)的形式存储。只缓存索引,并不缓存实际数据,每次读取数据时要使用磁盘IO,索引在内存中以cache block形式组织,与file block对应。索引的缓存使用LRU算法管理,为了提高缓存的利用率,支持将缓存分成多个区域,例如分成Hot Area、Warm Area
key_buffer_size:设置缓存总大小
key_buffer_block_size:设置cache block大小
key_cache_division_limit:以百分比的形式将整个缓存区划分为多个区域。系统默认为100,即只有Warm Area
key_cache_age_threshold:控制各区域中的何时被降级,值越小,越容易降级到下一级area中
表的扫描分为Sequential Scan和Radom Scan 2种方式,read_buffer_size设置sequential scan时使用的缓存,read_rnd_buffer_size设置radom scan时使用的缓存
InnoDB设计用于高并发读写情况,支持行锁(必须有索引支持);支持事务安全性,具有REDO、UNDO日志,具备故障恢复能力,其事务实现了SQL92的4个级别;支持外键,实现了数据库的引用完整性特性;
Adaptive Hash Index:InnoDB自动检测索引状况,如果发现可以通过hash index提高效率,会在内部创建一个基于B-Tree的hash index,并根据B-Tree索引的变化自动调整。hash index并不基于整个B-Tree创建,只针对其中的某部分;并不会存储到磁盘,仅创建在缓存区中
InnoDB的数据文件支持共享表空间和独享表空间2种模式,数据和索引存储在一起,支持数据和索引的缓存。存储结构从大到小依次为tablespace->segment->extent->Page,page默认为16KB,每个extent包含64个page,每个segment存放同一种数据,一般每个表存放于一个单独的segment中
锁机制有表锁(MyISAM)、页锁(BDB)、行锁(InnoDB)三种
表锁:
有4个队列记录锁的使用情况:Current read-lock(当前读锁队列), Pending read-lock(等待读锁的队列), Current write-lock(当前写锁队列), Pending write-lock(等待写锁的队列)
读锁、写锁:
a). Current write-lock中当前资源的写锁会阻塞读锁和写锁请求.
b). Pending write-lock中WRITE类型的写锁会阻塞除了READ_HIGH_PRIORITY类型外的所有读锁请 求;READ_HIGH_PRIORITY类型的读锁比WRITE类型的写锁优先级高,因此它会阻塞Pending write-lock中所有的写锁请求;除了WRITE类型的写锁,Pending write-lock中其他类型的写锁优先级比读锁低(提高查询的响应时间)
c). Current write-lock中对资源的写锁类型为WRITE_ALLOW_WRITE时,允许除了WRITE_ONLY之外的所有读锁和写锁请求
MyISAM是MySQL官方开发的存储引擎,完全使用MySQL自己的表锁机制,其他几种支持事务的存储引擎都是让MySQL将锁处理交由存储引擎自行 实现,他们在MySQL中仅持有WRITE_ALLOW_WRITE类型的锁,至于锁的定义、并发冲突控制等都由各存储引擎处理
MyISAM表锁优化提示:MyISAM表锁读写互相阻塞,写锁优先级高于读锁
a). 参数选项low_priority_updates设置写锁优先级比读锁低,用于保证查询响应速度
b). 参数选项concurrent_insert配置是否使用并发插入特性,可以实现并发的读取和插入操作,配置值: 0:不允许并发插入; 1:数据文件中不存在空闲空间的时候可以在文件尾部进行并发插入; 2:不管数据文件是否存在空闲空间,均允许在文件尾部进行并发插入(插入操作将一直在文件尾部进行,中间的空闲空间无法利用,适用于删除操作很少的表)
InnoDB的行锁:
不是MySQL实现的锁机制,行锁都由其他存储引擎实现,这里以InnoDB为例(不同存储引擎实现机制也不一样)
Oracle的行锁是在物理块的事务槽中记录锁信息,而InnoDB是在索引键值的起始、结束位置上记录锁信息(间隙锁),所以InnoDB的行锁只是利用索引实现的一个范围锁,而利用索引可以定位到数据行
锁类型以及排他性:
|
共享锁(S) |
排他锁(X) |
意向共享锁(IS) |
意向排他锁(IX) |
共享锁(S) |
兼容 |
冲突 |
兼容 |
冲突 |
排他锁(X) |
冲突 |
冲突 |
冲突 |
冲突 |
意向共享锁(IS) |
兼容 |
冲突 |
兼容 |
兼容 |
意向排他锁(IX) |
冲突 |
冲突 |
兼容 |
兼容 |
InnoDB行锁潜在的问题:
a). 如果无法使用索引信息,InnoDB将使用表锁
b). 当索引不是确定到某一行数据时,InnoDB锁定的是索引匹配到的整个范围内的数据。例如使用索引定位到了10条记录,而加上非索引的条件可以准确确定到一条记录,这种情况下InnoDB仍然锁定这10条记录
事务隔离级别:
InnoDB实现了SQL92的4个隔离级别:Read UnCommited, Read Commited, Repeatable Read, Serializable
InnoDB有死锁检测机制,将发生死锁的2个事务中较小(修改数据量比较少)的那个作为死锁牺牲品。当然只限于InnoDB存储引擎范围之内,跨存储引擎的死锁只能通过死锁超时设置进行处理
索引MySQL主要有4类索引:B-Tree索引、Hash索引、Fulltext索引、R-Tree索引
B-Tree索引: 通用索引类型
InnoDB中的B-Tree索引分为Cluster形式的Primary Key和Secondary Index,与SQL Server类似,Primary Key索引的叶节点是实际数据文件,按索引顺序排列,Secondary Index的叶节点只存储Primary Key值。MyISAM的主键索引和非主键索引没什么区别,与InnoDB的Secondary Index类似,只是其叶节点存放的不是PK值,而是直接定位到数据行的信息
Hash索引:
将数据的索引键值进行hash运算建立索引,查询匹配时将查询条件也做hash运算,比较hash值进行匹配。主要是Memory和NDB Cluster存储引擎使用
Hash索引的查询效率非常高,因为不需要像B-Tree一样从根匹配到页节点(《MySQL性能调优与架构设计》中说hash索引可以一次定位要查找的记录,这种说法可能存在问题,Hash索引的组织、hash值的匹配同样需要数据结构和算法的实现,不可能一次定位,设想hash索引以B-Tree方式组织,比B-Tree索引优秀的地方可能是其B-Tree数据量会小,即使这样也可能意味着hash冲突的存在,其效率比B-Tree索引高的说法是有待验证的,可能需要有不少前提条件)
缺点:只能进行等值匹配;hash索引是无序的,可能需要额外的排序操作;无法进行部分匹配,只能全索引匹配;遇到hash冲突后效率可能会比较低
Fulltext索引: 只有MyISAM的CHAR, VARCHAR, TEXT三种数据类型支持fulltext索引,主要用于优化效率比较低的like '%***%'操作
MySQL的fulltext中文支持有待考察,fulltext索引的创建成本比较高
R-Tree索引: 用于空间数据检索
MySQL有空间数据类型GEOMETRY(5.0.16之前只有MyISAM支持,之后BDB、InnoDB、NDB Cluster、Archieve等支持),只有MyISAM存储引擎支持R-Tree索引
查询优化器Explain:在查询语句前加EXPLAIN查看查询计划,各操作解释参考《MySQL性能调优与架构设计》P131
Profiling:
a). set profiling=1; 开启Query Profiler
b). 执行查询
c). show profiles; 显示已记录的profile信息列表
d). show profile *** for query n; 例如show profile cpu, block io for query 6; 其中n为c)中的Query_ID
MySQL查询优化器在CBO(Cost Based Optimizer)增加了Heuristic Optimize(启发式优化),即增加了RBO(Rule Based Optimizer)功能,主要优化处理步骤与SQL Server有点相似
JOIN算法:只支持Nested Loop方式,有2种类型
a). simple nested-loop join (NLJ),假如有t1, t2, t3 3个表join,则算法为:
for each row in t1 matching range {
for each row in t2 matching reference key {
for each row in t3 {
if row satisfies join conditions,
send to client
}
}
}
即直接使用3个嵌套循环进行匹配
b). Block Nested-Loop Join (BNL),具体算法描述为:
for each row in t1 matching range {
for each row in t2 matching reference key {
store used columns from t1, t2 in join buffer
if buffer is full {
for each row in t3 {
for each t1, t2 combination in join buffer {
if row satisfies join conditions,
send to client
}
}
empty buffer
}
}
}
if buffer is not empty {
for each row in t3 {
for each t1, t2 combination in join buffer {
if row satisfies join conditions,
send to client
}
}
}
使用BNL的条件: 系统参数join_buffer_size有设置;MySQL的join类型为all(全表扫描)、index(全索引扫描)、range(索引区间扫描);进行join的记录能够放入join buffer中(记录太大无法放入join buffer无法使用BNL);内存满足join buffer分配
因为MySQL只有Nested Loop Join,如果内层表(被驱动表)没有索引或者无法使用索引等,就根本不适合使用Nested Loop,BNL主要用于处理这种情况。上面例子中如果t3没有索引,采用NLJ算法时在最内层每次都要对t3全表扫描,而使用BNL时,假如join_buffer_size可以存储100条中间结果,与NLJ相比每100次全表扫描t3可以减少为1次
ORDER BY算法:有2种,
a). 将排序字段和数据行定位信息读取到排序区(sort_buffer_size设定)中进行排序,完成排序后根据行定位信息从读取查询所需的列。缺点是有2次读取数据的操作
b). 将所需的列都读取到排序区进行排序,排序完后直接将结果返回。缺点是如果所需的列空间占用多,将消耗排序区空间影响排序性能
可以通过max_length_for_sort_data控制排序算法
另外一种对order by的处理是排序字段位于有序的索引中(例如不是hash索引等),索引本身是有序的因此不需要额外的排序操作
GROUP BY算法: 有3种,
a). 使用临时表。创建临时表,放入要group by的数据,按照group by的字段进行排序,生成group by结果集以及计算聚合函数的结果
b). Loose Index Scan
使用条件是针对单表的查询;group by的字段按照顺序前导匹配某个索引;索引必须是有序的;聚合函数只包含min、max,字段必须相同并且在索引中,并且是索引中紧跟着group by字段的下一个字段;索引字段中除了group by的字段外,其他字段如果出现在where条件中,where条件只能是常量值。MySQL 5.4.4加入了几个新的聚合函数:AVG(DISTINCT), SUM(DISTINCT), 和COUNT(DISTINCT),也是有限制性的
这种方式只需要部分扫描索引即可完成group by操作,如果where子句中只有group by字段的条件,则索引扫描的层级只需要到group by包含的字段(如果索引除了group by字段之外还包含其他字段,这种处理方式根本不需要扫描到索引页节点等层级)。正因为索引中包含的信息有限,因此该算法对聚合函数、索引的要求非常多,适用的场景比较窄,但是效率是最高的
例如表t1(c1,c2,c3,c4),有索引idx(c1,c2,c3),下面语句可以使用Loose Index Scan:
SELECT c1, c2 FROM t1 GROUP BY c1, c2;
SELECT c1, MIN(c2) FROM t1 GROUP BY c1;
SELECT c1, c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;
SELECT MAX(c3), MIN(c3), c1, c2 FROM t1 WHERE c2 > const GROUP BY c1, c2;
SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;
SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2;
c). Tight Index Scan
其实就是利用有序索引的特性,避免创建临时表以及额外的排序操作,因此前提条件是能够利用有序索引;仅通过索引扫描可以完成查询
例如表t1(c1,c2,c3,c4),有索引idx(c1,c2,c3),下面2个语句都可以使用Tight Index Scan进行group by:
SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3;
SELECT c1, c2, c3 FROM t1 WHERE c1 = 'a' GROUP BY c2, c3;
在EXPLAIN中,Extra中出现Using index for group-by表示使用Loose Index Scan实现;Using Temporary表示使用临时表实现;否则表示使用Tight Index Scan实现
SQL Server 7只有基本的group by算法,即先排序然后求group by结果集;SQL Server 2000引入了使用hash方法计算group by。而MySQL则是在这个基本算法基础上稍微拓展出了2种优化处理方法
DISTINCT算法:
distinct算法与group by基本相同,同样可以使用Loose Index Scan、Tight Index Scan、临时表实现,只是distinct只需要从group by结果中取出一条记录,不需要聚合运算等。算法的不同之处在于distinct算法本身并不要求排序操作(查询中包含group by、max聚合运算等其他操作可能要求排序)
索引选择:与SQL Server类似,使用统计信息进行索引选择,支持query hints调优
参考:
《MySQL性能调优与架构设计》
MySQL存储引擎比较MySQL ref: Nested-Loop Join AlgorithmsMySQL ref: Loose Index ScanMySQL ref: Tight Index Scan