深入浅出MySQL读书笔记

SQL基础

SQL分类

SQL语句主要可以划分为以下3个类别。

  • DDL (Data Definition Language)语句:数据定义语言,这些语句定义了不同的数据段、数据库、表、列、索引等数据库对象的定义。常用的语句关键字主要包括 createdropalter等。
  • DML(Data Manipulation Language)语句:数据操纵语句,用于添加、删除、更新和查询数据库记录,并检查数据完整性,常用的语句关键字主要包括insertdeleteupdateselect等。
  • DCL(Data Control Language)语句:数据控制语句,用于控制不同数据段直接的许可和访问级别的语句。这些语句定义了数据库、表、字段、用户的访问权限和安全级别。主要语句关键字包括grantrevoke等。

MySQL数据类型

数值类型

MySQL支持所有标准SQL中的数值类型,其中包括严格数值类型(INTEGERSMALLINTDECIMALNUMERIC),以及近似值数据类型(FLOATREALDOUBLE PRECISION),并在此基础上做了扩展。扩展后增加了TINYINTMEDIUMINTBIGINT这3种支持的所有数值类型,其中INTINTEGER的同名词,DECDECIMAL的同名词。

MySQL中的数值类型

整数类型 字节 最小值 最大值
TINYINT 1 有符号-128 无符号 0 有符号 127 无符号 255
SMALLINT 2 有符号 -32768 无符号 0 有符号 32767 无符号 65535
MEDIUMINT 3 有符号-8388608 无符号 0 有符号 8388607 无符号 1677215
INT、INTEGER 4 有符号-2147483648 无符号 0 有符号 2147483647 无符号 4294967295
BIGINT 8 有符号-9223372036854775808
无符号 0
有符号 9223372036854775807
无符号 18446744073709551615
浮点数类型 字节 最小值 最大值
FLOAT 4 ±1.175494351E-38 ±3.402823466E+38
DOUBLE 8 ±2.2250738585072014E-308 ±1.7976931348623157E+308
定点数类型 字节
DEC(M,D),
DECIMAL(M,D)
M+2 最大取值范围与 DOUBLE 相同,给定 DECIMAL 的有效取值范围由 M 和 D
决定
位类型 字节 最小值 最大值
BIT(M) 1~8 BIT(1) BIT(64)

在整数类型中,按照取值范围和存储方式不同,分为 tinyint、smallint、mediumint、int、
bigint 这 5 个类型。

日期类型

MySQL中有多种数据类型可以用于日期和时间的表示,不同版本可能有所差异,下表列出了 MySQL 5.0中所支持的日期和时间类型。

MySQL中的日期和时间类型

日期和时间类型 字节 最小值 最大值
DATE 4 1000-01-01 9999-12-31
DATETIME 8 1000-01-01 00:00:00 9999-12-31 23:59:59
TIMESTAMP 4 19700101080001 2038 年的某个时刻
TIME 3 -838:59:59 838:59:59
YEAR 1 1901 2155

这些数据类型的主要区别如下:

  • 如果要用来表示年月日,通常用 DATE来表示。

  • 如果要用来表示年月日时分秒,通常用DATETIME表示。

  • 如果只用来表示时分秒,通常用TIME来表示。

  • 如果需要经常插入或者更新日期为当前系统时间,则通常用TIMESTAMP来表示。

    TIMESTAMP值返回后显示为"YYYY-MM-DD HH:MM:SS"格式的字符串,显示宽度固定为19个字符。如果想要获得数字值,应在TIMESTAMP列添加+0

  • 如果只是表示年份,可以用YEAR来表示,它比DATE占用更少的空间。YEAR有2位或4位格式的年。默认是 4 位格式中,允许的值时1901-2155 和 0000。在 2 位格式中,允许的值时 70 ~ 69,表示从 1970 ~ 2069年。MySQL 以 YYYY 格式显示 YEAR 值。

每种日期时间类型都有一个有效值范围,如果超出这个范围,在默认的 SQLMode下,系统会进行错误提示,并将以零值来进行存储。不同日期类型零值的表示如下表所示。

MySQL中日期和时间类型的零值表示

数据类型 零值表示
DATETIME 0000-00-00 00:00:00
DATE 0000-00-00
TIMESTAMP 00000000000000
TIME 00:00:00
YEAR 0000

TIMESTAMP类型的特殊之处:

  • TIMESTAMP也用来表示日期,但是和DATETIME有所不同。
  • 系统会自动给TIMESTAMP类型创建默认值 CURRENT_TIMESTAMP(系统日期)。
  • MySQL规定TIMESTAMP类型字段只能有一列的默认值为current_timestmap,如果强制修改,系统会报错误提示。
  • TIMESTAMP还有一个重要特点,就是和时区相关。当插入日期时,会先转换为本地时区后存放;而从数据库里面取出时,也同样需要将日期转换为本地时区后显示。这样,两个不同时区的用户看到的同一个日期可能是不一样的。
  • TIMESTAMP的取值范围为19700101080001到2038年的某一天,因此它不适合存放比较
    久远的日期。

TIMESTAMP和DATETIME的表示方法非常类似,区别主要有以下几点。

  • TIMESTAMP支持的时间范围较小,其取值范围从19700101080001到2038年的某个时间,而DATETIME是从1000-01-01 00:00:00到9999-12-31 23:59:59,范围更大
  • 表中的第一个TIMESTAMP列自动设置为系统时间。如果在一个TIMESTAMP列中插入NULL,则该列值将自动设置为当前的日期和时间。在插入或更新一行但不明确给TIMESTAMP列赋值时也会自动设置该列的值为当前的日期和时间,当插入的值超出取值范围时,MySQL认为该值溢出,使用“0000-00-00 00:00:00”进行填补。
  • TIMESTAMP的插入和查询都受当地时区的影响,更能反应出实际的日期。而DATETIME则只能反应出插入时当地的时区,其他时区的人查看数据必然会有误差的。
  • TIMESTAMP的属性受MySQL版本和服务器SQLMode的影响很大,本章都是以MySQL 5.0为例进行介绍,在不同的版本下可以参考相应的MySQL帮助文档。

字符串类型

MySQL 包括了 CHAR、VARCHAR、BINARY、VARBINARY、BLOB、TEXT、ENUM 和 SET 等多种
字符串类型。下表中详细列出了这些字符类型的比较。

MySQL中的字符类型

字符串类型 字节 描述及存储需求
CHAR(M) M M 为 0~255 之间的整数
VARCHAR(M) M 为 0~65535 之间的整数,值的长度+1 个字节
TINYBLOB 允许长度 0~255 字节,值的长度+1 个字节
BLOB 允许长度 0~65535 字节,值的长度+2 个字节
MEDIUMBLOB 允许长度 0 ~167772150 字节,值的长度+3 个字节
LONGBLOB 允许长度 0~4294967295 字节,值的长度+4 个字节
TINYTEXT 允许长度 0~255 字节,值的长度+2 个字节
TEXT 允许长度 0~65535 字节,值的长度+2 个字节
MEDIUMTEXT 允许长度 0 ~167772150 字节,值的长度+3 个字节
LONGTEXT 允许长度 0~4294967295 字节,值的长度+4 个字节
VARBINARY(M) 允许长度 0~M 个字节的变长字节字符串,值的长度+1 个字节
BINARY(M) M 允许长度 0~M 个字节的定长字节字符串

CHAR 和 VARCHAR 类型

CHARVARCHAR 很类似,都用来保存 MySQL 中较短的字符串。二者的主要区别在于存储
方式的不同:CHAR 列的长度固定为创建表时声明的长度,长度可以为从 0~255 的任何值;
VARCHAR 列中的值为可变长字符串,长度可以指定为0~255 (5.0.3以前)或者 65535 (5.0.3
以后)之间的值。在检索的时候,CHAR 列删除了尾部的空格,而 VARCHAR 则保留这些空格

MySQL中的运算符

MySQL 支持多种类型的运算符,来连接表达式的项。这些类型主要包括算术运算符、比较运算符、逻辑运算符和位运算符。

算术运算符

MySQL 支持的算术运算符包括加、减、乘、除和模运算。

MySQL 支持的算术运算符

运算符 作用
+ 加法
- 减法
* 乘法
/,DIV 除法,返回商
%,MOD 除法,返回余数

比较运算符

MySQL 5.0 支持的各种比较运算符。

MySQL 支持的比较运算符

= 等于
= 等于
<>或!= 不等于
<=> NULL 安全的等于(NULL-safe)
< 小于
<= 小于等于
> 大于
>= 大于等于
BETWEEN 存在与指定范围
IN 存在于指定集合
IS NULL 为 NULL
IS NOT NULL 不为 NULL
LIKE 通配符匹配
REGEXP 或 RLIKE 正则表达式匹配

逻辑运算符

逻辑运算符又称为布尔运算符,用来确认表达式的真和假。MySQL 支持 4 种逻辑运算
符,如表所示。

运算符 作用
NOT 或! 逻辑非
AND 或&& 逻辑与
OR 或 逻辑或
XOR 逻辑异或

位运算符

位运算是将给定的操作数转化为二进制后,对各个操作数每一位都进行指定的逻辑运算,
得到的二进制结果转换为十进制数后就是位运算的结果。MySQL 5.0 支持 6 种位运算符,
如表所示。

运算符 作用
& 位与(位 AND)
` ` 位或 (位 OR )
^ 位异或(位 XOR)
~ 位取反
>> 位右移
<< 位左移

运算符的优先级

优先级顺序 运算符
1 :=
2 ||, OR, XOR
3 &&, AND
4 NOT
5 BETWEEN, CASE, WHEN, THEN, ELSE
6 =, <=>, >=, >, <=, <, <>, !=, IS, LIKE, REGEXP, IN
7 |
8 &
9 <<, >>
10 -, +
11 *, /, DIV, %, MOD
12 ^
13 - (一元减号), ~ (一元比特反转)
14 !

常用函数

字符串函数

函数 功能
CANCAT(S1,S2,…Sn) 连接 S1,S2,…Sn 为一个字符串
INSERT(str,x,y,instr) 将字符串 str 从第 x 位置开始,y 个字符长的子串替换为字符串 instr
LOWER(str) 将字符串 str 中所有字符变为小写
UPPER(str) 将字符串 str 中所有字符变为大写
LEFT(str ,x) 返回字符串 str 最左边的 x 个字符
RIGHT(str,x) 返回字符串 str 最右边的 x 个字符
LPAD(str,n ,pad) 用字符串 pad 对 str 最左边进行填充,直到长度为 n 个字符长度
RPAD(str,n,pad) 用字符串 pad 对 str 最右边进行填充,直到长度为 n 个字符长度
LTRIM(str) 去掉字符串 str 左侧的空格
RTRIM(str) 去掉字符串 str 行尾的空格
REPEAT(str,x) 返回 str 重复 x 次的结果
REPLACE(str,a,b) 用字符串 b 替换字符串 str 中所有出现的字符串 a
STRCMP(s1,s2) 比较字符串 s1 和 s2
TRIM(str) 去掉字符串行尾和行头的空格
SUBSTRING(str,x,y) 返回从字符串 str x 位置起 y 个字符长度的字串

数值函数

函数 功能
ABS(x) 返回 x 的绝对值
CEIL(x) 返回大于 x 的最大整数值
FLOOR(x) 返回小于 x 的最大整数值
MOD(x,y) 返回 x/y 的模
RAND() 返回 0 到 1 内的随机值
ROUND(x,y) 返回参数 x 的四舍五入的有 y 位小数的值
TRUNCATE(x,y) 返回数字 x 截断为 y 位小数的结果

日期和时间函数

函数 功能
CURDATE() 返回当前日期
CURTIME() 返回当前时间
NOW() 返回当前的日期和时间
UNIX_TIMESTAMP(date) 返回日期 date 的 UNIX 时间戳
FROM_UNIXTIME 返回 UNIX 时间戳的日期值
WEEK(date) 返回日期 date 为一年中的第几周
YEAR(date) 返回日期 date 的年份
HOUR(time) 返回 time 的小时值
MINUTE(time) 返回 time 的分钟值
MONTHNAME(date) 返回 date 的月份名
DATE_FORMAT(date,fmt) 返回按字符串 fmt 格式化日期 date 值
DATE_ADD(date,INTERVAL expr type) 返回一个日期或时间值加上一个时间间隔的时间值
DATEDIFF(expr,expr2) 返回起始时间 expr 和结束时间 expr2 之间的天数

流程函数

函数 功能
IF(value,t f) 如果 value 是真,返回 t;否则返回 f
IFNULL(value1,value2) 如果 value1 不为空返回 value1,否则返回 value2
CASE WHEN [value1]
THEN[result1]…ELSE[default]END
如果 value1 是真,返回 result1,否则返回 default
CASE [expr] WHEN [value1]
THEN[result1]…ELSE[default]END
如果 expr 等于 value1,返回 result1,否则返回 default

其他常用函数

函数 功能
DATABASE() 返回当前数据库名
VERSION() 返回当前数据库版本
USER() 返回当前登录用户名
INET_ATON(IP) 返回 IP 地址的数字表示
INET_NTOA(num) 返回数字代表的 IP 地址
PASSWORD(str) 返回字符串 str 的加密版本
MD5() 返回字符串 str 的 MD5 值

MySQL存储引擎

MySQL 5.0 支持的存储引擎包括 MyISAM、InnoDB、BDB、MEMORY、MERGE、EXAMPLE、NDB Cluster、ARCHIVE、CSV、BLACKHOLE、FEDERATED 等,其中 InnoDB 和 BDB 提供事务安全表,其他存储引擎都是非事务安全表。

默认情况下,创建新表不指定表的存储引擎,则新表是默认存储引擎的,如果需要修改默认的存储引擎,则可以在参数文件中设置 default-table-type。查看当前的默认存储引擎,可以使用以下命令:

mysql > show variables like 'table_type';

各种存储引擎的特性

特点 MyISAM InnoDB MEMORY MERGE NDB
存储限制 64TB 没有
事务安全 支持
锁机制 表锁 行锁 表锁 表锁 行锁
B 树索引 支持 支持 支持 支持 支持
哈希索引 支持 支持
全文索引 支持
集群索引 支持
数据缓存 支持 支持 支持
索引缓存 支持 支持 支持 支持 支持
数据可压缩 支持
空间使用 N/A
内存使用 中等
批量插入的速度
支持外键 支持

MyISAM

MyISAMMySQL 的默认存储引擎。MyISAM 不支持事务、也不支持外键,其优势是访问的速度快,对事务完整性没有要求或者以 SELECTINSERT 为主的应用基本上都可以使用这个引擎来创建表。

每个 MyISAM 在磁盘上存储成 3 个文件,其文件名都和表名相同,但扩展名分别是:

  • .frm(存储表定义);

  • .MYD(MYData,存储数据);

  • .MYI(MYIndex,存储索引)。

    数据文件和索引文件可以放置在不同的目录,平均分布 IO,获得更快的速度。
    要指定索引文件和数据文件的路径,需要在创建表的时候通过 DATA DIRECTORYINDEX DIRECTORY 语句指定,也就是说不同 MyISAM 表的索引文件和数据文件可以放置到不同的路径下。文件路径需要是绝对路径,并且具有访问权限。
    MyISAM 类型的表可能会损坏,原因可能是多种多样的,损坏后的表可能不能访问,会提示需要修复或者访问后返回错误的结果。MyISAM 类型的表提供修复的工具,可以用 CHECK TABLE 语句来检查 MyISAM 表的健康,并用 REPAIR TABLE 语句修复一个损坏的 MyISAM 表。表损坏可能导致数据库异常重新启动,需要尽快修复并尽可能地确认损坏的原因。

    MyISAM 的表又支持 3 种不同的存储格式,分别是:

    • 静态(固定长度)表;

    • 动态表;

    • 压缩表。

其中,静态表是默认的存储格式。静态表中的字段都是非变长字段,这样每个记录都是固定长度的,这种存储方式的优点是存储非常迅速,容易缓存,出现故障容易恢复;缺点是占用的空间通常比动态表多。静态表的数据在存储的时候会按照列的宽度定义补足空格,但是在应用访问的时候并不会得到这些空格,这些空格在返回给应用之前已经去掉。

但是也有些需要特别注意的问题,如果需要保存的内容后面本来就带有空格,那么在返回结果的时候也会被去掉,开发人员在编写程序的时候需要特别注意,因为静态表是默认的存储格式,开发人员可能并没有意识到这一点,从而丢失了尾部的空格。

动态表中包含变长字段,记录不是固定长度的,这样存储的优点是占用的空间相对较少,但是频繁地更新删除记录会产生碎片,需要定期执行 OPTIMIZE TABLE 语句或 myisamchk -r 命令来改善性能,并且出现故障的时候恢复相对比较困难。

压缩表由 myisampack 工具创建,占据非常小的磁盘空间。因为每个记录是被单独压缩的,所以只有非常小的访问开支。

InnoDB

InnoDB 存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比 MyISAM 的存储引擎,InnoDB 写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。

  1. 自动增长列

    InnoDB 表的自动增长列可以手工插入,但是插入的值如果是空或者 0,则实际插入的将是自动增长后的值。

  2. 外键约束

    MySQL 支持外键的存储引擎只有 InnoDB,在创建外键的时候,要求父表必须有对应的索引,子表在创建外键的时候也会自动创建对应的索引。

  3. 存储方式

    InnoDB 存储表和索引有以下两种方式。

    • 使用共享表空间存储,这种方式创建的表的表结构保存在.frm 文件中,数据和索引保存在 innodb_data_home_dirinnodb_data_file_path 定义的表空间中,可以是多个文件。

    • 使用多表空间存储,这种方式创建的表的表结构仍然保存在.frm 文件中,但是每个表的数据和索引单独保存在.ibd 中。如果是个分区表,则每个分区对应单独的.ibd文件,文件名是“表名+分区名”,可以在创建分区的时候指定每个分区的数据文件的位置,以此来将表的 IO 均匀分布在多个磁盘上。

要使用多表空间的存储方式,需要设置参数innodb_file_per_table,并重新启动服务后才可以生效,对于新建的表按照多表空间的方式创建,已有的表仍然使用共享表空间存储。如果将已有的多表空间方式修改回共享表空间的方式,则新建表会在共享表空间中创建,但已有的多表空间的表仍然保存原来的访问方式。所以多表空间的参数生效后,只对新建的表生效。

多表空间的数据文件没有大小限制,不需要设置初始大小,也不需要设置文件的最大限制、扩展大小等参数。

对于使用多表空间特性的表,可以比较方便地进行单表备份和恢复操作,但是直接复制.ibd 文件是不行的,因为没有共享表空间的数据字典信息,直接复制的.ibd 文件和.frm 文件恢复时是不能被正确识别的,但可以通过以下命令:

ALTER TABLE tbl_name DISCARD TABLESPACE;
ALTER TABLE tbl_name IMPORT TABLESPACE;

将备份恢复到数据库中,但是这样的单表备份,只能恢复到表原来在的数据库中,而不能恢复到其他的数据库中。如果要将单表恢复到目标数据库,则需要通过 mysqldumpmysqlimport 来实现。

注意:即便在多表空间的存储方式下,共享表空间仍然是必须的,InnoDB 把内部数据词典和未作日志放在这个文件中。

MEMORY

MEMORY 存储引擎使用存在内存中的内容来创建表。每个 MEMORY 表只实际对应一个

磁盘文件,格式是.frmMEMORY 类型的表访问非常得快,因为它的数据是放在内存中的,
并且默认使用 HASH 索引,但是一旦服务关闭,表中的数据就会丢失掉。

在启动 MySQL 服务的时候使用--init-file 选项,把INSERT INTO ... SELECT 或 `LOAD DATA

INFILE` 这样的语句放入这个文件中,就可以在服务启动时从持久稳固的数据源装载表。

服务器需要足够内存来维持所有在同一时间使用的 MEMORY 表,当不再需要 MEMORY

表的内容之时,要释放被MEMORY表使用的内存,应该执行DELETE FROMTRUNCATE TABLE
或者整个地删除表(使用 DROP TABLE 操作)。

每个 MEMORY 表中可以放置的数据量的大小,受到 max_heap_table_size 系统变量的约

束,这个系统变量的初始值是 16MB,可以按照需要加大。此外,在定义 MEMORY 表的时候,
可以通过 MAX_ROWS 子句指定表的最大行数。

MEMORY 类型的存储引擎主要用在那些内容变化不频繁的代码表,或者作为统计操作

的中间结果表,便于高效地对中间结果进行分析并得到最终的统计结果。对 MEMORY 存储
引擎的表进行更新操作要谨慎,因为数据并没有实际写入到磁盘中,所以一定要对下次重新
启动服务后如何获得这些修改后的数据有所考虑。

MERGE

MERGE 存储引擎是一组 MyISAM 表的组合,这些 MyISAM 表必须结构完全相同,MERGE表本身并没有数据,对 MERGE 类型的表可以进行查询、更新、删除的操作,这些操作实际上是对内部的实际的MyISAM 表进行的。对于 MERGE 类型表的插入操作,是通过INSERT_METHOD 子句定义插入的表,可以有 3 个不同的值,使用 FIRSTLAST 值使得插入操作被相应地作用在第一或最后一个表上,不定义这个子句或者定义为 NO,表示不能对这个 MERGE表执行插入操作。

可以对 MERGE 表进行 DROP 操作,这个操作只是删除 MERGE 的定义,对内部的表没有任何的影响。

MERGE 表在磁盘上保留两个文件,文件名以表的名字开始,一个.frm文件存储表定义,另一个.MRG 文件包含组合表的信息,包括 MERGE 表由哪些表组成、插入新的数据时的依据。可以通过修改.MRG 文件来修改 MERGE 表,但是修改后要通过 FLUSH TABLES 刷新。

如何选择合适的引擎

下面是常用存储引擎的适用环境。

  • MyISAM:默认的 MySQL 插件式存储引擎。如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常适合的。MyISAM 是在 Web、数据仓储和其他应用环境下最常使用的存储引擎之一。
  • InnoDB:用于事务处理应用程序,支持外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询以外,还包括很多的更新、删除操作,那么 InnoDB 存储引擎应该是比较合适的选择。InnoDB 存储引擎除了有效地降低由于删除和更新导致的锁定,还可以确保事务的完整提交(Commit)和回滚(Rollback),对于类似计费系统或者财务系统等对数据准确性要求比较高的系统,InnoDB 都是合适的选择。
  • MEMORY:将所有数据保存在 RAM 中,在需要快速定位记录和其他类似数据的环境下,可提供极快的访问。MEMORY 的缺陷是对表的大小有限制,太大的表无法 CACHE 在内存中,其次是要确保表的数据可以恢复,数据库异常终止后表中的数据是可以恢复的。MEMORY 表通常用于更新不太频繁的小表,用以快速得到访问结果。
  • MERGE:用于将一系列等同的 MyISAM 表以逻辑方式组合在一起,并作为一个对象引用它们。MERGE 表的优点在于可以突破对单个 MyISAM 表大小的限制,并且通过将不同的表分布在多个磁盘上,可以有效地改善MERGE表的访问效率。这对于诸如数据仓储等VLDB环境十分适合。

选择合适的数据类型

CHAR 与 VARCHAR

CHARVARCHAR 类型类似,都用来存储字符串,但它们保存和检索的方式不同。CHAR属于固定长度的字符类型,而 VARCHAR 属于可变长度的字符类型。

CHAR(4)VARCHAR(4) 列检索的值并不总是相同,因为检索时从 CHAR 列删除了尾部的空格。

由于 CHAR 是固定长度的,所以它的处理速度比 VARCHAR 快得多,但是其缺点是浪费

存储空间,程序需要对行尾空格进行处理,所以对于那些长度变化不大并且对查询速度有较高要求的数据可以考虑使用CHAR 类型来存储。

MySQ 中,不同的存储引擎对 CHARVARCHAR 的使用原则有所不同,这里简单概括如下。

  • MyISAM 存储引擎:建议使用固定长度的数据列代替可变长度的数据列。
  • MEMORY 存储引擎:目前都使用固定长度的数据行存储,因此无论使用 CHARVARCHAR 列都没有关系。两者都是作为 CHAR 类型处理。
  • InnoDB 存储引擎:建议使用 VARCHAR 类型。对于 InnoDB 数据表,内部的行存储格式没有区分固定长度和可变长度列(所有数据行都使用指向数据列值的头指针),因此在本质上,使用固定长度的 CHAR 列不一定比使用可变长度 VARCHAR 列性能要好。因而,主要的性能因素是数据行使用的存储总量。由于 CHAR 平均占用的空间多于 VARCHAR,因此使用 VARCHAR 来最小化需要处理的数据行的存储总量和磁盘 I/O 是比较好的。

TEXT 与 BLOB

一般在保存少量字符串的时候,我们会选择 CHAR 或者 VARCHAR;而在保存较大文本时,通常会选择使用 TEXT 或者 BLOB,二者之间的主要差别是 BLOB 能用来保存二进制数据,比如照片;而 TEXT 只能保存字符数据,比如一篇文章或者日记。TEXTBLOB 中有分别包括TEXTMEDIUMTEXTLONGTEXTBLOBMEDIUMBLOBLONGBLOB3 种不同的类型,它们之间的主要区别是存储文本长度不同和存储字节不同,用户应该根据实际情况选择能够满足需求的最小存储类型。

  • BLOBTEXT 值会引起一些性能问题,特别是在执行了大量的删除操作时。删除操作会在数据表中留下很大的“空洞”,以后填入这些“空洞”的记录在插入的性能上会有影响。为了提高性能,建议定期使用 OPTIMIZE TABLE 功能对这类表进行碎片整理,避免因为“空洞”导致性能问题。
  • 可以使用合成的(Synthetic)索引来提高大文本字段(BLOBTEXT)的查询性能。简单来说,合成索引就是根据大文本字段的内容建立一个散列值,并把这个值存储在单独的数据列中,接下来就可以通过检索散列值找到数据行了。但是,要注意这种技术只能用于精确匹配的查询(散列值对于类似<>=等范围搜索操作符是没有用处的)。可以使用 MD5()函数生成散列值,也可以使用 SHA1()CRC32(),或者使用自己的应用程序逻辑来计算散列值。请记住数值型散列值可以很高效率地存储。同样,如果散列算法生成的字符串带有尾部空格,就不要把它们存储在 CHARVARCHAR 列中,它们会受到尾部空格去除的影响。合成的散列索引对于那些 BLOBTEXT 数据列特别有用。用散列标识符值查找的速度比搜索BLOB 列本身的速度快很多。
  • 在不必要的时候避免检索大型的 BLOBTEXT 值。
  • BLOBTEXT 列分离到单独的表中。

浮点数与定点数

浮点数一般用于表示含有小数部分的数值。当一个字段被定义为浮点类型后,如果插入数据的精度超过该列定义的实际精度,则插入值会被四舍五入到实际定义的精度值,然后插入,四舍五入的过程不会报错。在 MySQLfloatdouble(或 real)用来表示浮点数。

在选择浮点型数据保存小数时,要注意四舍五入的问题,并尽量保留足够的小数位,避免存储的数据不准确。

在精度要求比较高的应用中(比如货币)要使用定点数而不是浮点数来保存数据。

注意:在今后关于浮点数和定点数的应用中,用户要考虑到以下几个原则:

  • 浮点数存在误差问题;
  • 对货币等对精度敏感的数据,应该用定点数表示或存储;
  • 在编程中,如果用到浮点数,要特别注意误差问题,并尽量避免做浮点数比较;
  • 要注意浮点数中一些特殊值的处理。

日期类型选择

MySQL 提供的常用日期类型有 DATETIMEDATETIMETIMESTAMP

下面主要总结一下选择日期类型的原则。

  • 根据实际需要选择能够满足应用的最小存储的日期类型。如果应用只需要记录“年份”,那么用 1 个字节来存储的 YEAR 类型完全可以满足,而不需要用 4 个字节来存储的 DATE 类型。这样不仅仅能节约存储,更能够提高表的操作效率。
  • 如果要记录年月日时分秒,并且记录的年份比较久远,那么最好使用 DATETIME,而不要使用 TIMESTAMP。因为 TIMESTAMP 表示的日期范围比 DATETIME 要短得多。
  • 如果记录的日期需要让不同时区的用户使用,那么最好使用 TIMESTAMP,因为日期类型中只有它能够和实际时区相对应。

索引的设计和使用

索引概述

所有 MySQL 列类型都可以被索引,对相关列使用索引是提高 SELECT 操作性能的最佳途径。根据存储引擎可以定义每个表的最大索引数和最大索引长度,每种存储引擎(如 MyISAMInnoDBBDBMEMORY 等)对每个表至少支持 16 个索引,总索引长度至少为 256 字节。大多数存储引擎有更高的限制。

MyISAMInnoDB 存储引擎的表默认创建的都是 BTREE 索引。MySQL 目前还不支持函数索引,但是支持前缀索引,即对索引字段的前 N 个字符创建索引。前缀索引的长度跟存储引擎相关,对于 MyISAM 存储引擎的表,索引的前缀长度可以达到 1000 字节长,而对于InnoDB 存储引擎的表,索引的前缀长度最长是 767 字节。请注意前缀的限制应以字节为单位进行测量,而 CREATE TABLE 语句中的前缀长度解释为字符数。在为使用多字节字符集的列指定前缀长度时一定要加以考虑。

MySQL 中还支持全文本(FULLTEXT)索引,该索引可以用于全文搜索。但是当前最新版本中(5.0)只有 MyISAM 存储引擎支持 FULLTEXT 索引,并且只限于 CHARVARCHARTEXT列。索引总是对整个列进行的,不支持局部(前缀)索引。

也可以为空间列类型创建索引,但是只有 MyISAM 存储引擎支持空间类型索引,且索引的字段必须是非空的。

默认情况下,MEMORY 存储引擎使用 HASH 索引,但也支持 BTREE 索引。
索引在创建表的时候可以同时创建,也可以随时增加新的索引。创建新索引的语法为:

CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name
    [USING index_type]
    ON tbl_name(index_col_name,...)
    
index_col_name:
    col_name[(length)][ASC | DESC]

也可以使用 ALTER TABLE 的语法来增加索引,语法可 CREATE INDEX类似,可以查询帮助获得详细语法。

mysql > create index cityname on city(city(10));

索引的删除语法为:

DROP INDEX index_name ON tbl_name

设计索引的原则

  • 搜索的索引列,不一定是所要选择的列。换句话说,最合适的索引的列是出现在 WHERE 子句中的列,或连接子句中指定的列,而不是出现在 SELECT 关键字后的选择列表中的列。
  • 使用唯一索引。考虑某列中的值的分布。索引的列的基数越大,索引的效果越好。例如,存放出生日期的列具有不同的值,很容易区分各行。而用来记录性别的列,只含有 "M" 和 "F" ,则此列进行索引没有多大用处,因为不管搜索哪个值,都会得出大约一半的行。
  • 使用短索引。如果对字符串列进行索引,应该指定一个前缀长度,只要有可能就应该这样做。例如,如果有一个CHAR(200)列,如果在前10个或者20个字符内,多数值是唯一的,那么就不要对整个列进行索引。对前10个或20个字符进行索引能够节省大量索引空间,也可能会使查询更快。较小的索引涉及的磁盘IO较少,较短的值比较起来更快。更为重要的是,对于较短的键值,索引告诉缓存中的快能容纳更多的键值,因此,MySQL也可以在内存中容纳更多的值。这样就增加了找到行而不用读取索引中较多块的可能性。
  • 利用左前缀。在创建一个 n 列的索引时,实际是创建了 MySQL 可利用的 n 个索引。多列索引可起几个索引的作用,因为可利用索引中最左边的列集来匹配行。这样的列集称为最左前缀。
  • 不要过度索引。不要以为索引“越多越好“,什么东西都用索引是错误的。每个额外的索引都要占用额外的磁盘空间,并降低读写的性能。在修改表的内容时,索引必须进行更新,有时可能需要重构,因此,索引越多,所花时间越长。如果有一个索引很少利用或从从不使用,那么会不必要地减缓表的修改速度。此外,MySQL在生成一个执行计划时,要考虑各个索引,这也要花费时间。创建多余的索引给查询优化带来了更多的工作。索引太多,也可能会使MySQL选择不到所要使用的最好索引。只保持所需的索引有利于查询优化。
  • 对于 InnoDB 存储引擎的表,记录默认会按照一定的顺序保存,如果有明确定义的主键,则按照主键顺序保存。如果没有主键,但是有唯一的索引,那么就是按照唯一索引的顺序保存。如果既没有主键又没有唯一索引,那么表中会自动生成一个内部列,按照这个列的顺序保存。按照主键或者内部列进行的访问是最快的,所以 InnoDB 表尽量自己指定主键,当表中同时有几个列都是唯一的,都可以作为主键的时候,要选择最常作为访问条件的列作为主键,提高查询的效率。另外,还需要注意,InnoDB 表的普通索引都会保存主键的键值,所以主键要尽可能选择短的数据类型,可以有效地减少索引的磁盘占用,提高索引的缓存效果。

BTREE 索引与 HASH 索引

MEMORY 存储引擎的表可以选择使用 BTREE 索引或者 HASH 索引,两种不同类型的索引各有其不同的适用范围。HASH 索引有一些重要的特征需要在使用的时候特别注意,如下所示。

  • 只用于使用 =<=> 操作符的等式比较。
  • 优化器不能使用 HASH 索引来加速 ORDER BY 操作。
  • MySQL 不能确定在两个值之间大约有多少行。如果将一个 MyISAM 表改为 HASH 索引的 MEMORY 表,会影响一些查询的效率。
  • 只能使用整个关键字来搜索一行。

而对于 BTREE 索引,当使用><>=<=BETWEEN!=或者<>,或者 LIKE 'pattern'(其中'pattern'不以通配符开始)操作符时,都可以使用相关列上的索引。

下列范围查询适用于 BTREE 表索引和 HASH 索引:

SELECT * FROM t1 WHERE key_col = 1 OR key_col IN (15, 18, 20);

下列范围查询只适用于 BTREE 索引:

SELECT * FROM t1 WHERE key_col > 1 AND key_col < 10;
SELECT * FROM t1 WHERE key_col LIKE 'ab%' OR key_col BETWEEN 'lisa' AND 'simon';

当使用 MEMORY 表的时候,如果是默认创建的 HASH索引,就要注意 SQL 语句的编写,确保可以使用上索引,如果一定要使用范围查询,那么在创建索引的时候,就应该选择创建成 BTREE 索引。

事务控制和锁定语句

MySQL 支持对 MyISAMMEMORY 存储引擎的表进行表级锁定,对 BDB 存储引擎的表进行页级锁定,对 InnoDB 存储引擎的表进行行级锁定。默认情况下,表锁和行锁都是自动获得的,不需要额外的命令。但是在有的情况下,用户需要明确地进行锁表或者进行事务的控制,以便确保整个事务的完整性,这样就需要使用事务控制和锁定语句来完成。

1、LOCK TABLE 和 UNLOCK TABLE

LOCK TABLES 可以锁定用于当前线程的表。如果表被其他线程锁定,则当前线程会等待,直到可以获取所有锁定为止。
UNLOCK TABLES 可以释放当前线程获得的任何锁定。当前线程执行另一个 LOCK TABLES 时,或当与服务器的连接被关闭时,所有由当前线程锁定的表被隐含地解锁,具体语法如下:

LOCK TABLES
    tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}
    [, tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}]...
UNLOCK TABLES

2、事务控制

MySQL通过SET AUTOCOMMITSTART TRANSACTIONCOMMITROLLBACK等语句支持本地事务,具体语法如下:

START TRANSACTION | BEGIN [WORK]
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET AUTOCOMMIT = {0 | 1}

默认情况下,MySQL 是自动提交(Autocommit)的,如果需要通过明确的 CommitRollback 来提交和回滚事务,那么需要通过明确的事务控制命令来开始事务,这是和 Oracle的事务管理明显不同的地方。如果应用是从 Oracle 数据库迁移到 MySQL 数据库,则需要确保应用中是否对事务进行了明确的管理。

  • START TRANSACTIONBEGIN 语句可以开始一项新的事务。

  • COMMITROLLBACK用来提交或者回滚事务。

  • CHAINRELEASE 子句分别用来定义在事务提交或者回滚之后的操作,CHAIN 会立即启动一个新事物,并且和刚才的事务具有相同的隔离级别,RELEASE 则会断开和客户端的连接。

  • SET AUTOCOMMIT以修改当前连接的提交方式,如果设置了 SET AUTOCOMMIT=0,则设置之后的所有事务都需要通过明确的命令进行提交或者回滚。

    ​ 如果只是对某些语句需要进行事务控制,则使用 START TRANSACTION 语句开始一个事务比较方便,这样事务结束之后可以自动回到自动提交的方式,如果希望所有的事务都不是自动提交的,那么通过修改 AUTOCOMMIT 来控制事务比较方便,这样不用在每个事务开始的时候再执行 START TRANSACTION 语句。

    ​ 通常情况下,只对提交的事务记录到二进制的日志中,但是如果一个事务中包含非事务类型的表,那么回滚操作也会被记录到二进制日志中,以确保非事务类型表的更新可以被复制到从(Slave)数据库中。

    ​ 和 Oracle 的事务管理相同,所有的 DDL 语句是不能回滚的,并且部分的 DDL 语句会造成隐式的提交。

    ​ 在事务中可以通过定义 SAVEPOINT,指定回滚事务的一个部分,但是不能指定提交事务
    的一个部分。对于复杂的应用,可以定义多个不同的 SAVEPOINT,满足不同的条件时,回滚不同的 SAVEPOINT。需要注意的是,如果定义了相同名字的 SAVEPOINT,则后面定义的SAVEPOINT 会覆盖之前的定义。对于不再需要使用的 SAVEPOINT,可以通过 RELEASE SAVEPOINT 命令删除 SAVEPOINT,删除后的 SAVEPOINT,不能再执行 ROLLBACK TO SAVEPOINT命令。

SQL 中的安全问题

SQL 注入

SQL 注入(SQL Injection)攻击具有很大的危害,攻击者可以利用它读取、修改或者删除数据库内的数据,获取数据库中的用户名和密码等敏感信息,甚至可以获得数据库管理员的权限,而且,SQL Injection 也很难防范。网站管理员无法通过安装系统补丁或者进行简单的安全配置进行自我保护,一般的防火墙也无法拦截 SQL Injection 攻击。

应用开发中可以采取的应对措施

  • PrepareStatement+Bind-variable。
  • 使用应用程序提供的转换函数。
  • 自己定义函数进行校验。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,924评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,902评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,716评论 0 239
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,783评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,166评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,510评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,784评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,476评论 0 196
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,196评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,459评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,978评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,321评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,964评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,046评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,803评论 0 193
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,530评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,420评论 2 265