逻辑架构
MySQL 的整体架构如下:
每个客户端连接在服务器进程中都有自己的线程,每个连接所属的查询都会在指定的单独线程中完成。服务器会控制缓存线程,因此不需要每个连接都重建或撤销。
连接时服务器会进行用户名等认证。连接后服务器会先查看查询缓存中是否有现成的结果,如果没有,则会再次进行分析,优化,执行。注意这个顺序,不是先分析语句再查缓存,而是先查缓存,如果没有结果,再去分析语句。
所以,select * from a where id = 1 and status = 1
; 这条语句和 select * from a where status = 1 and id = 1
两条语句的缓存是不一样的,因为缓存是在语义分析之前执行的。
分析器会解析SQL语句,而优化器进行各种优化,如:查询的重写,决定查询的读表顺序,选择相应的索引等。这个过程是自动的,但用户可以通过特殊的关键字给优化器传递各种提示来影响它的决策。
优化器优化时并不知道表是什么引擎,所以它不关心这些。而存储引擎自身也会对查询进行优化。
存储引擎有多种,它们处理数据的方式各不相同。同时,提供给上层使用的功能也不大一样。比如 InnoDB 引擎支持事务,MyISAM 则不支持。
各存储引擎有不同的API用来和上层交互。比如:开始事务,获取某条数据。它不负责SQL语句解析,只是响应请求返回结果。
数据类型
MySQL 支持不同的数据类型。选择正确的类型对性能影响非常大。
在能存储够数据时,用越小的类型性能越好。
尽量用简单的数据类型。如时间存储就用内建的类型,不要用字符串;另外,IP用整数来保存,不要用字符串。
尽量避免 NULL。都定义成 NOT NULL。MySQL 很难优化引用了可空列的查询,它会使索引更复杂。而且可空列需要更多的空间及更特殊的处理。如果必须用,最好用 0 或特殊字符串来代替。
整数
如果存储整数,可用下面几种整数类型:TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT, 它们分别需要 8, 16, 24, 32, 64 位存储空间。它们能存储的数值范围是 -2^(N-1) 到 2^(N-1), N是空间位数。
另外,整数类型还有个可选的 UNSIGNED 属性,它表示不可为负,并大致把正数的上限提高一倍。如 TINYINT UNSIGNED 的范围变成 0-255, 而不是 -127-128 MySQL 可以对整型定义宽度,如:INT(11), 但它的存储和计算,占的空间和 INT(1), INT(20) 是一样的。只不过它规定了MySQL 的客户端工具用来显示字符的个数。
实数
实数有分数部分。可以使用 DECIMAL 保存比 BIGINT 还大的整数。同时支持精确与非精确的类型。
Float 和 Double 支持近似计算,是不精确的。
DECIMAL 类型用于保存精确的小数。服务器自身对 DECIMAL 进行运算,因为CPU不支持对它进行直接运算。而浮点数则可以在CPU中运算所以会更快一些。通常只有需要对小数进行精确计算的时候才使用 DECIMAL, 如金融数据。
varchar
可变长度,使用最为广泛。它比固定长度类型占用更少的存储空间。比如声明为 varchar(20),了但如果字段长度只有 2, 它也就只占 2 个位置。不会占到 20。
char
不管内容有多长。总是占用固定长度的空间。容易造成浪费。存储长度相似的字条串时好用。如MD5加密后的用户密码。另外,由于长度固定了,当字段频繁被更新时,也不会产生碎片。而且 char 效率要好于 varchar。
BLOB 和 TEXT
用二进制和字符形式来保存大量数据。它们的区别是一个是二进制形式,没有字符集和排序规则,TEXT有字符集和排序规则。
MySQL 对 TEXT 的排序和其它方式不同,它不会按照字符串完整长度进行排序,而只是按照 max_sort_length
规定的前若干个字节进行排序。若想只按前几个字节排序,可以减少该参数的值,或者使用:order by substring(column, length)
ENUM
ENUM 列可以存储 65535 个不同的字符串。而且 MySQL 以非常紧凑的方式保存它们,根据表中值的数量,MySQL 甚至会把它们压缩到 1-2 个字节中。
MySQL 在内部把它们保存为整数,表示值在列表中的位置,并且保存一份查找表来表示整数和字符的映射关系。另外,ENUM 列排序是按数字顺序排的,不是按字符串。
同上,在使用时,服务器会额外进行一次数值和字符的关联才能找到,所以会有额外的开销。特别是在某些连接查询中,具体取舍就要看性能和占用空间两个的具体值了。
日期和时间
MySQL 有多种类型来保存日期和时间。如:YEAR, DATE。
MySQL 能存储的最小粒度是秒。MySQL 还有两种数据类型:DATETIME, TIMESTAMP。
DATETIME
该类型能保存大范围的值:1001 到 9999 年。精度为妙。它把日期和时间保存到格式为YYYYMMDDHHMMSS的整数中。与时区无关,它使用8 个字节来保存。
TIMESTAMP
它存储的是自 1970.1.1 午夜以来的秒数。它只使用 4 字节来保存。因此范围要小,只能保存 1970 到 2038 年。MySQL 提供 FROM_UNIXTIME() 把 Unix 时间戳转换为日期,也能通过 UNIX_TIMESTAMP() 将日期转换成时间戳。
TIMESTAMP 的值的显示依赖于时区。而该值为 0 时的值实际显示为美国东部时间 1969-12-31 19:00:00 与格林尼治标准时间GMT相差5小时。
由于 MySQL 的时期和时间都只能精确到秒。如果需要更精确,可以使用 BIGINT或者使用DOUBLE的小数来保存秒的分数部分。
特殊类型
IP 地址,通常保存是用 varchar(15) 来保存,但实际上,IP地址是一个 32 位的整数。而不是字符串。MySQL 提供 INET_ATON()
和 INET_NTOA()
在 IP 和 整数之间互换。