深刻浅出MySQL数据库开发、优化与管理维护(第2版)读书笔记

基础篇

SQL基础

SQL分类

  1. DDL(Data Definition Languages):数据定义语句,这些语句定义了不一样的数据段、数据库、表、列、索引等数据库对象。create、drop、alter等。
  2. DML(Data Manipulation Language):数据操纵语句,用于添加、删除、更新和查询数据库记录,并检查数据完整性。insert、delete、update和select等。
  3. DCL(Data Control Language):数据控制语句,用于控制不一样数据段直接的许可和访问级别的语句。这些语句定义了数据库、表、字段、用户访问权限和安全级别。grant、revoke等。

DDL语句

它和DML语句最大的区别是DML只是对表内部数据操做,而不涉及表的定义、结构的修改,更不会涉及其余对象。mysql

1. 建立数据库
CREATE DATABASE dbname
复制代码
2. 删除数据库
DROP DATABASE dbname
复制代码
3. 建立表
CREATE TABLE tablename (
column_name_1 column_type_1 constraints,
column_name_2 column_type_2 constraints,
...
column_name_n column_type_n constraints)
复制代码

MySQL的表名是以目录的形式存在于磁盘上的,因此表名的字符能够用任何目录名容许的字符。算法

4.删除表
DROP TALBE tablename
复制代码
5. 修改表
-- 修改表类型
ALTER TABLE tablename MODIFY [COLUMN] column_definition [FIRST|AFTER col_name]
-- 增长表字段
ALTER TABLE tablename ADD [COLUMN] column_definition [FIRST|AFTER col_name]
-- 删除表字段
ALTER TABLE tablename DROP [COLUMN] col_name
-- 字段更名
ALTER TALBE tablename CHANGE [COLUMN] old_col_name new_col_name column_definition [FIRST|AFTER col_name]
--  更改表名
ALTER TABLE tablename RENAME [TO] new_tablename
复制代码

注意:sql

  1. change和modify均可以修改表的定义,不一样的是change后面须要写两次列名,不方便。但change的优势是能够修改列名称,modify不能。
  2. CHANGE/FIRST|AFTER COLUMN这些关键字都属于MySQL在标准SQL上的扩展,在其余数据库上不必定适用。

DML语句

1. 插入记录
INSERT INTO tablename(field1,field2,...,fieldn) VALUES(value1,value2,...,valuen)
复制代码
2. 更新记录
UPDATE tablename SET field1=value1,field2=value2,...,fieldn=valuen [WHERE CONDITION]
复制代码
3. 删除记录
DELETE FROM tablename [WHERE CONDITION]
复制代码

在MySQL中能够一次删除多个表的数据,语法以下:shell

DELETE t1,t2,...,tn FROM t1,t2,...,tn [WHERE CONDITION]
复制代码

若是from后面的表名用别名,则delete后面也要用相应的别名,不然会提示语法错误。数据库

4. 查询记录
-- 排序
SELETE * FROM tablename [WHERE CONDITION] [ORDER BY field1 [DESC|ASC], field2 [DESC|ASC],...,fieldn [DESC|ASC]]
-- 限制
SELECT ... [LIMIT offset_start,row_count]
-- 聚合
SELECT [field1,field2,...,fieldn] fun_name FROM tablename [WHERE where_condition] [GROUP BY field1,field2,...,fieldn] [WITH ROLLUP] [HAVING having_condition]
复制代码

注意:缓存

  1. WITH ROLLUP是可选语法,代表是否对分类聚合后的结果再进行汇总。
  2. having和where的区别在于,having是对聚合后的结果进行条件的过滤,而where是在聚合前就对记录进行过滤,若是逻辑容许,尽量使用where先过滤记录,这样由于结果集减少,将对聚合的效率大大提升,最后再根据逻辑看是否用having进行再过滤。

DCL语句

DCL语句主要是DBA用来管理系统中的对象权限时使用,通常的开发人员不多使用。如下经过例子说明。安全

-- 建立一个数据库用户z1,具备对sakila数据库中全部表的SELECT/INSERT权限
GRANT select,insert ON sakila.* to 'z1'@'localhost' identified by '123';
-- 收回INSERT权限
REVOKE insert ON sakila.* FROM 'z1'@'localhost';
复制代码

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
位类型 字节 最小值 最大值
BIT(M) 1~8 BIT(1) BIT(64)
定点类型 字节 描述
DEC(M,D),
DECIMAL(M,D)
M+2 最大值范围与DOUBLE相同,给定DECIMAL的有效取值范围由M和D决定

对于整型数据,MySQL还支持在类型名称后面的小括号内指定显示宽度,例如int(5)表示当数值宽度小于5位的时候在数字前面填满宽度,若是不显示指定宽度则默认为int(11)。通常配合zerofill使用,顾名思义,zerofill就是用"0"填充的意思,也就是在数字位数不够的空间用字符"0"填充。bash

若是一列指定为zerofill,则MySQL自动为该列添加UNSIGNED属性。服务器

日期时间类型

日期时间类型 字节 最小值 最大值
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

TIMESTAMP有一个重要的特色,就是和时区相关。当插入日期时,会先转换为本地时区后存放;而从数据库里面取出来时,也一样须要将日期转换为本地时区后显示。session

字符串类型

字符串类型 字节 描述及存储需求
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~4394967295字节,值的长度+4字节
TINYTEXT 容许长度0~255字节,值的长度+2字节
TEXT 容许长度0~65535字节,值的长度+3字节
MEDIUMTEXT 容许长度0~167772150字节,值的长度+3字节
LONGTEXT 容许长度0~4394967295字节,值的长度+4字节
VARBINARY(M) 容许长度0~M字节的变长字节字符串,值的长度+1个字节
BINARY(M) M 容许长度0~M字节的变长字节字符串

在检索的时候,CHAR列删除了尾部的空格,而VARCHAR则保留这些空格。

枚举类型

它的值范围须要在建立表时经过枚举方式显式指定,对1~255个成员的枚举须要1个字节存储;对于255~65535个成员,须要2个字节存储。最多容许有65535个成员。

SET类型

SET和ENUM类型很是相似,也是一个字符串对象,里面能够包含0~64个成员。

SET和ENUM除了存储以外,最主要的区别在于SET类型一次能够选取多个成员,而ENUM则只能选一个。

经常使用函数

字符串函数

函数 功能
CONCAT(S1,S2,...,Sn) 字符串链接
INSERT(str,x,y,instr) 将字符串str从第x位置开始,y个字符长的子串替换为字符串instr
LOWER(str) 转小写
UPPER(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) 绝对值
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,返回result,不然返回default

经常使用其余函数

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

开发篇

表类型(存储引擎)的选择

存储引擎概述

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

查看当前默认存储引擎,可使用以下命令。

show varivales like 'table_type';
复制代码

查询当前数据库支持的存储引擎,可使用如下两种方式。

SHOW ENGINES\G;
SHOW VARIABLES LIKE 'have%';
复制代码

各类存储引擎的特性

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

MyISAM

MyISAM不支持事务、也不支持外键,其优点是访问的速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用基本上均可以使用这个引擎来建立表。

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

  • .frm 存储表定义
  • .MYD MYData,存储数据
  • .MYI MYIdex,存储索引

数据文件和索引文件能够放置在不一样的目录,平均分布IO,得到更快的速度。

InnoDB

InnoDB存储引擎提供了具备提交、回滚和崩溃回复能力的事务安全。

自动增加列

能够经过ALTER TABLE *** AUTO_INCREMENT = n;语句强制设置自动增加列的初始值,默认从1开始,可是该强制的默认值是保留在内存中的,若是该值在使用以前数据库从新启动,那么这个强制的默认值就会丢失,就须要在数据库启动之后从新设置。

对于InnoDB,自动增加列必须是索引。若是是组合索引,也必须是组合索引的第一列,可是对于MyISAM表,自动增加列能够是组合索引的其余列,这样插入记录后,自动增加列是按照组合索引的前面几列进行排序后递增的。

外键约束

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

CREATE TABLE country(
	country_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
	country VARCHAR(50) NOT NULL,
  last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (country_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE city(
	city_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
  city VARCHAR(50) NOT NULL,
  country_id SMALL UNSIGNED NOT NULL,
  last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (city_id),
  KEY idx_fk_country_id (country_id),
  CONSTRAINT 'fk_city_country' FOREIGN KEY (country_id) REFERENCES country(country_id) ON DELETE RESTRICT ON UPDATE CASCADE
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码

能够指定在删除、更新父表时,对子表进行的相应操做,包括RESTRICT、CASCADE、SET NULL和NO ACTION。其中RESTRICT和NO ACTION相同,是指限制在子表有关联记录的状况下父表不能更新;CASCADE表示父表在更新或者删除时,更新或者删除子表对应记录;SET NULL则表示父表在更新或者删除的时候,子表的对应字段被SET NULL。

存储方式

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

  1. 使用共享表空间存储,这种方式建立的表的表结构保存在.frm文件中,数据和索引保存在innodb_data_home_dir和innodb_data_file_path定义的表空间中,能够是多个文件。
  2. 使用多表空间存储,这种方式建立的表的表结构仍然保存在.frm文件中,可是每一个表的数据和索引单独保存在.ibd中。若是是个分区表,则每一个分区对应单独的.ibd文件,文件名是“表名+分区名”,能够在建立分区的时候指定每一个分区的数据文件的位置,以此来将表的IO均匀分布在多个磁盘上。

MEMORY

MEMORY存储引擎使用存在于内存中的内容建立表。每一个MEMORY表只实际对应一个磁盘文件,格式是.frm。MEMORY类型的表访问很是快,由于它的数据是放在内存中的,而且默认使用HASH索引,可是一旦服务关闭,表中的数据就会丢失掉.

MERGE

MERGE存储引擎是一组MyISAM组合,这些MyISAM表必须结构彻底相同,MERGE表自己并无数据,对MERGE类型的表能够进行查询、更新、删除操做,这些操做其实是对内部的MyISAM表进行的。

MERGE表在磁盘上保留两个文件,文件名以表的名字开始,一个.frm文件存储表定义,另外一个.MRG文件包含组合表的信息,包括MERGE表由哪些表组成、插入新的数据时的依据。

字符集

MySQL支持的字符集

查看全部可用的字符集的命令是show character set;

或者查看information_schema.character_set,可用显示全部的字符集和该字符集默认的校对规则。

MySQL字符集的设置

MySQL的字符集和校对规则有4个级别的默认设置:服务器级、数据库级、表级和字段级。

服务器字符集和校对规则
  • 能够在my.cnf中设置

  • [mysqld]
    character-set-server=gbk
    复制代码
  • 或者在启动选项中指定

  • mysqld --character-set-server=gbk
    复制代码
  • 或者在编译时指定

  • shell> cmake . -DEFALUT_CHARSET=gbk
    复制代码

    可使用show variables like 'character_set_server'命令查询当前服务器的字符集和校对规则。

    show variables like 'character_set_server';
    show variables like 'collation_server';
    复制代码
数据库字符集和校对规则

数据库的字符集和校对规则在建立数据库的时候指定,也能够再建立完数据库后经过alter database命令进行修改。须要注意的是,若是数据库里已经存在数据,由于修改字符集并不能将已有的数据按照新的字符集进行存放,因此不能经过修改数据库的字符集直接修改数据的内容。

要显示当前数据库的字符集和校对规则,可使用show variables like 'character_set_database'show variables like 'collation_database'命令查看。

show variables like 'character_set_database';
show variables like 'collation_database';
复制代码
表字符集和校对规则

表的字符集和校对规则在建立表的时候指定,能够经过alter table命令进行修改,一样,若是表中已有记录,修改字符集对原有的记录并无影响,不会按照新的字符集进行存放。表的字段仍然使用原来的字符集。

要显示表的字符集和校对规则,可使用show create table命令查看。

show create table z1\G;
复制代码
列字符集和校对规则

MySQL能够定义列级别的字符集和校对规则,主要是针对相同的表不一样字段须要使用不一样的字符集的状况,应该说通常遇到这种状况的概率比较小,这只是MySQL提供给咱们一个灵活设置的手段。

链接字符集和校对规则

对于客户端和服务器的交互操做,MySQL提供了3中不一样的参数:character_set_client、character_set_connection和character_set_results,分别表明客户端、链接和返回结果的字符集。一般状况下,这3个字符集应该是相同的,才能够确保用户写入的数据能够正确地读出。

一般状况下,不会单个设置这3个参数,能够经过如下命令:

SET NAMES ***
复制代码

这个命令能够同时修改这3个参数的值。使用这个方法修改链接的字符集和校对规则,须要应用每次链接数据库后都执行这个命令。

另外一个更简便的方法,是在my.cnf中设置如下语句:

[mysql]
default-character-set=gbk
复制代码

字符集的修改步骤

(1) 导出表结构

  1. mysqldump -uroot -p --default-character-set=gbk -d databasename > create.sql
    复制代码

    其中--default-character-set=gbk表示设置以什么字符集链接,-d表示只导出表结构,不导出数据。

(2) 手工修改create.sql中表结构定义中的字符集为新的字符集。

(3) 确保记录再也不更新,导出全部记录。

  1. msyqldump -uroot -p --quick --no-create-info --extended-insert --default-character-set=latin1 databasename > data.sql
    复制代码
    • --quick:该选项用于转储大的表,它强制mysqldump从服务器一次一行地检索表中的行而不是检索全部行,并在输出前将它缓存到内存中。
    • --extended-insert:使用包含几个VALUES列表的多行INSERT语法,这样使转储文件更小,重载文件时能够加速插入。
    • --no-craete-info:不导出每一个转储表的CREATE TABLE语句。
    • --default-character-set=latin1:按照原有的字符集导出全部数据,这样导出的文件中,全部中文都是可见的,不会保存成乱码。

(4) 打开data.sql,将SET NAMES latin1修改为SET NAMES gbk。

(5) 使用新的字符集建立新的数据库

create database databasename default charset gbk;
复制代码

(6) 建立表,执行create.sql

mysql -uroot -p databasename < craete.sql
复制代码

(7) 导入数据,执行data.sql

mysql -uroot -p databasename < data.sql
复制代码

索引的设计和使用

索引的概述

MyISAM和InnoDB存储引擎的表默认建立的都是BTREE索引。MySQL目前还不支持函数索引,可是支持前缀索引,即对索引的前N个字符建立索引。前缀索引的长度跟存储引擎相关,对于MyISAM存储引擎的表,索引的前缀长度能够达到1000字节长,而对于InnoDB存储引擎的表,索引的前缀长度最长是767字节。请注意前缀的限制应以字节为单位进行测量,而CREATE TABLE语句中的前缀长度解释为字符数。

建立索引的语法

CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name [USING index_type]
ON table_name (index_col_name,...)
index_col_name: col_name [(length)] [ASC|DESC]
复制代码

索引的删除语法为

DROP IDNEX index_name ON tab_namne
复制代码

设计索引的原则

  • 最适合索引的列是出如今WHERE子句中的列,或链接子句中指定的列,而不是出如今SELECT关键字后的选择列表中的列。
  • 使用惟一索引。索引的列的基数越大,索引的效果越好。
  • 使用短索引。若是对字符串列进行索引,应该指定一个前缀长度,只要有可能就应该这样作。较小的索引涉及的磁盘IO较少,较短的值比较起来更快。
  • 利用最左前缀。
  • 不要过分索引。
  • 对于InnoDB存储引擎的表,记录默认会按照必定的顺序保存,若是有明肯定义的主键,则按照主键顺序保存。若是没有主键,可是有惟一索引,那么就是按照惟一索引的顺序保存。若是既没有主键又没有惟一索引,那么表中会自动生成一个内部列,按照这个列的顺序保存。按照主键或者内部列进行的访问是最快的,因此InnoDB表尽可能本身指定主键。InnoDB表的普通索引都会保存主键的键值,因此主键要尽量选择较短的数据类型,能够有效地减小索引的磁盘占用,提升索引的缓存效果。

BTREE索引和HASH索引

HASH索引有一些重要的特征须要在使用的时候特别注意,以下所示。

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

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

视图

什么是视图

视图(View)是一种虚拟存在的表,对于使用视图的用户来讲基本上是透明的。

视图操做

建立或者修改视图

建立视图须要有CREATE VIEW的权限,而且对于查询涉及的列有SELECT权限。若是使用CREATE OR REPLACE或者ALTER修改视图,那么还须要该视图的DROP权限。

-- 建立视图
CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED|MERGE|TEMPTABLE}]
VIEW view_name [(column_list)]
AS select_statement
[WITH[CASCADED|LOCAL]CHECK OPTION]
-- 修改视图
ALTER [ALGORITHM={UNDEFINED|MERGE|TEMPTABLE}]
VIEW view_name [(column_list)]
AS select_statement
[WITH[CASCADED|LOCAL]CHECK OPTION]
复制代码

视图的可更改性和视图中查询的定义有关系,如下类型的视图是不可更新的。

  • 包含如下关键字的SQL语句:聚合函数(SUM、MIN、MAX、COUNT等)、DISTINCT、GROUP BY、HAVING、UNION、UNION ALL
  • 常量视图
  • SELECT中包含子查询
  • JOIN
  • FROM一个不能更新的视图
  • WHERE子句的子查询引用了FROM子句中的表
删除视图

用户能够一次删除一个或者多个视图,前提是必须有该视图的DROP权限。

DROP VIEW [IF EXISTS] view_name[,view_name]...[RESTRICT|CASCADE]
复制代码
查看视图

从MySQL5.1版本开始,使用SHOW TABLES命令的时候不只显示表的名字,同时也会显示视图的名字。

SHOW TABLES
复制代码

一样,在使用SHOW TABLES STATUS命令的时候,不但能够显示表的信息,同时也能够显示视图的信息。

SHOW TABLES STATUS [FROM db_name] [LIKE 'pattern']
复制代码

存储过程和函数

什么是存储过程和函数

存储过程和函数是事先通过编译并存储在数据库中的一段SQL语句的集合。

存储过程和函数的区别在于函数必须有返回值,而存储过程没有,存储过程的参数可使用IN、OUT、INOUT类型,而函数的参数只能是IN类型的。

事务控制和锁定语句

LOCK TALBE 和 UNLOCK TABLE

LOCK TABLES 能够锁定用于当前线程的表。若是表被其余线程锁定,则当前线程会等待,直到能够获取全部锁定位置。

UNLOCK TALBES能够释放当前线程得到的任何锁定。当前线程执行另外一个LOCK TABLES时,或当服务器的链接被关闭时,全部由当前线程锁定的表被隐含地解锁。

LOCK TABLES
	tal_name [AS alias] {READ[LOCAL]|[LOW_PRIORITY] WRITE}
	[,tal_name [AS alias] {READ[LOCAL]|[LOW_PRIORITY] WRITE}]...
UNLOCK TABLES
复制代码

事务控制

MySQL经过SET AUTOCOMMIT、START TRANSACTION、COMMIT和ROLLBACK等语句支持本地事务,具体语法以下:

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

默认状况下,MySQL是自动提交(AutoCommit)的,若是须要经过明确的Commit喝Rollback来提交和回滚事务,那么就须要经过明确的事务控制命令来开始事务,这是和Oracle事务管理明显不一样的地方。

  • START TRANSACTION或BEGIN语句能够开始一项新的事务
  • COMMIT和ROLLBACK用来提交或者回滚事务
  • CHAIN和RELEASE子句分别用来定义在事务提交或者回滚以后的操做,CHAIN会当即启动一个新事务,而且和刚才的事务具备相同的隔离级别,RELEASE则会断开和客户端的链接。
  • SET AUTOCOMMIT能够修改当前链接的提交方式,若是设置了SET AUTOCOMMIT=0,则设置以后的全部事务 0须要经过明确的命令进行提交或者回滚。

在锁表期间,用 START TRANSACTION命令开始一个新事务,会形成一个隐含地UNLOCK TABLES被执行。

MySQL分区

分区概述

分区引入了分区键(partition key)的概念,分区键用来根据某个区间值(或者范围值)、特定值列表或者HASH函数值执行数据的汇集,让数据根据规则分布在不一样的分区中,让一个大对象变成一些小对象。

能够经过SHOW VARIABLES命令来肯定当前的MySQL是否支持分区

SHOW VARIABLES LIEK '%partition%'
复制代码

分区类型

在MySQL5.1中可用的分区类型,主要有如下4种。

  • RANGE分区:基于一个给定连续区间范围,把数据分配到不一样的分区。
  • LIST分区:相似RANGE分区,区别在LIST分区是基于枚举出的值列表分区,RANGE是基于给定的连续区间范围分区。
  • HASH分区:基于给定的分区个数,把数据分配到不一样的分区。
  • KEY分区:相似于HASH分区。

在MySQL5.1版本中,RANGE分区、LIST分区、HASH分区都要求分区键必须是INT类型,或者经过表达式返回INT类型。不管哪一种MySQL分区类型,不能使用主键/惟一键字段以外的其余字段分区。

RANGE分区

CREATE TABLE emp(
	id INT NOT NULL,
	store_id INT NOT NULL
)PARTITION BY RANGE(store_id)(
	PARTITION p0 VALUES LESS THEN (10),
	PARTITION p1 VALUES LESS THEN (20),
	PARTITION p2 VALUES LESS THEN (30)
);
复制代码

LIST分区

CREATE TABLE expenses(
	expense_date DATE NOT NULL,
	category INT
)PARTITION BY LIST(category)(
	PARTITION p0 VALUES IN(3, 5),
	PARTITION p1 VALUES IN(1, 10),
	PARTITION p2 VALUES IN(4, 9),
  PARTITION p3 VALUES IN(2),
  PARTITION p4 VALUES IN(6)
);
复制代码

COLUMNS分区

Columns分区是MySQL5.5引入的分区类型,引入Columns分区解决了MySQL5.5版本以前RANGE分区和LIST分区只支持整型分区,从而致使须要额外的函数计算获得整数或经过额外的转换表来转换为整数再分区的问题。Columns分区能够细分为RANGE Columns分区和LIST Columns分区,RANGE Columns分区和LIST Columns分区都支持整数、日期时间、字符串三大数据类型。

对比RANGE分区和LIST分区,Columns分区的亮点除了支持数据类型增长以外,另外,一大亮点是Columns分区还支持多列分区。

CREATE TABLE r3(
	a INT,
	b INT
)PARTITION BY RANGE COLUMNS(a, b)(
	PARTITION p01 VALUES LESS THAN (0, 10),
	PARTITION p02 VALUES LESS THAN (10, 10),
	PARTITION p03 VALUES LESS THAN (10, 20),
	PARTITION p04 VALUES LESS THAN (10, 35),
	PARTITION p05 VALUES LESS THAN (10, MAXVALUE),
	PARTITION p06 VALUES LESS THAN (MAXVALUE, MAXVALUE)
);
复制代码

HASH分区

HASH分区主要用来分散热点读,确保数据在预先肯定个数的分区中尽量平均分布。

MySQL支持两种HASH分区,常规HASH分区和线性HASH分区;常规HASH使用的是取模算法,线性HASH分区使用的是一个线性的2的幂的运算法则。

-- 常规HASH分区
CREATE TABLE emp(
	id INT NOT NULL,
	store_id INT NOT NULL
)PARTITION BY HASH(store_id) PARTITIONS 4;

-- 线性HASH
CREATE TABLE emp(
	id INT NOT NULL,
	store_id INT NOT NULL
)PARTITION BY LINEAR HASH(store_id) PARTITIONS 4;
复制代码

KEY分区

按照Key进行分区很是相似于按照HASH进行分区,只不过HASH分区容许使用用户自定义的表达式,而Key分区不容许使用用户自定义的表达式,须要使用MySQL服务器提供的HASH函数;同时HASH分区只支持整数分区,而Key分区支持使用除BLOB或Text类型外其余类型的列做为分区键。

CREATE TABLE emp(
	id INT NOT NULL,
	store_id INT NOT NULL,
  job VARCHAR(30) NOT NULL
)PARTITION BY KEY (job) PARTITIONS 4;
复制代码

建立Key分区表的时候,能够不指定分区键,默认会首先选择使用主键做为分区键,在没有主键的状况,会选择非空惟一键做为分区键。

MySQL分区处理NULL值的方式

MySQL不由止在分区键值上使用NULL,分区键多是一个字段或者一个用户定义的表达式。通常状况下,MySQL的分区把NULL当作零值,或者是一个最小值进行处理。

注意:RANGE分区中,NULL值会被看成最小值来处理;LIST分区中,NULL值必须出如今枚举列表中,不然不被接受;HASH/KEY分区中,NULL值会被看成零值来处理。

优化篇

SQL优化

优化SQL语句的通常步骤

  1. 经过 show status 命令了解各类SQL的执行频率
    show [session|global] status
    复制代码

    下面的命令显示了当前session中全部统计参数的值:

    mysql> show status like 'Com_%';
    +-------------------------------------+-------+
    | Variable_name                       | Value |
    +-------------------------------------+-------+
    | Com_admin_commands                  | 0     |
    | Com_assign_to_keycache              | 0     |
    | Com_alter_db                        | 0     |
    | Com_alter_event                     | 0     |
    | Com_alter_function                  | 0     |
    | Com_alter_instance                  | 0     |
    | Com_alter_procedure                 | 0     |
    | Com_alter_resource_group            | 0     |
    | Com_alter_server                    | 0     |
    | Com_alter_table                     | 0     |
    | Com_alter_tablespace                | 0     |
    | Com_alter_user                      | 0     |
    | Com_alter_user_default_role         | 0     |
    | Com_analyze                         | 0     |
    | Com_begin                           | 0     |
    | Com_binlog                          | 0     |
    | Com_call_procedure                  | 0     |
    | Com_change_db                       | 1     |
    | Com_change_master                   | 0     |
    | Com_change_repl_filter              | 0     |
    | Com_check                           | 0     |
    ...
    复制代码

    Com_xxx表示每一个xxx语句执行的次数,一般比较关心的是如下几个统计参数:

    • Com_select:执行SELECT操做的次数,一次查询只累加1.
    • Com_insert:执行INSERT操做的次数,对于批量插入的INSERT操做,只累加一次。
    • Com_update:执行UPDATE操做的次数。
    • Com_delete:执行DELETE操做的次数。

    上面这些参数对于全部存储引擎的表操做都会进行累加。下面这几个参数只是针对InnoDB存储引擎的,累加的算法也略有不一样。

    • Com_rows_read:SELECT查询返回的行数。
    • Com_rows_inserted:执行INSERT操做插入的行数。
    • Com_rows_updated:执行UPDATE操做更新的行数。
    • Com_rows_deleted:执行DELETE操做删除的行数。

    经过以上几个参数,能够很容易地了解当前数据库是以插入更新为主仍是以查询操做为主。

    对于事务型的应用,能够经过Com_commitCom_rollback能够了解事务和回滚的状况。

    经过如下几个参数能够了解数据库的基本状况:

    • Connections:试图链接MySQL服务器的次数。
    • Uptime:服务器工做时间
    • Slow_queries:慢查询的次数。
  2. 定位执行效率较低的SQL语句
    • 经过慢查询日志定位那些执行效率较低的SQL语句,用--log-slow-queries[=file_name]选项启动时,mysqld写一个包含全部执行时间超过long_query_time秒的SQL语句的日志文件。
    • 慢查询日志在查询结束之后才记录,因此在应用反映执行效率出现问题的时候查询慢查询日志并不能定位问题,可使用show processlist命令查看当前MySQL在进行的线程,包括线程的状态、是否锁表等,能够实时地查看SQL的执行状况,同时对一些锁表操做进行优化。
  3. 经过EXPLAIN分析低效SQL的执行计划
    • select_type:表示SELECT的类型,常见的取值有SIMPLE(简单表,即不使用表链接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(子查询中的第一个SELECT)等。
    • table:输出结果集的表
    • type:表示MySQL在表中找到所需行的方式,或者叫访问类型
      • type = ALL,全表扫描,MySQL遍历全表来找到匹配的行。
      • type = index,索引扫描,MySQL遍历整个索引来找到匹配的行。
      • type = range,索引范围扫描,常见于<、<=、>、>=、between等
      • type = ref,使用非惟一索引扫描或惟一索引的前缀扫描,返回匹配某个单独值的记录行。
      • type = eq_ref,相似ref,区别就在使用的索引是惟一索引,对于每一个索引键值,表中只有一条记录匹配;简单来讲,就是多表链接中使用primary key或者unique index做为关联条件。
      • type = const/system,单表中最多有一个匹配行,查询起来很是迅速,因此这个匹配行中的其余列的值能够被优化器在当前查询中看成常量来处理,例如,根据主键primary key或者惟一索引unique index进行的查询。
      • type = NULL,MySQL不用访问表或者索引,就能直接获得结果。
      • type还有其余值,如ref_or_null(与ref相似,区别在于条件中包含对NULL的查询)、index_range(索引合并优化)、unique_subquery(in的后面是一个查询主键字段的子查询)、index_subquery(与unique_subquery相似,区别在于in的后面是查询非惟一索引字段的子查询)
    • possible_keys:表示查询时可能使用的索引。
    • key:表示实际使用的索引。
    • key_len:使用到的索引字段的长度
    • rows:扫描行的数量
    • Extra:执行状况的说明和描述,包含不适合在其余列中显示可是对执行计划很是重要的额外信息。

    MySQL4.1开始引入explain extended命令,经过explain extended加上show warnings,可以看到SQL真正被执行以前优化器作了哪些SQL改写。

    MySQL5.1开始支持分区功能,同时explain命令也增长了对分区的支持。能够经过explain partitions命令查看SQL所访问的分区。

  4. 经过show profile分析SQL

    经过have_profiling参数,查看当前MySQL是否支持profile。

    select @@have_profiling;
    +------------------+
    | @@have_profiling |
    +------------------+
    | YES              |
    +------------------+
    复制代码

    默认profiling是关闭的,能够经过set语句在session级别开启profiling:

    select @@profiling;
    +-------------+
    | @@profiling |
    +-------------+
    | 0           |
    +-------------+
    set profiling = 1;
    复制代码

    经过show profiles语句,查看执行SQL的Query ID

    show profiles;
    +----------+----------+-----------------------------+
    | Query_ID | Duration | Query                       |
    +----------+----------+-----------------------------+
    | 1        | 8.7e-05  | SHOW WARNINGS               |
    | 2        | 0.000141 | select @@profiling          |
    | 3        | 6.8e-05  | SHOW WARNINGS               |
    | 4        | 0.000324 | select * from customer_part |
    | 5        | 5.2e-05  | SHOW WARNINGS               |
    | 6        | 4.4e-05  | SHOW WARNINGS               |
    | 7        | 0.000199 | select @@have_profiling     |
    | 8        | 7.5e-05  | SHOW WARNINGS               |
    +----------+----------+-----------------------------+
    复制代码

    经过show profile for query {queryId}语句可以看到执行过程当中线程的每一个状态和消耗时间

    show profile for query 4;
    +--------------------------------+----------+
    | Status                         | Duration |
    +--------------------------------+----------+
    | starting                       | 0.000077 |
    | Executing hook on transaction  | 0.000009 |
    | starting                       | 0.000008 |
    | checking permissions           | 0.000006 |
    | Opening tables                 | 0.000033 |
    | init                           | 0.000006 |
    | System lock                    | 0.000009 |
    | optimizing                     | 0.000005 |
    | statistics                     | 0.000013 |
    | preparing                      | 0.000014 |
    | executing                      | 0.000052 |
    | end                            | 0.000005 |
    | query end                      | 0.000003 |
    | waiting for handler commit     | 0.000008 |
    | closing tables                 | 0.000008 |
    | freeing items                  | 0.000024 |
    | cleaning up                    | 0.000044 |
    +--------------------------------+----------+
    复制代码

    MySQL支持进一步选择all、cpu、block io、context、switch、page faults等明细类型来查看MySQL在使用什么资源上耗费了太高的时间

    show profile cpu for query 1;
    复制代码
  5. 经过trace分析优化器如何选择执行计划

    MySQL5.6提供了对SQL跟踪trace。

    使用方式:首先打开trace,设置格式为JSON,设置trace最大可以使用的内存大小,避免解析过程当中由于默认内存太小而不可以完整显示。

    SET OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=on;
    SET OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000;
    复制代码

    接下来执行想要trace的SQL语句

    select rental_id from rental where rental_date >= '2005-05-25 04:00:00' and rental_date <= '2005-05-25 05:00:00' and inventory_id = 5566;
    复制代码

    最后检查INFORMATION_SCHEMA.OPTIMIZER_TRACE就能够知道MySQL是如何执行SQL的

    SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE\G;
    复制代码

索引问题

索引的存储分类

索引是在MySQL的存储引擎层中实现的,而不是在服务器层实现的。MySQL目前提供如下4种索引:

  • B-Tree索引
  • HASH索引:只有Memory引擎支持
  • R-Tree索引(空间索引):空间索引是MyISAM的一个特殊索引类型,主要用于地理空间数据类型。
  • Full-text(全文索引):全文索引也是MyISAM的一个特殊索引类型。

前缀索引也有缺点,在排序Order By和分组Group By操做的时候没法使用。

MySQL如何使用索引
MySQL中可以使用索引的典型场景
  1. 匹配全值(Match the full value),对索引中全部列都指定具体值,便是对索引中的全部列都有等值匹配的条件。
  2. 匹配值的范围查询(Match a range of values),对索引的值可以进行范围查找。
  3. 匹配最左前缀(Match a leftmost prefix),仅仅使用索引中的最左边列进行查找。
  4. 仅仅对索引进行查询(Index only query),当查询的列都在索引的字段中时,查询的效率更高。
  5. 匹配列前缀(Match a column prefix),仅仅使用索引中的第一列,而且只包含索引第一列的开头一部分进行查找。
  6. 可以实现索引匹配部分精确而其余部分进行范围匹配(Match one part exactly and match a range on another part)。
  7. 若是列名是索引,那么使用column name is null就会使用索引(区别于Oracle)。
  8. MySQL5.6引入了Index Condition Pushdown(ICP)的特性,进一步优化了查询。Pushdown表示操做下放,某些状况下的条件过滤操做下放到存储引擎。以前操做是经过索引回表获取记录后,再根据条件来过滤出最后的查询结果;下放后,在索引返回指针的时候已通过滤掉了,这样可以下降没必要要的IO访问。
存在索引不能使用索引的典型场景
  1. 以%开头的LIKE查询不能利用B-Tree索引
  2. 数据类型出现隐式转换的时候也不会使用索引
  3. 复合索引的状况下,假如查询条件不包含索引列最左边部分,即不知足最左原则Leftmost,是不会使用复合索引。
  4. 若是MySQL估计使用索引比全表扫描慢,则不使用索引。
  5. 使用or分割开的条件,若是or前的条件中的列有索引,然后面的列没有索引,那么涉及的索引都不会被用到。由于or后面的条件列中没有索引,那么后面的查询确定要走全表扫描,在存在全表扫描的状况下,就没有必要多一次索引扫描增长IO访问,一次全表扫描过滤条件就足够了。
查看索引使用状况

若是索引正在工做,Handler_read_key的值将很高,这个值表明了一个行被索引值读的次数,很低的值代表增长索引获得的性能改善不高,由于索引并不常用。

Handler_read_rnd_next的值高则意味着查询运行低效,而且应该创建索引补救。这个值的含义是在数据文件中读下一行的请求数。

show status like 'Handler_read%';
复制代码
两个简单实用的优化方法
  1. 按期分析表和检查表

    -- 分析表
    ANLYZE [LOCAL|NO_WRITE_TO_BINLOG] TALBE tbl_name[,tbl_name]...
    
    -- 检查表
    CHECK TALBE tbl_name[,tbl_name]...[option]...option={QUICK|FAST|MEDIUM|EXTENDED|CHANGED}
    复制代码

    在分析期间,使用一个读取锁定对表进行锁定。

  2. 按期优化表

    OPTIMIZE [LOCAL|NO_WRITE_TO_BINLOG] TABLE tbl_name[,tbl_name]...
    复制代码

    若是已经删除了表的一大部分,或者若是已经对含有可变长度行的表(含有VARCHAR、BLOB或TEXT列的表)进行了不少更改,则应实用OPTIMIZE TABLE命令来进行表优化。这个命令能够将表中的空间碎片进行合并,而且能够消除因为删除或者更新形成的表空间浪费,但OPTIMIZE TABLE命令只对MyISAM、BDB和InnoDB表起做用。

经常使用SQL的优化
大批量插入

当用load命令导入数据的时候,适当的设置能够提升导入的速度。

对于MyISAM存储引擎的表,能够经过如下方式快速地导入大量的数据。

ALTER TALBE tbl_name DISABLE KEYS;
loading the data
ALTER TABLE tbl_name ENABLE KEYS;
复制代码

DISABLE KEYS和ENABLE KEYS用来打开或者关闭MyISAM表非惟一索引的更新。这种方式是对MyISAM表进行数据导入时的优化措施。

有如下几种方式提升InnoDB表的导入效率。

  1. 由于InnoDB类型的表是按照主键的顺序保存的,因此将导入的数据按照主键的顺序排列,能够有效地提升导入数据的效率。
  2. 在导入数据前执行SET UNIQUE_CHECKS=0,关闭惟一性校验,在导入结束后执行SET UNIQUE_CHECKS=1,恢复惟一性校验,能够提升导入的效率。
  3. 若是应用使用自动提交的方式,建议在导入前执行SET AUTOCOMMIT=0,关闭自动提交,导入结束后再执行SET AUTOCOMMIT=1,打开自动提交,也能够提升导入的效率。
优化INSET语句

当进行数据INSERT的时候,能够考虑采用如下几种优化方式。

  1. 若是同时从同一客户插入不少行,应尽可能使用多个值表的INSERT INTO tlb_name VALUES(), (), ..., ()语句,这种方式将大大缩减客户端与数据库之间的链接、关闭等消耗,使得效率比分开执行的单个INSERT语句快(在大部分状况下,使用多个值表的INSERT语句能比单个INSERT语句快上好几倍)。
  2. 若是从不一样客户插入不少行,能够经过使用ISNERT DELAYED语句获得更高的速度。DELAYED的含义是让INSERT语句立刻执行,其实数据都被放在内存的队列中,并无真正写入磁盘,这比每条语句分别插入要快得多;LOW_PRIORITY恰好相反,在全部其余用户对表的写完成后才进行插入。
  3. 将索引文件和数据文件分在不一样的磁盘上存放(利用建表中的选项)
  4. 若是进行批量插入,能够经过增长bulk_insert_buffer_size变量值得方法来提升速度,可是,这只能对MyISAM表使用。
  5. 当从一个文本文件装载一个表时,使用LOAD DATA INFILE。这一般比使用不少INSERT语句快20倍。
优化ORDER BY语句

MySQL中有两种排序方式

第一种经过有序索引顺序扫描直接返回有序数据,这种方式在使用explain分析查询的时候显示为Using index,不须要额外的排序,操做效率较高。

第二种是经过对返回数据进行排序,也就是一般说的FileSort排序,全部不是经过索引直接返回排序结果的排序都叫Filesort排序。Filesort并不表明经过磁盘文件进行排序,而只是说明进行了一个排序操做,至于排序操做是否使用了磁盘文件或临时表等,则取决于MySQL服务器对排序参数的设置和须要排序数据的大小。

Filesort是经过相应的排序算法,将取得的数据在sort_buffer_size系统变量设置的内存排序区中进行排序,若是内存装载不下,它就会将磁盘上的数据进行分块,再对各个数据块进行排序,而后将各个块合并成有序的结果集。sort_buffer_size设置的排序区是每一个线程独占的,因此同一时刻,MySQL中存在多个sort_buffer_size排序区。

优化GROUP BY语句

若是查询包括GROUP BY但用户想要避免排序结果的消耗,则能够指定ORDER BY NULL禁止排序。

优化嵌套查询

在某些状况下,子查询能够被更有效率的链接(JOIN)替代。

链接(JOIN)之因此更有效率一些,是由于MySQL不须要在内存中建立临时表来完成这个逻辑上须要两个步骤的查询工做。

MySQL如何优化OR条件

对于含有OR的查询子句,若是要利用索引,则OR之间的每一个条件都必须用到索引;若是没有索引,则应该考虑增长索引。

优化分页查询
  1. 第一种优化思路

    在索引上完成排序分页的操做,最后根据主键关联回原表查询所须要的其余列内容。

  2. 第二种优化思路

    把LIMIT查询转换为某个位置的查询。例如,分页的时候,增长last_page_record,用来记录上一页最后一行的id。

    注意,这样把LIMIT m, n转换成LIMIT n的查询,只适合在排序字段不会出现重复值的特定环境下,可以减轻分页翻页的压力;若是排序字段出现大量重复值,而仍进行这种优化,那么分页结果可能会丢失部分记录,不适用这种方式优化。

适用SQL提示
  1. USE INDEX

    在查询语句中表名的后面,添加USE INDEX来提供但愿MySQL去参考的索引列表,就可让MySQL再也不考虑其余可用的索引。

    mysql> explain select count(*) from rental use index(idx_rental_date);
    复制代码
  2. IGNORE INDEX

    让MySQL忽略一个或者多个索引,则可使用IGNORE INDEX做为HINT。

    mysql> explain select count(*) from rental ignore index(idx_rental_date);
    复制代码
  3. FORCE INDEX

    强制MySQL使用一个特定的索引,可在查询中使用FORCE INDEX做为HINT。

    mysql> explain select * from rental force index(idx_fk_inventory_id) where inventory_id > 1;
    复制代码

优化数据库对象

优化表的数据类型

在MySQL中,可使用函数PROCEDURE ANALYSE()对当前应用的表进行分析,该函数能够对数据表中列的数据类型提出优化建议。

SELECT * FROM tbl_name PROCEDURE ANALYSE();
-- 不要为那些包含的值多于16个或者256个字节的ENUM类型提出建议
SELECT * FROM tbl_name PROCEDURE ANALYSE(16, 256);
复制代码

经过拆分提升表的访问效率

  1. 垂直拆分

    若是一张表中某些列经常使用,某些列不经常使用,可使用垂直拆分,另外,垂直拆分可使得数据行变小,一个数据页就能存放更多的数据,在查询时就会减小I/O次数。

    缺点是,须要管理冗余列,查询全部数据须要联合(JOIN)操做。

  2. 水平拆分

    根据一列或多列数据的值把数据行放到两个独立的表中。

逆规范化

反规范的好处是下降链接操做的需求、下降外码和索引的数目,还可能减小表的数目,相应带来的问题是可能出现数据的完整性问题。加快查询速度,但会下降修改速度。

经常使用的范规范技术有增长冗余列、增长派生列、从新组表和分割表。

  • 增长冗余列:指在多个表中具备相同的列,它经常使用来在查询时避免链接操做。
  • 增长派生列:指增长的列来自其余表中的数据,由其余表中的数据通过计算生成。
  • 从新组表:指若是许多用户须要查看两个表链接出来的结果数据,则把这两个表从新组成一个表来减小链接而提升性能。
  • 分割表

另外,逆规范化技术须要维护数据的完整性。经常使用的方法是批处理维护、应用逻辑和触发器。

使用中间表提升统计查询速度

对于数据量较大的表,在其上进行统计查询一般会效率很低,而且要考虑统计查询是否会对在线的应用产生负面影响。一般在这种状况下,使用中间表能够提升统计查询的效率。

锁问题

MySQL锁概述

MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);DBD存储引擎采用的是页面锁(page-level locking),但也支持表级锁;InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认状况下采用行级锁。

MySQL这3种锁的特性可大体概括以下:

  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的几率最高,并发度最低。
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的几率最低,并发度也最高。
  • 页面锁:开销和加锁时间介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度通常。

MyISAM表锁

MyISAM存储引擎只支持表锁。

查询表级锁争用状况
mysql> show status like 'table%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Table_locks_immediate      | 3     |
| Table_locks_waited         | 0     |
+----------------------------+-------+
复制代码

若是Table_locks_waited的值比较高,则说明存在着较严重的表级锁争用状况。

MySQL表级锁的锁模式

MySQL表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。

如何加表锁

MyISAM在执行查询语句(SELECT)前,会自动给涉及的全部表加读锁,在执行更新操做(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不须要用户干预。

显示加锁与解锁

-- 加读锁
lock table tbl_name read [local];
lock talbes tbl1_name read local, tbl2_name read local;
-- 写锁
lock table tbl_name write;
-- 解锁
unlock tables;
复制代码
  • local选项,其做用就是在知足MyISAM表并发插入条件的状况下,容许其余用户在表尾并发插入记录。
  • 在用LOCK TABLES给表显式加锁时,必须同时取得全部涉及表的锁,而且MySQL不支持锁升级。
  • 当用LOCK TABLES时,不只须要一次锁定用到的全部表,并且,同一个表在SQL语句中出现多少次,就要经过与SQL语句中相同的别名锁定多少次,不然也会出错!
并发插入

MyISAM表也支持查询和插入操做的并发进行。

MyISAM存储引擎有一个系统变量concurrent_insert,专门用于控制其并发插入的行为,其值分别能够为0、1或2。

  • 0:不容许并发插入。
  • 1:若是MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM容许在一个进程读表的同时,另外一个进程从尾部插入记录。这也是MySQL的默认设置。
  • 2:不管MyISAM表中有没有空洞,都容许在表尾并发插入记录。
MyISAM的锁调度

MyISAM存储引擎的读锁和写锁都是互斥的,读写操做是串行的。那么,一个进程请求某个MyISAM表的读锁,同时另外一个进程也请求同一个表的写锁,MySQL如何处理呢?答案是写进程先得到锁。不只如此,即便读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求以前!这是由于MySQL认为写请求通常比读请求更重要。这也是MyISAM表不太适合有大量更新操做和查询操做应用的缘由,由于,大量的更新操做会形成查询操做很难得到写锁,从而可能永远阻塞。

能够经过一些设置来调节MyISAM的调度行为。

  • 经过指定启动参数low-priority-updates,使MyISAM引擎默认给予读请求以优先的权利。
  • 经过执行命令SET LOW_PRIORITY_UPDATES=1,使该链接发出的更新请求优先级下降。
  • 经过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,下降该语句的优先级。

另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级下降,给读进程必定得到锁的机会。

InnoDB锁问题

InnoDB支持事务,采用了行级锁。

事务

事务是由一组SQL语句组成的逻辑处理单元,事务具备4个属性:原子性(Atomicity)、一致性(Consistent)、隔离性(Isolation)、持久性(Durable)。

并发事务带来的问题

  • 更新丢失(Lost Update):当两个或多个事务选择同一行,而后基于最初选定的值更新该行时,因为每一个事务都不知道其余事务的存在,就会发生丢失更新问题。
  • 脏读(Dirty Reads):一个事务正在对一条记录作修改,在这个事务完成并提交以前,这个记录的数据就处于不一致状态;另外一个事务读取到了这些“脏”数据,就会产生读未提交的数据。
  • 不可重复读(Non-Repeatable Reads):一个事务在读取某些数据后的某个时间,再次读取之前读过的数据,却发现其读出的数据已经发生了改变或某些记录已经被删除了!这种现象就叫“不可重复读”。
  • 幻读(Phantom Reads):一个事务按照相同的查询提交从新读取之前检索过的数据,却发现其余事务插入了知足其查询条件的新数据,这种现象就称为“幻读”。

事务隔离级别

数据库实现事务隔离的方式,基本上可分为如下两种。

  • 在读取数据前,对其加锁,阻止其余事务对数据进行修改。
  • 不加任何锁,经过必定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供必定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库能够提供同一数据的多个版本,所以,这种技术叫作数据多版本并发控制(Multi Version Concurrency Control,简称MVCC或MCC),也常常成为多版本数据库。

4种隔离性比较

隔离级别\读数据一致性及并发反作用 读数据一致性 脏读 不可重复读 幻读
未提交读(Read uncommitted) 最低级别,只能保证不读取物理上损坏的数据
已提交读(Read committed) 语句级
可重复读(Repeatable read) 事务级
可序列化(Serializable) 最高级别,事务级
获取InnoDB行锁争用状况

能够经过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺状况

mysql> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0     |
| Innodb_row_lock_time          | 0     |
| Innodb_row_lock_time_avg      | 0     |
| Innodb_row_lock_time_max      | 0     |
| Innodb_row_lock_waits         | 0     |
+-------------------------------+-------+
复制代码

若是锁争用状况比较严重,InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高,能够经过查询 infomation_schema 数据库中相关的表来查看锁状况,或经过设置InnoDB Monitors来进一步观察发生锁冲突的表、数据行等,并分析锁争用的缘由。

InnoDB的行锁模式及加锁方法

InnoDB实现了如下两种类型的行锁。

  • 共享锁(S):容许一个事务去读一行,阻止其余事务得到相同数据集的排他锁。
  • 排他锁(X):容许得到排他锁的事务更新数据,阻止其余事务取得相同的数据集的共享读锁和排他写锁。

另外,为了容许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

  • 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
  • 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

上述锁模式的兼容状况具体以下:

当前锁模式\是否兼容\请求锁模式 X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务能够经过如下语句显示给记录集加共享锁或排他锁。

  • 共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
  • 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE

用 SELECT ... IN SHARE MODE得到共享锁,主要用在须要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操做。可是若是当前事务也须要对该记录进行更新操做,则颇有可能形成死锁,对于锁定行记录后须要进行更新操做的应用,应该使用SELECT ... FOR UPDATE方式得到排他锁。

InnoDB行锁实现方式

InnoDB行锁是经过给索引上的索引项加锁来实现的,若是没有索引,InnoDB将经过隐藏的聚簇索引来对记录加锁。InnoDB行锁分为3种情形。

  • Record lock:对索引加锁。
  • Gap lock:对索引之间的“间隙”、第一条记录前的“间隙”或最后一条记录的“间隙”加锁。
  • Next-key lock:前两种的组合,对记录及其前面的间隙加锁。

InnoDB这种行锁实现特色意味着:若是不经过索引条件检索数据,那么InnoDB将对表中的全部记录加锁,实际效果跟表锁同样。

注意如下几点:

  1. 在不经过索引条件查询时,InnoDB会锁定表中的全部记录。
  2. 因为MySQL的行锁是针对索引加的锁,不是针对记录加的锁,因此虽然是访问不一样行的记录,可是若是是使用相同的索引键,是会出现锁冲突的。
  3. 当表有多个索引的时候,不一样的事务可使用不一样的索引锁定不一样的行,不管是使用主键索引、惟一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
  4. 即使在条件中使用了索引字段,可是否使用索引来检索数据是由MySQL经过判断不一样执行计划的代价来决定的,若是MySQL不使用索引,这种状况下InnoDB也会对全部记录加锁。所以,在分析锁冲突时,别忘记了检查SQL的执行计划,以确认是否真正使用了索引。
Next-Key锁

当咱们使用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但不存在的记录,叫作“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的Next-Key锁。

举例来讲,假如emp表中只有101条记录,其empid的值分别是一、二、...、100、101,下面的SQL:

select * from emp where empid > 100 for update;
复制代码

是一个范围条件的检索,InnoDB不只会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。

InnoDB使用Next-Key锁的目的,一方面是为了防止幻读,以知足相关隔离级别的要求;另外一方面,是为了知足其恢复和复制的须要。

还要特别说明的是,InnoDB除了经过范围条件加锁时使用Next-Key锁外,若是使用相等条件请求一个不存在的记录加锁,InnoDB也会使用Next-Key锁!

恢复和复制的须要,对InnoDB锁机制的影响

MySQL经过BINLOG记录执行成功的INSERT、UPDATE、DELETE等更新数据的SQL语句,并由此实现MySQL数据库的恢复和主从复制。MySQL 5.6 支持3种日志格式,即基于语句的日志格式SBL、基于行的日志格式RBL和混合格式。还支持4种复制模式。

  1. 基于SQL语句的复制SBR:这也是MySQL最先支持的复制模式。
  2. 基于行数据的复制RBR:这是MySQL5.1之后开始支持的复制模式,主要优势是支持对非安全SQL的复制。
  3. 混合复制模式:对安全的SQL语句采用基于SQL语句的复制模式,对于非安全的SQL语句采用居于行的复制模式。
  4. 使用全局事务ID(GTIDs)的复制:主要是解决主从自动同步一致性问题。

对基于语句日志格式(SBL)的恢复和复制而言,因为MySQL的BINLOG是按照事务提交的前后顺序记录的,所以要正确恢复或复制数据,就必须知足:在一个事务未提交前,其余并发事务不能插入知足其锁定条件的任何记录,也就是不容许出现幻读。这已经超过了ISO/ANSI SQL92“可重复读”隔离级别的要求,其实是要求事务要串行化。这也是许多状况下,InnoDB要用到Next-Key锁的缘由,好比在用范围条件更新记录时,不管实在Read Commited或是Repeatable Read隔离级别下,InnoDB都要使用Next-Key锁,但这并非隔离级别要求的。

ISNERT ... SELECT ... 和 CREATE TALBE ... SELECT ..语句,可能会阻止对源表的并发更新。若是查询比较复杂,会形成严重的性能问题,读者在应用中应尽可能避免使用。实际上,MySQL将这种SQL叫作不肯定(non-deterministic)的SQL,属于“Unsafe SQL”,不推荐使用。

何时使用表锁
  • 第一种状况是:事务须要更新大部分或所有数据,表又比较大,若是使用默认的行锁,不只这个事务执行效率低,并且可能形成其余事务长时间锁等待和锁冲突,这种状况下能够考虑使用表锁来提升该事务的执行速度。
  • 第二种状况是:事务涉及多个表,比较复杂,极可能引发死锁,形成大量事务回滚。这种状况也能够考虑一次性锁定事务涉及的表,从而避免死锁,减小数据库因事务回滚带来的开销。

在InnoDB下,使用表锁要注意如下两点。

  • 使用LOCK TABLES虽然能够给InnoDB叫表锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层——MySQL Server负责的,仅当autocommit=0、innodb_table_locks=1(默认设置)时,InnoDB层才能自动识别涉及表锁的死锁;不然,InnoDB将没法自动检测并处理这种死锁。
  • 在用LOCK TABLES对InnoDB表加锁时要注意,要将AUTOCOMMIT设为0,不然MySQL不会给表加锁;事务结束前,不要用UNLOCK TABLES释放表锁,由于UNLOCK TABLES会隐含地提交事务;COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表锁,必须用UNLOCK TABLES释放表锁。
关于死锁

MyISAM表锁是deadlock free的,这是由于MyISAM老是一次得到所需的所有锁,要么所有知足,要么等待,所以不会出现死锁。但在InnoDB中,除单个SQL组成的事务外,锁是逐步得到的,这就决定了在InnoDB中发生死锁是有可能的。

发生死锁后,InnoDB通常都能自动检测到,并使一个事务释放锁并回退,另外一个事务得到锁,继续完成事务。但在涉及外部锁或涉及表锁的状况下,InnoDB并不能彻底自动检测到死锁,这须要经过设置锁等待超时参数innodb_lock_wait_timeout来解决。须要说明的是,这个参数并非只用来解决死锁问题,在并发访问比较高的状况下,若是大量事务因没法当即得到所需的锁而挂起,会占用大量计算机资源,形成严重性能问题,设置拖垮数据库。咱们经过设置合适的锁等待超时阈值,能够避免这种状况发生。

避免死锁的经常使用方法:

  1. 在应用中,若是不一样的程序会并发存取多个表,应尽可能约定以相同的顺序来访问表,这样能够大大下降产生死锁的机会。
  2. 在程序以批量方式处理数据的时候,若是事先对数据排序,保证每一个线程按固定的顺序来处理记录,也能够大大下降出现死锁的可能。
  3. 在事务中,若是须要更新记录,应该直接申请足够级别的锁,即排他锁,而不该该先申请共享锁,更新时再申请排他锁,由于当用户申请排他锁时,其余事务可能又已经得到了相同记录的共享锁,从而形成锁冲突,甚至死锁。
  4. 在REPEATABLE-READ隔离级别下,若是两个线程同时对相同条件记录用SELECT ... FOR UPDATE加排他锁,在没有符合该条件记录状况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,若是两个线程都这么作,就会出现死锁。将隔离级别改为READ COMMITTED,就可避免问题。
  5. 当隔离级别为READ COMMITTED时,若是两个线程都先执行SELECT ... FOR UPDATE,判断是否存在符合条件的记录,若是没有,就插入记录。此时,只有一个线程能插入成功,另外一个线程会出现锁等待,当第1个线程提交后,第2个线程会因主键重出错,但虽然这个线程出错了,却会得到一个排他锁!这时若是出现第3个线程又来申请排他锁,也会出现死锁。
相关文章
相关标签/搜索