mysql总结
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql总结相关的知识,希望对你有一定的参考价值。
【数据库引擎】
mysql具有 免费开源、稳定性好、配置简单、支护支持所有操作系统。myisam存储引擎性速度快,但是不支持事务处理,不支持外键。mysql使用的默认引擎是nowinDB,支持事务处理,支持外键,磁盘读写性能差一点,空间占据大一些。
myisam:默认的MySQL 插件式存储引擎。如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常适合的。
InnoDB:用于事务处理应用程序,支持外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询以外,还包括很多的更新、删除操作,那么InnoDB 存储引擎应该是比较合适的选择。InnoDB 存储引擎除了有效地降低由于删除和更新导致的锁定,还可以确保事务的完整提交(Commit)和回滚(Rollback),对于类似计费系统或者财务系统等对数据准确性要求比较高的系统,InnoDB 都是合适的选.
MEMORY:将所有数据保存在RAM 中,在需要快速定位记录和其他类似数据的环境下,可提供极快的访问。MEMORY 的缺陷是对表的大小有限制,太大的表无法CACHE 在内存中,其次是要确保表的数据可以恢复,数据库异常终止后表中的数据是可以恢复的。MEMORY 表通常用于更新不太频繁的小表,用以快速得到访问结果。
MySQL中提到了存储引擎的概念。简而方之,存储引擎就是指表的类型。数据库存储引擎决定了表在计算机中的存储方式。
MyISAM:空间小,速度快,但是不支持事务处理,外键等功能。InnoDB:性能不如myisam但是支持事务处理,外键等。mysql默认使用的就是innodb引擎。
可以在mysql.ini配置文件中修改数据库默认引擎。
在MySQL 中,不同的存储引擎对CHAR 和VARCHAR 的使用原则有所不同,这里简单概括如下。
MyISAM 存储引擎:建议使用固定长度的数据列代替可变长度的数据列。
MEMORY 存储引擎:目前都使用固定长度的数据行存储,因此无论使用CHAR 或VARCHAR 列都没有关系。两者都是作为CHAR 类型处理。
InnoDB 存储引擎:建议使用VARCHAR 类型。对于InnoDB 数据表,内部的行存储
格式没有区分固定长度和可变长度列(所有数据行都使用指向数据列值的头指针),因此在
本质上,使用固定长度的CHAR 列不一定比使用可变长度VARCHAR 列性能要好。因而,主
要的性能因素是数据行使用的存储总量。由于CHAR 平均占用的空间多于VARCHAR,因此使
用VARCHAR 来最小化需要处理的数据行的存储总量和磁盘I/O 是比较好的。
【数据库安装】
Mysql安装类型:typical表示一般常用的组件都会安装。complete表示会安装所有组件。custom表示根据用户可以选择需要安装的组件。Linux安装mysql:1. # rpm -ivh MySQL-server-5.0.45.rpm 2. # rpm -ivh MySQL-client-5.0.45.rpm。安装完事后,mysql的参数文件会在/usr/share/mysql下,可以选择一个文件,改名成my.cnf(windows下是my.ini),mysql启动时候会读取此文件的配置。
mysql服务跟mysql数据库不同,mysql服务是一系列的后台进程,而mysql数据库则是一系列的数据目录和数据文件。mysql数据库必须在mysql服务启动后才可以进行访问。mysql服务的关闭和启用,除了通过用户图形界面操作,还可以通过命令。windows下启动服务,进到bin目录下执行mysqld --conolse。Linux环境下查看mysql运行状态# netstart -nlp,启动服务# service mysql start,重启服务: #service mysql restart,关闭服务:#service mysql stop。
启动和关闭mysql服务器,启动:net start mysql;关闭:net stop mysql
切换数据库:USE mydb1,切换到mydb1数据库;修改数据库编码:ALTER DATABASE mydb1 CHARACTER SET utf8(注意编码中间没有-)
日志是MySQL数据库的重要组成部分。日志文件中记录着MySQL数据库运行期间发生的变化。当数据库遭到意外的损害时,可以通过日志文件来查询出错原因,并且可以通过日志文件进行数据恢复。
二进制日志记录了用记对数据库中数据的改变。如INSERT语句、UPDATE语句、CREATE语句等都会记录到二进制日志中。一旦数据库遭到破坏,可以使用二进制日志还原数据库。
错误日志是MySQL数据库中最常用的一种日志。错误日志主要用来记录MySQL服务的开户、关闭和错误信息。
在MySQL数据库中,错误日志功能是默认开启的。而且,错误日志无法被禁止。默认情况下,错误日志存储在MySQL数据库的数据文件夹下。错误日志文件通常的名称为hostname.err。其中,hostname表示MySQL服务器的主机名。错误日志的存储位置可以通过log-error选项来设置。
错误日志是以文本文件形式存储的,可以直接使用普通文件工具就可以查看。Windows操作系统可以使用文本文件查看器查看。Linux操作系统下,可以使用vi工具或者使用gedit工具来查看。
【视图和触发器】
视图是从一个或多个表中导出来的表,是一种虚拟存在的表。视图就像一个窗口,通过这个窗口可以看到系统专门提供的数据。视图中的数据是依赖于原来的表中的数据的。一旦表中的数据发生改变,显示在视图中的数据也会发生改变。
触发器(TRIGGER)是由事件来触发某个操作。这些事件包括INSERT语句、UPDATE语句和DELETE语句。当数据库系统执行这些事件时,就会激活触发器执行相应的操作。MySQL从5.0.2版本开始支持触发器。
触发器是由INSERT、UPDATE和DELETE等事件触发某种特定操作。满足触发器的触发条件时,数据库系统就会执行触发器中定义的程序语句。这样做可以保证某些操作之间的一致性。例如,当学生表中增加了一个学生的信息时,学生的总数就必须同时改变。可以在这里创建一个触发器,每次增加一个学生的记录,就执行一次计算学生总数的操作。这样就可以保证每次增加学生的记录后,学生总数是与记录数是一致的。触发器触发的执行语句可能只有一个,也可能有多个。
MySQL中,触发器执行的顺序是BEFORE触发器、表操作(INSERT、UPDATE和DELETE)、AFTER触发器。在department表上创建BEFORE INSERT和AFTER INSERT这两个触发器。在向department表中插入数据时,观察这两个触发器的触发顺序。
USE example;
CREATE TABLE trigger_test(
id INT PRIMARY KEY AUTO_INCREMENT,
info VARCHAR(20)
);
DROP TRIGGER dept_trig1;
CREATE TRIGGER before_insert BEFORE INSERT
ON department FOR EACH ROW
INSERT INTO trigger_test VALUES(NULL, ‘before_insert‘);
CREATE TRIGGER after_insert AFTER INSERT
ON department FOR EACH ROW
INSERT INTO trigger_test VALUES(NULL, ‘after_insert‘);
INSERT INTO department VALUES(1003, ‘销售部‘, ‘负责产品销售‘, ‘1号楼销售大厅‘);
【基础语法知识】
SQL语句分类,DDL数据定义语言,包括create、drop、alter等。DML数据操作语句,包括insert、update、select、delete,distinct等。DCL数据控制语言包括grant、 revoke。group by 、Oder by、in、not in、exists 、not exists、union、union all。
grant select、insert on 数据库名.* to 用户名@localhost identified by 密码;
revoke insert on 数据库名.* from 用户名@localhost; 删除inset权限只保留select权限。
查看用户权限SHOW GRANTS FOR 用户名,删除用户DROP USER 用户名,
修改用户密码UPDATE USER SET PASSWORD=PASSWORD(‘密码’) WHERE User=’用户名’ and Host=’IP’; FLUSH PRIVILEGES;
虽然TRUNCATE和DELETE都可以删除表的所有记录,但有原理不同。DELETE的效率没有TRUNCATE高!TRUNCATE其实属性DDL语句,因为它是先DROP TABLE,再CREATE TABLE。而且TRUNCATE删除的记录是无法回滚的,但DELETE删除的记录是可以回滚的
show databases,show tables,use dbname,drop database name,desc table,
alter table tablename modify column column_definition;
alter table tablename change column 1 column 2 CHAR(2);
alter table tablename add column column_definition;
alter table tablename drop column column_name;
create database if not exists `databasename`;
left(s,n)函数返回字符串s的前n个字符;
right(s,n)函数将返回字符串s的后n个字符
lpad(s1,len,s2)函数将字符串s2填充到s1的开始处,使字符串长度达到len;
rpad(s1,len,s2)函数将字符串s2填充到s1的结尾处,使字符串长度达到len。
删除空格的函数ltrim(s)、rtrim(s)和trim(s)
空格函数space(n)和替换函数replace(s,s1,s2);
字符串逆序的函数reverse(s);
获取当前日期: curdate()和current_date()
获取当前时间: curtime()和current_time()
获取当前日期和时间的函数: now(),sysdate()
获取月分的函数month(d)和monthname(d)
获取星期的函数 dayname(d)、dayofweek(d)和weekday(d);0表示星期一,一表示星期二
dayofmonth(d)函数返回计算日期d是本月的第几天。
dayofyear(d)函数日期d是本年的第几天;
year(d)函数返回日期d中的年份值;
quarter(d)函数返回日期d是本年第几季度,值范围是1~4
hour(t)函数是返回时间t中的小时值;
minute(t)函数是返回时间t中的分钟值;
second(t)函数返回时间t中的秒钟值。
insert(str,x,y,instr) 字符串替换。lower(str),upper(str),substring(str,x,y)。
abs(x)绝对值,ceil(x)返回大于x的最大整数,floor(x)返回小于x的最大整数。rand()返回0~1随机数。
round(x,y) x的四舍五入有y位的小数值。
加密password(str),加密函数md5(str),
database()返回当前数据库名,version()返回当前数据库版本,user()返回当前登录的用户名
使用字符串pswd_str来为crypt_str解密。crypt_str是通过encode(str,pswd_str)加密后的二进制数据。字符串pswd_str应该与加密时的字符串pswd_str是相同的。
数值类型:tinyint占用1字节,smallint占2字节,int站4字节,bigint站8字节,
float站4字节,double站8字节,decimal,bit.
时间类型:年月日用Date占用4字节,年月日时分秒用Datetime占用8字节,时分秒用time占用3字节,yeah表示年占用1字节。
字符类型:char,varchar,blob,longblob,text,longtext;
char是固定的长度,在存储数据长度不够的时候,自动补充空格,查询的时候默认把尾部空格去掉。
varchar是可变的长度,长度不够会自动补充空格,查询的时候不会去掉尾部空格,把尾部空格也算长度。
算数运算符:+,-,*,/,%
比较运算符:=,<>,!=,<,<=,>,>=, between and ,in ,not in , exist,not exist,is null ,is not null,like.
逻辑运算符:and,or,!,not,||
if(boolean,a,b) boolean判断如果成立则显示a,否则显示b。
ifNull(a,b) 如果a为null,则显示b。
case when then else end。
date_sub(info.FINANCE_END_DATE,interval 1 day) 当前日期减一天
2. 如何为自增字段(AUTO_INCREMENT)赋值?
答:在INSERT语句中可以直接为自增字段赋值。但是,大部分的自增字段是需要数据库系统为其自动生成一个值的。这样可以保证这个值的惟一性。用户可以通过两种方式来让数据库系统自动为自增字段赋值。第一种方法是在INSERT语句中不为该字段赋值。第二种方法是在INSERT语句中将该字段赋值为NULL。这两种情况下,数据库系统自动为自增字段赋值。而且,其值是上条记录中该字段的取值值加一。
3. 如何进行联表删除?
这个可以通过外键来实现。其它表中的信息与学生表中的信息都是通过学号来联系的。根据学号查询存在该同学信息的表,删除相应的数据。联表删除可以保证数据库中数据的一致性。
1. 比较比较运算符结果只能是0和1吗?
答:MySQL中,比较运算符用来判断运算符两边的操作数的大小关系。例如a>b就是用来判断a是否大于b。如果大于则返回真;如果不大于则返回假。MySQL中,真是用1来表示的,假是用0来表示的。所以,比较运算符的运算结果只有0和1.不仅比较运算符是如此,逻辑运算符的运算结果也只有0和1。
2. 哪种运算符的优先级别最高?
答:非运算(!)的级别最高,赋值符号(:=)的级别最低。但是,通常情况下可以使用括号来设定运算符的先后顺序。使用括号可以使运算的层次更加清晰,而且可以不必局限于各种运算符的优先级别。
3. 十进制数也可以直接使用位运算符吗?
答:在进行位运算时,数据库系统会先将所有的操作数转换为二进制数。然后将这些二进制数进行位运算,然后将这些运算结果转换为二进制数。所以,位运算符的操作数是十进制数。十进制数与二进制数之间的互换是数据库系统实现的。因此,位运算的操作数必须是十进制数,否则计算的结果就会是错误的。在使用位运算符时,如果操作数是二进制数、八进制数、十六进制数,要先通过CONV()函数将操作数转换为十进制数。然后,才能进行相应的位运算。
表中birth字段存的出生日期,如何来计算年龄?
年龄是通过当前年份减去出生的年份来计算的。但是brith字段中有年、月、日,这就必须从birth字段中过滤出出生的年份。MySQL中提供了year()函数用来获取日期中的年份。如year(‘2008-08-08‘)的返回结果是2008。所以,可以通过year(birth)来获取出生的年份。可以通过year(now())或者year(current_date())来获取当前的年份。这两者相减就可以获得年龄了。
如何改变字符串的字符集?
在安装MySQL时就已经设置了数据库的字符编码。字符串的字符集与字符编码是一个意思。MySQL中可以通过重新配置字符集来修改字符集。也可以在MySQL中安装路径下修改my.ini。将default-charset-set的值改变来修改字符集。上面这两种方式将改变整个数据库的字符集。如果只想改变某个字符串的字符集,可以使用convert(s suing cs)函数。该函数可以将字符串s的字符集变成cs。
用户的密码应该怎么加密?
MySQL中可以使用password(str)函数来给密码加密。这个密码是不可逆的,即使有人取得了加密后的数据,也不能通过解密来获取密码值。系统会将用户注册时输入的密码通过password(str)函数来加密。将加密后的密码存入表中。用户登录时,系统会将再次用户输入的密码用password(str)函数加密,将加密后的数据与表中的数据进行比较。如果相等,说明用户输入的密码是正确的。
1. MySQL中索引、主键和惟一性的区别是什么?
索引建立在一个或者多个字段上。建立了索引后,表中的数据就按照索引的一定规则排列。这样可以提高查询速度。主键是表中数据的唯一标识。不同的记录的主键值不同。在建立主键的时候,系统会自动建立一个惟一性索引。唯一性也是建立在表中一个或者几个字段上。其目的是为了对于不同的记录,具有唯一性的字段的值是不同的。
MySQL中如何存储JPG图片和MP3音乐?
一般情况下,数据库中不直接存储图片和音乐文件。而是存储图片和文件的路径。如果实在需要在MySQL数据库中存储图片和音频文件,就选择BLOB类型。因为,BLOB类型可以用来存储二进制类型的文件。
【事务控制】
事务的特性:
原子性事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
隔离性数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。
一致性在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性。
持久性事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
并发事务处理带来的问题
更新丢失(Lost Update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题--最后的更新覆盖了由其他事务所做的更新。例如,两个编辑人员制作了同一文档的电子副本。每个编辑人员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖另一个编辑人员所做的更改。如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同一文件,则可避免此问题。
脏读(Dirty Reads):一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做"脏读"
不可重复读(Non-Repeatable Reads):一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。
幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”
【数据锁机制】
lock table tablename read; 锁定表只能读操作。unlock tables 表解锁。set autocommit={0,1} 设置是否自动提交。commit; rollback; start transaction开启事物的时候,如果表是锁定的,那么默认将表解锁; mysql的事务控制,目前只有innodb引擎支持事务控制。默认情况下mysql自动提交事务,例如:一个事务A中对表进行开启事务,然后insert两条记录,并没有commit,那么在A事务中查询是可以看到两条未提交的记录的。在其他事务中在查询表,是看不到这两条insert数据的,因为未提交。
分布式事务机制:一个分布式事务会涉及多个行动,这些行动本身是事务性的,所有的行动必须一起被完成或者回滚。开启事务:xa start ‘唯一的分布式事务名称‘,‘唯一子分布式事务名称‘;执行insert,update等语句,然后进入prepare阶段:xa end ‘唯一的分布式事务名称‘,‘唯一子分布式事务名称‘;xa prepare ‘唯一的分布式事务名称‘,‘唯一子分布式事务名称‘;查看分支事务状态:xa recover;当两个事务都进入准备提交阶段,如果之前遇到任何错误,都应该回滚所有分支,确保分布式事务数据正确。提交分支事务:xa commit ‘唯一的分布式事务名称‘,‘唯一子分布式事务名称‘;主从复制也是基于分布式事务机制的。
在上面讲到的并发事务处理带来的问题中,“更新丢失”通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web 应用。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。
共享锁(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 方式获得排他锁。
当前session1 对actor_id=178 的记录加share mode的共享锁。
session1:select actor_id,first_name,last_name from actor where actor_id = 178 lock in share mode;
其他session2 仍然可以查询记录,并也可以对该记录加share mode 的共享锁:
session2:select actor_id,first_name,last_namefrom actor where actor_id = 178 lock in share mode;
当前session1 对锁定的记录进行更新操作,等待锁:
session1:update actor set last_name = ‘MONROE T‘ where actor_id = 178;
其他session2 也对该记录进行更新操作,则会导致死锁退出:
session2:update actor set last_name = ‘MONROE T‘ where actor_id = 178;ERROR 1213 (40001): Deadlock found when tryingto get lock; try restarting transaction
session1:获得锁后,可以成功更新:
当前session1 对actor_id=178 的记录加for update的排他锁:
select actor_id,first_name,last_name from actor where actor_id = 178 for update;
其他session2 可以查询该记录,但是不能对该记录加排他锁,会等待获得锁:
select actor_id,first_name,last_namefrom actor where actor_id = 178 for update;等待
当前session1 可以对锁定的记录进行更新操作,更新后释放锁:释放后session2可以得到锁,执行for update;
【主从复制】
1、MySQL主从复制原理:
MySQL的主从复制解决了数据库的读写分离,并很好的提升了读的性能。有一个主的数据库和多个附属数据库,数据库中表结构是一样的,主数据库用来编辑数据,多个附属数据库用来读取数据。
该过程的第一部分就是master记录二进制日志。在每个事务更新数据完成之前,master在二日志记录这些改变。MySQL将事务串行的写入二进制日志,即使事务中的语句都是交叉执行的。在事件写入二进制日志完成后,master通知存储引擎提交事务。
下一步就是slave将master的binary log拷贝到它自己的中继日志。首先,slave开始一个工作线程——I/O线程。I/O线程在master上打开一个普通的连接,然后开始binlog dump process。Binlog dump process从master的二进制日志中读取事件,如果已经跟上master,它会睡眠并等待master产生新的事件。I/O线程将这些事件写入中继日志。
SQL slave thread(SQL从线程)处理该过程的最后一步。SQL线程从中继日志读取事件,并重放其中的事件而更新slave的数据,使其与master中的数据一致。只要该线程与I/O线程保持一致,中继日志通常会位于OS的缓存中,所以中继日志的开销很小。
此外,在master中也有一个工作线程:和其它MySQL的连接一样,slave在master中打开一个连接也会使得master开始一个线程。复制过程有一个很重要的限制——复制在slave上是串行化的,也就是说master上的并行更新操作不能在slave上并行操作。
2、MySQL主从复制配置:
Master和slave的MySQL数据库版本同为5.1.17
操作系统:centos 6.5
IP地址:10.100.0.100
2.1、创建复制帐号
1、在Master的数据库中建立一个备份帐户:每个slave使用标准的MySQL用户名和密码连接master。进行复制操作的用户会授予REPLICATION SLAVE权限。用户名的密码都会存储在文本文件master.info中
命令如下:
mysql > GRANT REPLICATION SLAVE,RELOAD,SUPER ON *.*
TO [email protected]’10.100.0.200’
IDENTIFIED BY ‘1234’;
建立一个帐户backup,并且只能允许从10.100.0.200这个地址上来登陆,密码是1234。这里@后面的ip地址就是slave的ip
(如果因为mysql版本新旧密码算法不同,可以设置:set password for ‘backup‘@‘10.100.0.200‘=old_password(‘1234‘))
2.2、拷贝数据
(假如是你完全新安装mysql主从服务器,这个一步就不需要。因为新安装的master和slave有相同的数据)
关停Master服务器,将Master中的数据拷贝到B服务器中,使得Master和slave中的数据同步,并且确保在全部设置操作结束前,禁止在Master和slave服务器中进行写操作,使得两数据库中的数据一定要相同!
2.3、配置master
接下来对master进行配置,包括打开二进制日志,指定唯一的servr ID。例如,在配置文件加入如下值:
server-id=1
log-bin=mysql-bin
server-id:为主服务器A的ID值
log-bin:二进制变更日值
重启master
2.4、配置slave
Slave的配置与master类似,你同样需要重启slave的MySQL。如下:
log_bin = mysql-bin
server_id = 2
relay_log = mysql-relay-bin
log_slave_updates = 1
read_only = 1
server_id是必须的,而且唯一。slave没有必要开启二进制日志,但是在一些情况下,必须设置,例如,如果slave为其它slave的master,必须设置bin_log。在这里,我们开启了二进制日志,而且显示的命名(默认名称为hostname,但是,如果hostname改变则会出现问题)。
relay_log配置中继日志,log_slave_updates表示slave将复制事件写进自己的二进制日志(后面会看到它的用处)。
有些人开启了slave的二进制日志,却没有设置log_slave_updates,然后查看slave的数据是否改变,这是一种错误的配置。所以,尽量使用read_only,它防止改变数据(除了特殊的线程)。但是,read_only并是很实用,特别是那些需要在slave上创建表的应用。
2.5、启动slave
接下来就是让slave连接master,并开始重做master二进制日志中的事件。你不应该用配置文件进行该操作,而应该使用CHANGE MASTER TO语句,该语句可以完全取代对配置文件的修改,而且它可以为slave指定不同的master,而不需要停止服务器。如下:
mysql> CHANGE MASTER TO MASTER_HOST=‘server1‘,
-> MASTER_USER=‘repl‘,
-> MASTER_PASSWORD=‘p4ssword‘,
-> MASTER_LOG_FILE=‘mysql-bin.000001‘,
-> MASTER_LOG_POS=0;
-> MASTER_LOG_POS=0;
MASTER_LOG_POS的值为0,因为它是日志的开始位置。
你可以用SHOW SLAVE STATUS语句查看slave的设置是否正确:
mysql> SHOW SLAVE STATUS\\G
*************************** 1. row ***************************
Slave_IO_State:
Master_Host: server1
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 4
Relay_Log_File: mysql-relay-bin.000001
Relay_Log_Pos: 4
Relay_Master_Log_File: mysql-bin.000001
Slave_IO_Running: No
Slave_SQL_Running: No
...omitted...
Seconds_Behind_Master: NULL
Slave_IO_State, Slave_IO_Running, 和Slave_SQL_Running是No
表明slave还没有开始复制过程。日志的位置为4而不是0,这是因为0只是日志文件的开始位置,并不是日志位置。实际上,MySQL知道的第一个事件的位置是4。
为了开始复制,你可以运行:
mysql> START SLAVE;
运行SHOW SLAVE STATUS查看输出结果:
mysql> SHOW SLAVE STATUS\\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: server1
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 164
Relay_Log_File: mysql-relay-bin.000001
Relay_Log_Pos: 164
Relay_Master_Log_File: mysql-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
...omitted...
Seconds_Behind_Master: 0
在这里主要是看:
Slave_IO_Running=Yes
Slave_SQL_Running=Yes
slave的I/O和SQL线程都已经开始运行,而且Seconds_Behind_Master不再是NULL。日志的位置增加了,意味着一些事件被获取并执行了。如果你在master上进行修改,你可以在slave上看到各种日志文件的位置的变化,同样,你也可以看到数据库中数据的变化。
【分库分表】
Mysql分库分表方案
将总字段划分到两个表中,如果一个表中某些列常用,而另外一些列不常用,则可以采用垂直拆分,另外垂直拆分可以使得数据行变小,一个数据页就能存放更多的数据,在查询时就会减少I/O 次数。其缺点是需要管理冗余列,查询所有数据需要联合(JOIN)操作
两个表的字段一样,数据不一样,一个表可能存储的是近期内的数据,另一个表可能存的是历史数据,这样可以根据日期查询不同的表,提高检索速度。
对于数据量较大的表,在其上进行统计查询通常会效率很低,并且还要考虑统计查询是否会对在线的应用产生负面影响。通常在这种情况下,使用中间表可以提高统计查询的效率,其优点如下:
中间表复制源表部分数据,并且与源表相“隔离”,在中间表上做统计查询不会对在线应用产生负面影响;中间表上可以灵活的添加索引或增加临时用的新字段,从而达到提高统计查询效率和辅助统计查询作用。
1.为什么要分表:
当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了。分表的目的就在于此,减小数据库的负担,缩短查询时间。
mysql中有一种机制是表锁定和行锁定,是为了保证数据的完整性。表锁定表示你们都不能对这张表进行操作,必须等我对表操作完才行。行锁定也一样,别的sql必须等我对这条数据操作完了,才能对这条数据进行操作。
2. mysql proxy:amoeba
做mysql集群,利用amoeba。
从上层的java程序来讲,不需要知道主服务器和从服务器的来源,即主从数据库服务器对于上层来讲是透明的。可以通过amoeba来配置。
3.大数据量并且访问频繁的表,将其分为若干个表
比如对于某网站平台的数据库表-公司表,数据量很大,这种能预估出来的大数据量表,我们就事先分出个N个表,这个N是多少,根据实际情况而定。
某网站现在的数据量至多是5000万条,可以设计每张表容纳的数据量是500万条,也就是拆分成10张表,
那么如何判断某张表的数据是否容量已满呢?可以在程序段对于要新增数据的表,在插入前先做统计表记录数量的操作,当<500万条数据,就直接插入,当已经到达阀值,可以在程序段新创建数据库表(或者已经事先创建好),再执行插入操作。
4. 利用merge存储引擎来实现分表
如果要把已有的大数据量表分开比较痛苦,最痛苦的事就是改代码,因为程序里面的sql语句已经写好了。用merge存储引擎来实现分表, 这种方法比较适合.
分库分表原则::
单库单表
单库单表是最常见的数据库设计,例如,有一张用户(user)表放在数据库db中,所有的用户都可以在db库中的user表中查到。
单库多表
随着用户数量的增加,user表的数据量会越来越大,当数据量达到一定程度的时候对user表的查询会渐渐的变慢,从而影响整个DB的性能。如果使用mysql, 还有一个更严重的问题是,当需要添加一列的时候,mysql会锁表,期间所有的读写操作只能等待。
可以通过某种方式将user进行水平的切分,产生两个表结构完全一样的user_0000,user_0001等表,user_0000 + user_0001 + …的数据刚好是一份完整的数据。
多库多表
随着数据量增加也许单台DB的存储空间不够,随着查询量的增加单台数据库服务器已经没办法支撑。这个时候可以再对数据库进行水平区分。
分库分表规则
设计表的时候需要确定此表按照什么样的规则进行分库分表。例如,当有新用户时,程序得确定将此用户信息添加到哪个表中;同理,当登录的时候我们得通过用户的账号找到数据库中对应的记录,所有的这些都需要按照某一规则进行。
路由
通过分库分表规则查找到对应的表和库的过程。如分库分表的规则是user_id mod 4的方式,当用户新注册了一个账号,账号id的123,我们可以通过id mod 4的方式确定此账号应该保存到User_0003表中。当用户123登录的时候,我们通过123 mod 4后确定记录在User_0003中。
分库分表产生的问题,及注意事项
1. 分库分表维度的问题
假如用户购买了商品,需要将交易记录保存取来,如果按照用户的纬度分表,则每个用户的交易记录都保存在同一表中,所以很快很方便的查找到某用户的 购买情况,但是某商品被购买的情况则很有可能分布在多张表中,查找起来比较麻烦。反之,按照商品维度分表,可以很方便的查找到此商品的购买情况,但要查找 到买人的交易记录比较麻烦。
所以常见的解决方式有:
a.通过扫表的方式解决,此方法基本不可能,效率太低了。
b.记录两份数据,一份按照用户纬度分表,一份按照商品维度分表。
c.通过搜索引擎解决,但如果实时性要求很高,又得关系到实时搜索。
2. 联合查询的问题
联合查询基本不可能,因为关联的表有可能不在同一数据库中。
3. 避免跨库事务
避免在一个事务中修改db0中的表的时候同时修改db1中的表,一个是操作起来更复杂,效率也会有一定影响。
4. 尽量把同一组数据放到同一DB服务器上
例如将卖家a的商品和交易信息都放到db0中,当db1挂了的时候,卖家a相关的东西可以正常使用。也就是说避免数据库中的数据依赖另一数据库中的数据。
一主多备
在实际的应用中,绝大部分情况都是读远大于写。Mysql提供了读写分离的机制,所有的写操作都必须对应到Master,读操作可以在 Master和Slave机器上进行,Slave与Master的结构完全一样,一个Master可以有多个Slave,甚至Slave下还可以挂 Slave,通过此方式可以有效的提高DB集群的 QPS.
所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
此外,可以看出Master是集群的瓶颈,当写操作过多,会严重影响到Master的稳定性,如果Master挂掉,整个集群都将不能正常工作。
所以,1. 当读压力很大的时候,可以考虑添加Slave机器的分式解决,但是当Slave机器达到一定的数量就得考虑分库了。 2. 当写压力很大的时候,就必须得进行分库操作。
MySQL使用为什么要分库分表
可以用说用到MySQL的地方,只要数据量一大, 马上就会遇到一个问题,要分库分表.
这里引用一个问题为什么要分库分表呢?MySQL处理不了大的表吗?
其实是可以处理的大表的.我所经历的项目中单表物理上文件大小在80G多,单表记录数在5亿以上,而且这个表 属于一个非常核用的表:朋友关系表.
但这种方式可以说不是一个最佳方式. 因为面临文件系统如Ext3文件系统对大于大文件处理上也有许多问题.
这个层面可以用xfs文件系统进行替换.但MySQL单表太大后有一个问题是不好解决: 表结构调整相关的操作基 本不在可能.所以大项在使用中都会面监着分库分表的应用.
从Innodb本身来讲数据文件的Btree上只有两个锁, 叶子节点锁和子节点锁,可以想而知道,当发生页拆分或是添加 新叶时都会造成表里不能写入数据.
所以分库分表还就是一个比较好的选择了.
那么分库分表多少合适呢?
经测试在单表1000万条记录一下,写入读取性能是比较好的. 这样在留点buffer,那么单表全是数据字型的保持在 800万条记录以下, 有字符型的单表保持在500万以下.
【索引的使用】
索引是创建在表上的,是对数据库中一列或多列的值进行排序的一种结构。
索引的优点:可以提高检索数据的速度,这是创建索引的最主要的原因;对于有依赖关系的子表和父表之间联合查询时,可以提高查询速度;使用分组和排序子句进行数据查询时,同样可以显著节省查询中分组和排序的时间。
索引的缺点:创建和维护索引需要耗费时间,耗费时间的数量随着数据量的增加而增加;索引需要占用物理空间,每一个索引要占一定的物理空间;增加、删除和修改数据时,要动态的维护索引,造成数据的维护速度降低了。
惟一性索引的的值是惟一的,可以更快的通过该索引来确定某条记录。例如,学生表中学号是具有惟一性的字段。为该字段建立惟一性索引可以很快的确定某个学生的信息。如果使用姓名的话,可能存在同名现象,从而降低查询速度。
经常需要排序、分组和联合操作的字段,排序操作会浪费很多时间。如果为其建立索引,可以有效地避免排序操作。 如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。因此,为这样的字段建立索引,可以提高整个表的查询速度。
索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间越大。修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。
如果索引的值很长,那么查询的速度会受到影响。例如,对一个CHAR(100)类型的字段进行全文检索需要时间肯定要比对CHAR(10)类型字段需要的时间要多。
如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLoB类型的字段,进行全文检索会浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再被需要。数据库管理员应当定期找出这些索引,将它们删除,人而减少索引对更新操作的影响。
1. 创建普通索引
CREATE TABLE index2_tbl_1 (id INT,name VARCHAR(20),gender BOOLEAN);
ALTER TABLE index2_tbl_1 ADD INDEX idx (id);
2. 创建惟一性索引
CREATE TABLE index2_tbl_2 (id INT,name VARCHAR(20),gender BOOLEAN );
ALTER TABLE index2_tbl_2 ADD UNIQUE INDEX u_idx (id ASC);
3. 创建全文索引
CREATE TABLE index2_tbl_3 (id INT,info VARCHAR(20) ) ENGINE=MyISAM;
ALTER TABLE index2_tbl_3 ADD FULLTEXT INDEX f_idx(info(10));
4. 创建单列索引
CREATE TABLE index2_tbl_4 (id INT,subject VARCHAR(30));
ALTER TABLE index2_tbl_4 ADD INDEX idx_sbj(subject(10));
5. 创建多列索引
CREATE TABLE index2_tbl_5 (id INT,name VARCHAR(20),subject VARCHAR(30));
ALTER TABLE index2_tbl_5 ADD INDEX idx_m (name(5), subject(10));
删除索引是指将表中已经存在的索引删除掉。一些不再使用的索引会降低表的更新速度,影响数据库的性能。对于这样的索引,应该将其删除。DROP INDEX 索引名 ON 表名;
使用索引的优点
1.可以通过建立唯一索引或者主键索引,保证数据库表中每一行数据的唯一性.
2.建立索引可以大大提高检索的数据,以及减少表的检索行数
3.在表连接的连接条件 可以加速表与表直接的相连
4.在分组和排序字句进行数据检索,可以减少查询时间中 分组 和 排序时所消耗的时间(数据库的记录会重新排序)
5.建立索引,在查询中使用索引 可以提高性能
使用索引的缺点
1.在创建索引和维护索引 会耗费时间,随着数据量的增加而增加
2.索引文件会占用物理空间,除了数据表需要占用物理空间之外,每一个索引还会占用一定的物理空间
3.当对表的数据进行 INSERT,UPDATE,DELETE 的时候,索引也要动态的维护,这样就会降低数据的维护速度,(建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快)。
索引目的
索引的目的在于提高查询效率,可以类比字典,如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从下往下找到y字母,再找到剩下的sql。如果没有索引,那么你可能需要把所有单词看一遍才能找到你想要的,如果我想找到m开头的单词呢?或者ze开头的单词呢?是不是觉得如果没有索引,这个事情根本无法完成?
索引原理
除了词典,生活中随处可见索引的例子,如火车站的车次表、图书的目录等。它们的原理都是一样的,通过不断的缩小想要获得数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是我们总是通过同一种查找方式来锁定数据。
数据库也是一样,但显然要复杂许多,因为不仅面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段,201到300分成第三段……这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。但如果是1千万的记录呢,分成几段比较好?稍有算法基础的同学会想到搜索树,其平均复杂度是lgN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的,数据库实现比较复杂,数据保存在磁盘上,而为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。
磁盘IO与预读
前面提到了访问磁盘,那么这里先简单介绍一下磁盘IO和预读,磁盘读取数据靠的是机械运动,每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分,寻道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下;旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms;传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右,听起来还挺不错的,但要知道一台500 -MIPS的机器每秒可以执行5亿条指令,因为指令依靠的是电的性质,换句话说执行一次IO的时间可以执行40万条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难。
考虑到磁盘IO是非常高昂的操作,计算机操作系统做了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k,也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。
索引的数据结构
前面讲了生活中索引的例子,索引的基本原理,数据库的复杂性,又讲了操作系统的相关知识,目的就是让大家了解,任何一种数据结构都不是凭空产生的,一定会有它的背景和使用场景,我们现在总结一下,我们需要这种数据结构能够做些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么我们就想到如果一个高度可控的多路搜索树是否能满足需求呢?就这样,b+树应运而生。
如上图,是一颗b+树,关于b+树的定义可以参见B+树,这里只说一些重点,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
b+树的查找过程
如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
b+树性质
1.通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。
2.当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
【性能调优】
select top(10) s.* from Student s where s.id not in (select top 10*(5-1) s1.id from Student s1) order by s.id;
select s.* from (select s1.*,rownum rn from student s1 where rownum<=10) s where s.rn>=10*(5-1);
select s.* from student s where s.id>=(select s1.id from student s1 order by s1.id limit 10*(5-1),1) limit 10;
随机抽取5条数据:select * from tablename order by rand() limit 5;
mysql中insert语句可以一次性插入多条记录,一个values,每条数据用逗号隔开。
insert into student values(‘张三‘,‘25‘),(‘李四‘,‘28‘); 这样可以一次插入大量记录,减少操作数据库的次数,提高性能。having和where的区别在于having是用在最后,对聚合的结果进行过滤,而where是聚合前就对结果集进行过滤。如果逻辑允许我们尽可能用where先过滤,这样因为结果集减小,将对聚合函数效率大大提高,最后再根据逻辑看看是否用having进行过滤。
保存较大文本通常选择Text或者Blob,两者的区别在于blob可以保存二进制数据,比如照片,文件。而text只能保存字符数据。blob和text会引起性能问题,特别是执行了大量的删除操作时,会残留很多的碎片。可以考虑使用optimize table tablename 语句进行表碎片整理,节省空间。大文本数据检索比较慢,mysql提供了前缀索引。create table t (id varchar(100),context blob,hash_value varchar(40)); create index idx_blob on t(context(100)); 使用前缀索引查询效率能快一些。
数据库结构是否合理,需要考虑是否存在冗余、对表的查询和更新的速度、表中字段的数据类型否合理等多方面的内容。
有些表在设置时设置了很多的字段。这个表中有些字段的使用频率很低。当这个表的数据量很大时,查询数据的速度就会很慢。
对于这种字段特别多且有些字段的使用频率很低的表,可以将其分解成多个表。
有时候需要经常查询某两个表中的几个字段。如果经常进行联表查询,会降低MySQL数据库的查询速度。对于这种情况,可以建立中间表来提高查询速度。
先分析经常需要同时查询哪几个表中的哪些字段。然后将这些字段建立一个中间表,并从原来那几个表将数据插入到中间表中。之后就可以使用中间表来进行查询和统计了。
设计数据库表的时候尽量让表达到三范式。但是,有时候为了提高查询速度,可以有意识的在表中增加冗余字段。
表的规范化程序越高,表与表之间的关系就越多。查询时可能经常需要多个表之间进行连接查询。而进行连接操作会降低查询速度。例如,学生的信息存储在student表中,院系信息存储在department表中,通过student表中的dept_id字段与department表建立关联关系。如果要查询一个学生所在系的名称,必须从student表中查找学生所在院系的编号(dept_id),然后根据这个编号去department查找系的名称。如果经常需要进行这个操作时,连接查询会浪费很多的时候。因此可以在student表中增加一个冗余字段dept_name,该字段用来存储学生所在院系的名称。这样就不用每次都进行连接操作了。
优化MySQL服务器可以从两个方面来理解。一个是从硬件方面来进行优化。另一个是从MySQL服务的参数进行优化。通过这些优化方式,可以提供MySQL的运行速度。但是这部分的内容很难理解,一般只有专业的数据库管理员才能进行这一类的优化。
优化MySQL服务器可以从两个方面来理解。一个是从硬件方面来进行优化。另一个是从MySQL服务的参数进行优化。通过这些优化方式,可以提供MySQL的运行速度。但是这部分的内容很难理解,一般只有专业的数据库管理员才能进行这一类的优化。
服务器的硬件性能直接决定着MySQL数据库的性能。例如,增加内存和提高硬盘的读写速度,这能够提高MySQL数据库的查询、更新的速度。
随着硬件技术的成熟、硬件的价格也随之降低。现在普通的个人电话都已配置了2G内存,甚至一些个人电脑配置4G内存。因为内存的读写速度比硬盘的读写速度快。可以在内存中为MySQL设置更多的缓冲区,这样可以提高MySQL访问的速度。如果将查询频率很高的记录存储在内存中,那查询速度就会很快。
内存中会为MySQL保留部分的缓存区。这些缓存区可以提高MySQL数据库的处理速度。缓存区的大小都是在MySQL的配置文件中进行设置的。
MySQL中比较重要的配置参数都在my.cnf或者my.ini文件[mysqld]组中。
innodb_buffer_pool_size=36M
为什么查询语句中的索引没有发挥作用?
在很多情况下,虽然查询语句中使用了索引,但是索引并没有发挥作用。例如,在WHERE条件的LIKE关键字匹配的字符串以"%"开头,这种情况下索引不会起作用。WHERE条件中使用OR关键字来连接多个查询条件,如果有一个条件没有使用索引,那么其它的索引也不会起作用。如果使用多列索引时,多列索引第一个字段没有使用,那么这个多列索引也不起作用。根据这些情况,必须对这些语句进行相应的优化。
不按索引最左列开始查询(多列索引) 例如index(‘c1’, ‘c2’, ‘c3’) where ‘c2’ = ‘aaa’ 不使用索引,where `c2` = `aaa` and `c3`=`sss` 不能使用索引
Where c1= ‘xxx’ and c2 like = ‘aa%’ and c3=’sss’ 改查询只会使用索引中的前两列,因为like是范围查询.
explain select * from `award` where nickname > ‘rSUQFzpkDz3R‘ and account = ‘DYxJoqZq2rd7‘ and created_time = 1449567822; 那么这时候他使用不到其组合索引.
因为我的索引是 (nickname, account, created_time),如果第一个字段出现 范围符号的查找,那么将不会用到索引,如果我是第二个或者第三个字段使用范围符号的查找,那么他会利用索引,利用的索引是(nickname),
select * from users where YEAR(adddate)<2007,将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成:select * from users where adddate<’2007-01-01′。关于这一点可以围观:一个单引号引发的MYSQL性能损失。
最后总结一下,MySQL只对一下操作符才使用索引:<,<=,=,>,>=,between,in,以及某些时候的like(不以通配符%或_开头的情形)。而理论上每张表里面最多可创建16个索引,不过除非是数据量真的很多,否则过多的使用索引也不是那么好玩的,比如我刚才针对text类型的字段创建索引的时候,系统差点就卡死了。
(五)使用索引需要注意的地方
在建立索引的时候应该考虑索引应该建立在数据库表中的某些列上面 哪一些索引需要建立,哪一些所以是多余的.
一般来说,
1.在经常需要搜索的列上,可以加快索引的速度
2.主键列上可以确保列的唯一性
3.在表与表的而连接条件上加上索引,可以加快连接查询的速度
4.在经常需要排序(order by),分组(group by)和的distinct 列上加索引 可以加快排序查询的时间, (单独order by 用不了索引,索引考虑加where 或加limit)
5.在一些where 之后的 < <= > >= BETWEEN IN 以及某个情况下的like 建立字段的索引(B-TREE)
mysql默认对group by a,b,c 后的字段进行排序,会耗费性能,可以使用 order by null 禁止排序。
6.like语句的 如果你对nickname字段建立了一个索引.当查询的时候的语句是 nickname lick ‘%ABC%‘ 那么这个索引讲不会起到作用.而nickname lick ‘ABC%‘ 那么将可以用到索引
7.索引不会包含NULL列,如果列中包含NULL值都将不会被包含在索引中,复合索引中如果有一列含有NULL值那么这个组合索引都将失效,一般需要给默认值0或者 ‘ ‘字符串
8.使用短索引,如果你的一个字段是Char(32)或者int(32),在创建索引的时候指定前缀长度 比如前10个字符 (前提是多数值是唯一的..)那么短索引可以提高查询速度,并且可以减少磁盘的空间,也可以减少I/0操作.
9.不要在列上进行运算,这样会使得mysql索引失效,也会进行全表扫描
10.选择越小的数据类型越好,因为通常越小的数据类型通常在磁盘,内存,cpu,缓存中 占用的空间很少,处理起来更快
(六)什么情况下不创建索引
1.查询中很少使用到的列 不应该创建索引,如果建立了索引然而还会降低mysql的性能和增大了空间需求.
2.很少数据的列也不应该建立索引,比如 一个性别字段 0或者1,在查询中,结果集的数据占了表中数据行的比例比较大,mysql需要扫描的行数很多,增加索引,并不能提高效率
3.定义为text和image和bit数据类型的列不应该增加索引,
4.当表的修改(UPDATE,INSERT,DELETE)操作远远大于检索(SELECT)操作时不应该创建索引,这两个操作是互斥的关系
在某些情况中,MySQL 可以使用一个索引来满足ORDER BY 子句,而不需要额外的排序。WHERE 条件和ORDER BY 使用相同的索引,并且ORDER BY 的顺序和索引顺序相同,并且ORDER BY 的字段都是升序或者都是降序
下列SQL 可以使用索引
SELECT * FROM t1 ORDER BY key_part1,key_part2,... ;
SELECT * FROM t1 WHERE key_part1=1 ORDER BY key_part1 DESC, key_part2 DESC;
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC;
但是在以下几种情况下则不使用索引
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;
--order by 的字段混合ASC 和DESC
SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
--用于查询行的关键字与ORDER BY 中所使用的不相同
SELECT * FROM t1 ORDER BY key1, key2;
--对不同的关键字使用ORDER BY:
持SQL 的子查询。这个技术可以使用SELECT 语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中。使用子查询可以一次性地完成很多逻辑上需要多个步骤才能完成的SQL 操作,同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,子查询可以被更有效率的连接(JOIN)替代。
对于含有OR 的查询子句,如果要利用索引,则OR 之间的每个条件列都必须用到索引;如果没有索引,则应该考虑增加独立索引。因为OR两个复合索引字段,索引是不能被使用的。
id,year是两个独立的索引,有效
explain select * from sales2 where id = 2 or year = 1998
company_id,moneys是复合索引,失效
explain select * from sales2 where company_id = 3 or moneys = 100
【mysql百万级别数据优化】
一般刚开始学SQL的时候,会这样写 SELECT * FROM table ORDER BY id LIMIT 1000, 10; 但在数据达到百万级的时候,这样写会慢死 SELECT * FROM table ORDER BY id LIMIT 1000000, 10;
网上很多优化的方法是这样的
SELECT * FROM table WHERE id >= (SELECT id FROM table LIMIT 1000000, 1) LIMIT 10;
是的,速度提升到0.x秒了,看样子还行了 可是,还不是完美的!
以下这句才是完美的!
SELECT * FROM table WHERE id BETWEEN 1000000 AND 1000010;
比上面那句,还要再快5至10倍
另外,如果需要查询 id 不是连续的一段,最佳的方法就是先找出 id ,然后用 in 查询
SELECT * FROM table WHERE id IN(10000, 100000, 1000000...);
再分享一点
查询字段一较长字符串的时候,表设计时要为该字段多加一个字段,如,存储网址的字段
查询的时候,不要直接查询字符串,效率低下,应该查诡该字串的crc32或md5
如何优化Mysql千万级快速分页
数据表 collect ( id, title ,info ,vtype) 就这4个字段,其中 title 用定长,info 用text, id 是主键,vtype是tinyint,vtype是索引。
这是一个基本的新闻系统的简单模型。现在往里面填充数据,填充10万篇新闻。
最后collect 为 10万条记录,数据库表占用硬盘1.6G。OK ,看下面这条sql语句:
select id,title from collect limit 1000,10; 很快;基本上0.01秒就OK,再看下面的
select id,title from collect limit 90000,10; 从9万条开始分页,结果?
8-9秒完成,my god 哪出问题了????其实要优化这条数据,网上找得到答案。看下面一条语句:
select id from collect order by id limit 90000,10; 很快,0.04秒就OK。 为什么?因为用了id主键做索引当然快。网上的改法是:
select id,title from collect where id>=(select id from collect order by id limit 90000,1) limit 10;
这就是用了id做索引的结果。可是问题复杂那么一点点,就完了。
看下面的语句
select id from collect where vtype=1 order by id limit 90000,10; 很慢,用了8-9秒!
到 了这里我相信很多人会和我一样,有崩溃感觉!vtype 做了索引了啊?怎么会慢呢?vtype做了索引是不错,
你直接 select id from collect where vtype=1 limit 1000,10; 是很快的,基本上0.05秒,可是提高90倍,从9万开始,那就是0.05*90=4.5秒的速度了。
和测试结果8-9秒到了一个数量级。从这里开始有人 提出了分表的思路,这个和dis #cuz 论坛是一样的思路。思路如下:
建一个索引表: t (id,title,vtype) 并设置成定长,然后做分页,分页出结果再到 collect 里面去找info 。 是否可行呢?实验下就知道了。
10万条记录到 t(id,title,vtype) 里,数据表大小20M左右。用
select id from t where vtype=1 order by id limit 90000,10; 很快了。基本上0.1-0.2秒可以跑完。为什么会这样呢?我猜想是因为collect 数据太多,
所以分页要跑很长的路。limit 完全和数据表的大小有关的。其实这样做还是全表扫描,只是因为数据量小,只有10万才快。OK,
来个疯狂的实验,加到100万条,测试性能。 加了10倍的数据,马上t表就到了200多M,而且是定长。还是刚才的查询语句,时间是0.1-0.2秒完成!分表性能没问题?错!因为我们的limit还是9万,所以快。给个大的,90万开始
select id from t where vtype=1 order by id limit 900000,10; 看看结果,时间是1-2秒!
why ?? 分表了时间还是这么长,非常之郁闷!有人说定长会提高limit的性能,开始我也以为,因为一条记录的长度是固定的,mysql 应该可以算出90万的位置才对啊? 可是我们高估了mysql 的智能,他不是商务数据库,事实证明定长和非定长对limit影响不大? 怪不得有人说 discuz到了100万条记录就会很慢,我相信这是真的,这个和数据库设计有关!
难道MySQL 无法突破100万的限制吗???到了100万的分页就真的到了极限???
答案是: NO !!!! 为什么突破不了100万是因为不会设计mysql造成的。下面介绍非分表法,来个疯狂的测试!一张表搞定100万记录,并且10G 数据库,如何快速分页!
好了,我们的测试又回到 collect表,开始测试结论是: 30万数据,用分表法可行,超过30万他的速度会慢道你无法忍受!当然如果用分表+我这种方法,那是绝对完美的。但是用了我这种方法后,不用分表也可以完美解决!
答 案就是:复合索引! 有一次设计mysql索引的时候,无意中发现索引名字可以任取,可以选择几个字段进来,这有什么用呢?开始的select id from collect order by id limit 90000,10; 这么快就是因为走了索引,可是如果加了where 就不走索引了。抱着试试看的想法加了 search(vtype,id) 这样的索引。然后测试
select id from collect where vtype=1 limit 90000,10; 非常快!0.04秒完成!
再测试: select id ,title from collect where vtype=1 limit 90000,10; 非常遗憾,8-9秒,没走search索引!
再测试:search(id,vtype),还是select id 这个语句,也非常遗憾,0.5秒。
综上:如果对于有where 条件,又想走索引用limit的,必须设计一个索引,将where 放第一位,limit用到的主键放第2位,而且只能select 主键!
完美解决了分页问题了。可以快速返回id就有希望优化limit , 按这样的逻辑,百万级的limit 应该在0.0x秒就可以分完。看来mysql 语句的优化和索引时非常重要的!
好了,回到原题,如何将上面的研究成功快速应用于开发呢?如果用复合查询,我的轻量级框架就没的用了。分页字符串还得自己写,那多麻烦?这里再看一个例子,思路就出来了:
select * from collect where id in (9000,12,50,7000); 竟然 0秒就可以查完!
mygod ,mysql 的索引竟然对于in语句同样有效!看来网上说in无法用索引是错误的!
慢查询优化
关于MySQL索引原理是比较枯燥的东西,大家只需要有一个感性的认识,并不需要理解得非常透彻和深入。我们回头来看看一开始我们说的慢查询,了解完索引原理之后,大家是不是有什么想法呢?先总结一下索引的几大基本原则
建索引的几大原则
1.最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
2.=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
3.尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录
4.索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’);
5.尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
回到开始的慢查询
根据最左匹配原则,最开始的sql语句的索引应该是status、operator_id、type、operate_time的联合索引;其中status、operator_id、type的顺序可以颠倒,所以我才会说,把这个表的所有相关查询都找到,会综合分析;
比如还有如下查询:
select * from task where status = 0 and type = 12 limit 10;
select count(*) from task where status = 0 ;
那么索引建立成(status,type,operator_id,operate_time)就是非常正确的,因为可以覆盖到所有情况。这个就是利用了索引的最左匹配的原则
慢查询优化基本步骤
0.先运行看看是否真的很慢,注意设置SQL_NO_CACHE
1.where条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高
2.explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询)
3.order by limit 形式的sql语句让排序的表优先查
4.了解业务方使用场景
5.加索引时参照建索引的几大原则
6.观察结果,不符合预期继续从0分析
很多情况下,我们写SQL只是为了实现功能,这只是第一步,不同的语句书写方式对于效率往往有本质的差别,这要求我们对mysql的执行计划和索引原则有非常清楚的认识,请看下面的语句
select distinct cert.emp_id from cm_log cl inner join
( select emp.id as emp_id,emp_cert.id as cert_id fromemployee emp
left join emp_certificate emp_cert on emp.id = emp_cert.emp_id
where emp.is_deleted=0) cert
on (
cl.ref_table=‘Employee‘
and cl.ref_oid= cert.emp_id
)
or (
cl.ref_table=‘EmpCertificate‘
and cl.ref_oid= cert.cert_id
)
where
cl.last_upd_date >=‘2013-11-07 15:03:00‘
and cl.last_upd_date<=‘2013-11-08 16:00:00‘;
先运行一下,53条记录 1.87秒,又没有用聚合语句,比较慢 看下 explain
简述一下执行计划,首先mysql根据idx_last_upd_date索引扫描cm_log表获得379条记录;然后查表扫描了63727条记录,分为两部分,derived表示构造表,也就是不存在的表,可以简单理解成是一个语句形成的结果集,后面的数字表示语句的ID。derived2表示的是ID = 2的查询构造了虚拟表,并且返回了63727条记录。我们再来看看ID = 2的语句究竟做了写什么返回了这么大量的数据,首先全表扫描employee表13317条记录,然后根据索引emp_certificate_empid关联emp_certificate表,rows = 1表示,每个关联都只锁定了一条记录,效率比较高。获得后,再和cm_log的379条记录根据规则关联。从执行过程上可以看出返回了太多的数据,返回的数据绝大部分cm_log都用不到,因为cm_log只锁定了379条记录。
如何优化呢?可以看到我们在运行完后还是要和cm_log做join,那么我们能不能之前和cm_log做join呢?仔细分析语句不难发现,其基本思想是如果cm_log的ref_table是EmpCertificate就关联emp_certificate表,如果ref_table是Employee就关联employee表,我们完全可以拆成两部分,并用union连接起来,注意这里用union,而不用union all是因为原语句有“distinct”来得到唯一的记录,而union恰好具备了这种功能。如果原语句中没有distinct不需要去重,我们就可以直接使用union all了,因为使用union需要去重的动作,会影响SQL性能。
优化过的语句如下
select emp.id from cm_log cl inner join employee emp on cl.ref_table = ‘Employee‘ and cl.ref_oid = emp.id where cl.last_upd_date >=‘2013-11-07 15:03:00‘ and cl.last_upd_date<=‘2013-11-08 16:00:00‘ and emp.is_deleted = 0
union
select emp.id from cm_log cl inner join emp_certificate ec on cl.ref_table = ‘EmpCertificate‘
and cl.ref_oid = ec.id inner join employee emp on emp.id = ec.emp_id
where cl.last_upd_date >=‘2013-11-07 15:03:00‘ and cl.last_upd_date<=‘2013-11-08 16:00:00‘
and emp.is_deleted = 0
举这个例子的目的在于颠覆我们对列的区分度的认知,一般上我们认为区分度越高的列,越容易锁定更少的记录,但在一些特殊的情况下,这种理论是有局限性的
select * from stage_poi sp where sp.accurate_result=1
and (
sp.sync_status=0 or sp.sync_status=2 or sp.sync_status=4
);
0.先看看运行多长时间,951条数据6.22秒,真的很慢
1.先explain,rows达到了361万,type = ALL表明是全表扫描
无法优化的语句
select c.id, c.name, c.position, c.sex, c.phone, c.office_phone, c.feature_info,
c.birthday, c.creator_id, c.is_keyperson, c.giveup_reason, c.status, c.data_source,
from_unixtime(c.created_time) as created_time,
from_unixtime(c.last_modified) as last_modified,
c.last_modified_user_id
from contact c inner join contact_branch cb on c.id = cb.contact_id
inner join
branch_user bu on cb.branch_id = bu.branch_id and bu.status in ( 1,2)
inner join
org_emp_info oei
on oei.data_id = bu.user_id and oei.node_left >= 2875
and oei.node_right <= 10802 and oei.org_category = - 1
order by c.created_time desc limit 0 ,10;
0.先看语句运行多长时间,10条记录用了13秒,已经不可忍受
从执行计划上看,mysql先查org_emp_info表扫描8849记录,再用索引idx_userid_status关联branch_user表,再用索引idx_branch_id关联contact_branch表,最后主键关联contact表。
rows返回的都非常少,看不到有什么异常情况。我们在看一下语句,发现后面有order by + limit组合,会不会是排序量太大搞的?于是我们简化SQL,去掉后面的order by 和 limit,看看到底用了多少记录来排序
select count(*) from contact c inner join contact_branch cb
on c.id = cb.contact_id inner join
branch_user bu on cb.branch_id = bu.branch_id and bu.status in (1,2)
inner join org_emp_info oei on oei.data_id = bu.user_id
and oei.node_left >= 2875 and oei.node_right <= 10802 and oei.org_category = - 1
发现排序之前居然锁定了778878条记录,如果针对70万的结果集排序,将是灾难性的,怪不得这么慢,那我们能不能换个思路,先根据contact的created_time排序,再来join会不会比较快呢?
selectc.id,c.name,c.position,c.sex,c.phone,c.office_phone,c.feature_info,c.birthday,
c.creator_id,c.is_keyperson,c.giveup_reason,c.status,c.data_source,
from_unixtime(c.created_time) as created_time,
from_unixtime(c.last_modified) as last_modified,
c.last_modified_user_id from
contact c where exists (
select 1 from contact_branch cbinner joinbranch_user bu on cb.branch_id = bu.branch_id
and bu.status in (1,2)inner join org_emp_info oei
on oei.data_id = bu.user_id and oei.node_left >= 2875
and oei.node_right <= 10802 and oei.org_category = - 1
where c.id = cb.contact_id)
order by c.created_time desc limit 0 ,10;
验证一下效果 预计在1ms内,提升了13000多倍!
写在后面的话
本文以一个慢查询案例引入了MySQL索引原理、优化慢查询的一些方法论;并针对遇到的典型案例做了详细的分析。其实做了这么长时间的语句优化后才发现,任何数据库层面的优化都抵不上应用系统的优化,同样是MySQL,可以用来支撑Google/FaceBook/Taobao应用,但可能连你的个人网站都撑不住。套用最近比较流行的话:“查询容易,优化不易,且写且珍惜!”
通过对查询语句的分析,可以了解查询语句的执行情况。MySQL中,可以使用explain语句和describe语句来分析查询语句。
索引可以快速的定位表中的某条记录。使用索引可以提高数据库查询的速度,从而提高数据库的性能。如果查询时不使用索引,查询语句将查询表中的所有字段。这样查询的速度会很慢。如果使用索引进行查询,查询语句只查询索引字段。这样可以减少查询的记录数,达到提高查询速度的目的。
索引可以提高查询的速度。但是有些时间即使查询时使用的是索引,但索引并没有起作用。
show status like ‘Handler_read%‘; 查看索引使用情况。
1. 查询语句中使用LIKE关键字
explain select * from student where name like ‘%四‘ \\G
explain select * from student where name like ‘李%‘ \\G
使用like关键字的时候索引字段的搜索必须开头必须有值开始
2. 查询语句中使用多列索引
使用第一个索引字段的时候才会起作用
create index idx_birth_depart on student(birth,department);
explain select * from student where birth > 1990;
explain select * from student where department like ‘中文%‘;
3. 查询语句中使用OR关键字
两个字段都有索引的时候索引查询,其中一个字段没有索引时不会使用索引查询。
explain select * from student where name="张三" or gender="女" \\G
explain select * from student where name="张三" or id=905 \\G
很多查询中需要使用子查询。子查询可以使查询语句很灵活,但子查询的执行效率不高。子查询时,MySQL需要为内层查询语句的查询结果建立一个临时表。然后外层查询语句再临时表中查询记录。查询完毕后,MySQL需要撤销这些临时表。因此,子查询的速度会受到一定的影响。如果查询的数据量比较大,这种影响就会随之增大。在MySQL中可以使用连接查询来替代子查询。连接查询不需要建立临时表,其速度比子查询要快。
以上是关于mysql总结的主要内容,如果未能解决你的问题,请参考以下文章