MySql


MySql

数据库基础

MySql与MariaDb

MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可MariaDB的目的是完全兼容MySQL,包括API和命令行,使之能轻松成为MySQL的代替品。在存储引擎方面,使用XtraDB(英语:XtraDB)来代替MySQL的InnoDB。 MariaDB由MySQL的创始人Michael Widenius(英语:Michael Widenius)主导开发,他早前曾以10亿美元的价格,将自己创建的公司MySQL AB卖给了SUN,此后,随着SUN被甲骨文收购,MySQL的所有权也落入Oracle的手中。MariaDB名称来自Michael Widenius的女儿Maria的名字。MariaDB基于事务的Maria存储引擎,替换了MySQL的MyISAM存储引擎,它使用了Percona的XtraDB,InnoDB的变体,分支的开发者希望提供访问即将到来的MySQL 5.4 InnoDB性能。这个版本还包括了PrimeBase XT (PBXT) 和 FederatedX存储引擎。

数据库数据类型

数据库类型的使用

分类 数据类型 说明
整数类型 tinyint 极小的整数
整数类型 smallint 小的整数
整数类型 mediumint 中等大小的整数
整数类型 int 普通大小的整数
整数类型 bigint 极大的整数
小数类型 float 单精度浮点数
小数类型 double 双精度浮点数
小数类型 decimal(m,d) 压缩严格的定点数,一般用于记录货币
日期类型 year YYYY,范围1901~2155
日期类型 time HH:MM:SS,范围-838:59:59~838:59:59
日期类型 date YYYY-MM-DD,范围1000-01-01~9999-12-3
日期类型 datetime YYYY-MM-DD HH:MM:SS,范围1000-01-01 00:00:00~ 9999-12-31 23:59:59
日期类型 timestamp YYYY-MM-DD HH:MM:SS,范围19700101 00:00:01 UTC~2038-01-19 03:14:07UTC,以时间戳格式存储,一般使用timestamp储存时间,空间效率高于datetime。
文本、二进制类型 CHAR(M) M为0~255之间的整数
文本、二进制类型 VARCHAR(M) M为0~65535之间的整数
文本、二进制类型 TINYBLOB 允许长度0~255字节
文本、二进制类型 BLOB 允许长度0~65535字节,可用于保存图片base64格式
文本、二进制类型 MEDIUMBLOB 允许长度0~167772150字节
文本、二进制类型 LONGBLOB 允许长度0~4294967295字节
文本、二进制类型 TINYTEXT 允许长度0~255字节
文本、二进制类型 TEXT 允许长度0~65535字节
文本、二进制类型 MEDIUMTEXT 允许长度0~167772150字节
文本、二进制类型 LONGTEXT 允许长度0~4294967295字节
文本、二进制类型 VARBINARY(M) 允许长度0~M个字节的变长字节字符串
文本、二进制类型 BINARY(M) 允许长度0~M个字节的定长字节字符串
文本、二进制类型 ENUM 把不重复的数据存储为一个预定义的集合,ENUM在内部存储时,其实存的是整数,尽量避免使用数字作为ENUM枚举的常量,因为容易混乱。排序是按照内部存储的整数,即('男','女','未知')会对应0,1,2。

数据库三大范式

第一范式:关系模式的每一个分量是不可再分的数据项,数据库表的字段无法再细分

第二范式:消除非主属性对码的部分函数依赖,主键是该条数据唯一代表

第三范式:消除非主属性对码的传递函数依赖,该表依赖的外键应该是外键表数据的唯一代表,如外键的主键

数据库储存引擎

数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建、查询、更新和删除数据。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以获得特定的功能。现在许多不同的数据库管理系统都支持多种不同的数据引擎。存储引擎主要有:MyIsam、InnoDB、Memory、Archive、Federated等。

名称 说明
ISAM ISAM是一个定义明确且历经时间考验的数据表格管理方法,它在设计之时就考虑到数据库被查询的次数要远大于更新的次数。因此,ISAM执行读取操作的速度很快,而且不占用大量的内存和存储资源。ISAM的两个主要不足之处在于,它不支持事务处理,也不能够容错:如果你的硬盘崩溃了,那么数据文件就无法恢复了。如果你正在把ISAM用在关键任务应用程序里,那就必须经常备份你所有的实时数据,通过其复制特性,MYSQL能够支持这样的备份应用程序。
MyIsam MyISAM是MySQL的ISAM扩展格式和缺省的数据库引擎。除了提供ISAM里所没有的索引和字段管理的大量功能,MyISAM还使用一种表格锁定的机制,来优化多个并发的读写操作,其代价是你需要经常运行OPTIMIZE TABLE命令,来恢复被更新机制所浪费的空间。MyISAM还有一些有用的扩展,例如用来修复数据库文件的MyISAMCHK工具和用来恢复浪费空间的MyISAMPACK工具。MYISAM强调了快速读取操作,这可能就是为什么MySQL受到了WEB开发如此青睐的主要原因:在WEB开发中你所进行的大量数据操作都是读取操作。所以,大多数虚拟主机提供商和INTERNET平台提供商只允许使用MYISAM格式。MyISAM格式的一个重要缺陷就是不能在表损坏后恢复数据。
InnoDB InnoDB数据库引擎都是造就MySQL灵活性的技术的直接产品,这项技术就是MYSQL+API。在使用MYSQL的时候,你所面对的每一个挑战几乎都源于ISAM和MyISAM数据库引擎不支持事务处理(transaction process)也不支持外来键。尽管要比ISAM和MyISAM引擎慢很多,但是InnoDB包括了对事务处理和外来键的支持,这两点都是前两个引擎所没有的。
Memory(也叫HEAP) HEAP允许只驻留在内存里的临时表格。驻留在内存里让HEAP要比ISAM和MYISAM都快,但是它所管理的数据是不稳定的,而且如果在关机之前没有进行保存,那么所有的数据都会丢失。在数据行被删除的时候,HEAP也不会浪费大量的空间。HEAP表格在你需要使用SELECT表达式来选择和操控数据的时候非常有用。要记住,在用完表格之后就删除表格。
Archive
Federated

InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型,这两个表类型各有优劣,视具体应用而定。基本的差别为:MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持。MyISAM类型的表强调的是性能,其执行速度比InnoDB类型更快,但是不提供事务支持,而InnoDB提供事务支持以及外部键等高级数据库功能。一般来说,MyISAM适合:做很多count的计算;插入不频繁,查询非常频繁;没有事务。InnoDB适合:可靠性要求比较高,或者要求事务;表更新和查询都相当的频繁,并且表锁定的机会比较大的情况。

DDL、DML、DQL、TCL

术语 包含
数据定义语言(DDL) 1、CREATE: 在数据库中创建新的数据对象
2、ALTER: 修改数据库中对象的数据结构
3、DROP: 删除数据库中的对象
4、DISABLE/ENABLE TRIGGER: 修改触发器的状态
5、UPDATE STATISTIC : 更新表/视图统计信息
6、TRUNCATE TABLE: 清空表中数据
7、COMMENT : 给数据对象添加注释
8、RENAME : 更改数据对象名称
数据操作语言(DML) 1、INSERT:将数据插入到表或视图
2、DELETE:从表或视图删除数据
3、SELECT:从表或视图中获取数据
4、UPDATE:更新表或视图中的数据
5、MERGE : 对数据进行合并操作(插入/更新/删除)
数据控制语言(DCL) 1、GRANT: 赋予用户某种控制权限
2、REVOKE:取消用户某种控制权限
事务控制语言(TCL) 1、COMMIT: 保存已完成事务动作结果
2、SAVEPOINT : 保存事务相关数据和状态用以可能的回滚操作
3、ROLLBACK: 恢复事务相关数据至上一次COMMIT操作之后
4、SET TRANSACTION: 设置事务选项

存储过程、触发器、视图

存储过程:是一个预编译的SQL语句,优点是允许模块化的设计,就是说只需要创建一次,以后在该程序中就可以调用多次。如果某次操作需要执行多次SQL,使用存储过程比单纯SQL语句执行要快。

-- 创建一个储存过程,根据指定ID查询所有子节点数据,monitor_menu:树结构表
DROP FUNCTION IF EXISTS `getChildrenList`;
CREATE FUNCTION `getChildrenList` (rootId VARCHAR ( 1000 )) 
RETURNS VARCHAR ( 1000 ) 
BEGIN
    DECLARE
        sTemp VARCHAR ( 1000 );
    DECLARE
        sTempChd VARCHAR ( 1000 );
    
    SET sTemp = '$';
    SET sTempChd = cast( rootId AS CHAR );
    
    WHILE sTempChd IS NOT NULL DO
        SET sTemp = concat( sTemp, ',', sTempChd );
        SELECT
            group_concat( id ) INTO sTempChd 
        FROM
            monitor_menu 
        WHERE
            FIND_IN_SET( parent_id, sTempChd )> 0;
    END WHILE;
    RETURN sTemp;
END

-- 使用存储过程,1001:指定查询ID
select * from monitor_menu where FIND_IN_SET(id,getChildrenList('1001'))

触发器:是用户定义在关系表上的一类由事件驱动的特殊的存储过程。触发器是指一段代码,当触发某个事件时,自动执行这些代码。在MySQL数据库中有如下六种触发器:Before Insert、After Insert、Before Update、After Update、Before Delete、After Delete。

视图:视图只能进行数据查询,增删改数据需要去操作它相关的基本表。为了提高复杂SQL语句的复用性和表操作的安全性,MySQL数据库管理系统提供了视图特性。所谓视图,本质上是一种虚拟表,在物理上是不存在的,其内容与真实的表相似,包含一系列带有名称的列和行数据。但是,视图并不在数据库中以储存的数据值形式存在。行和列数据来自定义视图的查询所引用基本表,并且在具体引用视图时动态生成。

IN和EXISTS

-- 如果查询的两个表大小相当,那么用in和exists差别不大
-- 如果两个表中一个较小,一个是大表,则子查询表(即B表)大的用exists,子查询表小的用in
-- 如果查询语句使用了not in,那么内外表都进行全表扫描,没有用到索引;而not extsts的子查询依然能用到表上的索引。所以无论那个表大,用not exists都比not in要快
select * from A where id in(select id from B);
select a.* from A a where exists(select 1 from B b where a.id=b.id)

VARCHAR与CHAR

数据类型 区别
VARCHAR 表示可变长字符串,长度是可变的,插入的数据是多长,就按照多长来存储,它存取慢,因为长度不固定,但正因如此,不占据多余的空间,是时间换空间的做法,对于VARCHAR来说,最多能存放的字符个数为65532。
CHAR 表示定长字符串,长度是固定的,如果插入数据的长度小于char的固定长度时,则用空格填充,因为长度固定,所以存取速度要比varchar快很多,甚至能快50%,但正因为其长度固定,所以会占据多余的空间,是空间换时间的做法,对于char来说,最多能存放的字符个数为255,和编码无关。

VARCHAR(50)和INT(20)

varchar(50):50是指最多存放50个字符,varchar(50)和(200)存储hello所占空间一样,但后者在排序时会消耗更多内存,因为order by col采用fixed_length计算col长度(memory引擎也一样)。在早期MySQL版本中,50代表字节数,现在代表字符数。

INT(20):20是指显示字符的长度。20表示最大显示宽度为20,但仍占4字节存储,存储范围不变;不影响内部存储,只是影响带zerofill定义的int时,前面补多少个0,易于报表展示。这里显示的宽度和数据类型的取值范围是没有任何关系的,显示宽度只是指明Mysql最大可能显示的数字个数,数值的位数小于指定的宽度时会由空格填充;如果插入了大于显示宽度的值,只要该值不超过该类型的取值范围,数值依然可以插入,而且能够显示出来。对大多数应用没有意义,只是规定一些工具用来显示字符的个数;int(1)和int(20)存储和计算均一样。

数据库操作语法

查看数据库版本

1)没有连接到MySQL服务器,就想查看MySQL的版本。打开cmd,切换至mysql的bin目录,运行下面的命令即可:mysql -V 或 mysqladmin --version 或 mysql --help|find "Distrib"
2)如果已经连接到了MySQL服务器,则运行下面的命令:select version() 或 status 或 \s
3)在命令行连接上MySQL服务器时,其实就已经显示了MySQL的版本,如:mysql -uroot -padmins

查看数据库信息

1)使用 show databases 展示所有数据库;
2)使用 use+数据库名称 进入或改变当前使用的数据库;
3)使用 show+数据库名称 展示该数据库下的所有表;
4)查看表结构的方法:
登录mysql,执行:
desc+表名 或 describe+表名 或 show columns from 表名 或 explain+表名;
使用mysql的工具mysqlshow.exe:
mysql+数据库名称+表名

查看数据库储存引擎

1)查看数据库现在已提供什么存储引擎:show engines;
2)查看数据库当前默认的存储引擎:show variables like '%storage_engine%';
3)查看某个表用了什么引擎(在显示结果里参数engine后面的就表示该表当前用的存储引擎): show create table 表名;

修改数据库密码

1)mysql初始密码为空,默认端口3306,默认最大连接数为100,修改密码方式:在DOS下进入目录mysql\bin,然后键入以下命令:mysqladmin -u用户名 -p旧密码 password 新密码,如: mysqladmin -u root -p ab12 password djg345
2)命令行修改root密码:
mysql> UPDATE mysql.user SET password=PASSWORD(’新密码’) WHERE User=’root’;
mysql> FLUSH PRIVILEGES;
显示当前的user:
mysql> SELECT USER();

导入导出与备份

导出单表内容:mysqldump -uroot -padmins 数据库名 表名 > database_dump.sql
导出数据库:mysqldump -uroot -padmins 数据库名 > database.sql
导入脚本:mysql -uroot -padmins 数据库名 < database_dump.sql
另外可以使用图形化界面进行导出导入

数据库增删改

创建数据库:CREATE DATABASE item_mysql;
删除数据库:DROP DATABASE item_mysql;
修改数据库名称:创建新的数据库,导入之前数据库数据结构和数据

数据库表操作语法

数据库表创建语句

-- 创建数据库表:学生表
-- 如果存在则删除表
DROP TABLE IF EXISTS stu_info;
-- 创建表结构
CREATE TABLE stu_info (
    -- 主键自增
    -- NOT NULL: 非空约束,用于控制字段的内容一定不能为空(NULL)。
    sNo int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    sName varchar(20) NOT NULL,
    sAge int(11) NOT NULL,
    -- mysql不支持检查约束,但是写上检查约束不会报错
	-- CHECK: 检查约束,用于控制字段的值范围。
    check(sAge between 15 and 20),
    -- 18位数字,小数位数为0(身份证),默认为空
    sId decimal(18,0) DEFAULT NULL,
    -- 设置当前日期为该字段默认值
    sDate timestamp NULL DEFAULT CURRENT_TIMESTAMP,
    sAddress text,
    -- 设置主键
    -- PRIMARY KEY: 主键约束,也是用于控件字段内容不能重复,但它在一个表只允许出现一个。
    PRIMARY KEY (sNo),
    -- UNIQUE: 唯一约束,控件字段内容不能重复,一个表允许有多个Unique约束。
    UNIQUE KEY sId (sId)
)

-- 创建数据库表:老师表
DROP TABLE IF EXISTS tch_info;
CREATE TABLE tch_info (
    tNo int(11) NOT NULL AUTO_INCREMENT,
    tName varchar(20) NOT NULL,
    tStuNo int(11) DEFAULT NULL,
    PRIMARY KEY (tNo),
    -- 设置外建
    -- FOREIGN KEY: 外键约束,用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。
    FOREIGN KEY (tStuNo) REFERENCES stu_info (sNo)
)

-- 创建临时表
CREATE TEMPORARY TABLE stu_info_temp SELECT * FROM stu_info;
-- 设置数量
CREATE TEMPORARY TABLE stu_info_temp AS(SELECT *  FROM stu_info LIMIT 0,10000);

-- 备份表
create table copytable select * from stu_info;

数据库表新增语句

-- 新增一列
alter table stu_info add column stucard varchar(20) not null after sAddress;
alter table stu_info add column sexenum enum('男','女','未知') after stucard;

-- 添加数据,如果主键自增可以不写
insert into stu_info(sName,sAge,sID,sAddress) values('juluy',18,'360724155815457542','江西');
insert into stu_info(sName,sAge,sID,sAddress) values('judy',20,'360724155815457523','珠海');
insert into stu_info(sName,sAge,sID,sAddress) values('sssk',20,'360724155815457524','珠海');

insert into tch_info(tName,tStuNo) values('sada',1);
insert into tch_info(tName,tStuNo) values('jsdg',2);

数据库表删除语句

-- 删除表,删除时要先将有外建的表删除
drop table tch_info;
drop table stu_info;

-- 删除数据
delete from tch_info where tName='xdzy'

-- 清空表(自增ID从头开始)
truncate table tch_info

-- 如果存在外键约束是无法清空的,可以先禁用外键约束,再清空
-- 查看外键约束状态,0:禁用;1:使用
SELECT @@FOREIGN_KEY_CHECKS;
SET FOREIGN_KEY_CHECKS=0;
SET FOREIGN_KEY_CHECKS=1;

数据库表修改语句

-- 修改数据
update tch_info set tName='xdzy' where tNo=1

数据库表查询语法

基本查询语句

-- 查询部分数据
select sName,sID from stu_info where sNo=1

-- 查询时更改列标题(as,可省略)
select sName 姓名,sID 身份证 from stu_info

函数查询语句

-- 查询时删除重复行(可用all或distinct)distinct:只保留一行输出
select distinct(tName) from tch_info

-- count(*/column):返回行数
-- sum(column):返回指定列中唯一值的和
-- max(column):返回指定列或表达式中的数值最大值
-- min(column):返回指定列或表达式中的数值最小值
-- avg(column):返回指定列或表达式中的数值平均值
-- date(Expression): 返回指定表达式代表的日期值

条件查询语句

-- where后面可以有:比较(>,<,=,<>.!>,!<)
-- between、and(可以返回包括开头与结尾的所有数据)
-- 判断是否为列表指定项(in,not in)
-- 空值:null,not null
-- 逻辑:not,and,or
select * from stu_info where sName in('juluy','judy')

-- group by:按字段分组
-- 使用顺序:SELECT、FROM、JOIN、ON、WHERE、GROUP BY、HAVING、UNION、ORDER BY、LIMIT
-- where不能与聚合函数一起使用;having则可以
select sName from stu_info group by sName

-- 模式匹配符:like,not like(可用于char,varchar,text,ntext,datetime,smalldatetime)
-- %可用于缺少的值
select * from stu_info where sName like 'j%'
-- 下划线可代表一个未知的值
select * from stu_info where sName like '_u%'

-- desc:根据字段反排序;asc:根据字段正排序(优先选择前面排序)
-- 但是如果有二个相同字段的数据,则会根据前面先排序,再二者根据后面排序
select sName,sAge,sNo from stu_info order by sAge desc,sNo asc

-- 分页查询
-- 做分页的话,MySql使用Limit,Sql Service使用top,Oracle使用rownum
select * from stu_info limit 5,10; #返回第6-15行数据 
select * from stu_info limit 5; #返回前5行 
select * from stu_info limit 0,5; #返回前5行

-- 查询时限制返回的行数
select top 2 * from stu_info
-- 20 percent*:20%
select top 20 percent * from stu_info
-- 返回tName的前2行
select top 2 tName from stu_info

-- 查询前10条记录
select * from stu_info WHERE ROWNUM<10;

合并查询语句

-- from后面可以指定256个表或视图(可以为表取个别名)
-- 将2表的有关联的数据合并之后返回
select * from stu_info as a,tch_info as b where a.sNo=b.tStuNo;
select * from stu_info where sNo in(select tStuNo from tch_info where tNo=3);

-- inner join:内连接,没有匹配不返回
select a.sName,b.tName from stu_info a inner join tch_info b on stu_info.sNo=tch_info.tStuNo

-- left join:左连接,返回左边所有数据,没有匹配则对应为null
-- right join:右连接,full join:全连接
select a.sName,b.tName from stu_info a left join tch_info b on stu_info.sNo=tch_info.tStuNo

-- union:具有相似数据类型的2张表合并;union all:即使数据重复也会列出
-- 效率UNION高于UNION ALL
select tName from tch_info union select tName from tch_info

复杂查询语句

1、班级表、学生表、专业表、成绩表多表关联查询

-- 班级信息表
create table class_info(
	c_id int primary key auto_increment not null,
	c_name varchar(20) not null
);
insert into class_info(c_name) values('ST01'),('ST02'),('ST03');
select * from class_info;

-- 学生信息表
create table stu_info(
	s_id int primary key auto_increment,
	s_name varchar(50) not null,
	s_age int not null,
	s_sex int not null,
	s_origin varchar(50),
	s_tel varchar(100),
	c_id int,
	foreign key(c_id) references class_info(c_id)
);
insert into stu_info(s_name,s_age,s_sex,s_origin,s_tel,c_id) 
values('stu1',19,0,'广东珠海','13543090987',1),
('stu2',20,1,'广东广州','13543090987',1),
('stu3',18,0,'江西赣州','13543093245',2),
('stu4',21,1,'江西赣州','15890456789',2),
('stu5',20,0,'广东深圳','18769446565',3),
('stu6',19,1,'广东阳江','15908675453',3),
('stu7',20,0,'广东茂名','13554679546',null);
select * from stu_info;

-- 专业信息表
create table subject_info(
	sub_id int primary key auto_increment,
	sub_name varchar(20) not null
);
insert into subject_info(sub_name) 
values('JAVA'),('.Net'),('PHP'),('UI');
select * from subject_info;

-- 成绩表
create table score_info(
	sc_id int primary key auto_increment,
	score float not null,
	s_id int,
	sub_id int,
	foreign key (s_id) references stu_info(s_id),
	foreign key (sub_id) REFERENCES subject_info(sub_id)
);
insert into score_info(score,s_id,sub_id) 
values(50,1,1),(70,1,4),(65,2,1),(62,2,2),
(75,3,2),(80,3,3),(72,4,1),(55,4,2),(60,5,2),
(83,5,4),(92,6,2),(65,6,3),(65,7,1),(62,7,2);
select * from score_info;

-- 1)拷贝学生信息表数据到学生信息备份表中(stu_bak_info)
create table stu_bak_info select * from stu_info; 

-- 2)查询所有学生信息包含学生所在的班级
-- 这个不能将空的查询出来
select * from stu_info s,class_info c where s.c_id=c.c_id
-- 这个能将空值查询出来
select s.s_name,s.s_age,s_sex,s.s_origin,s.s_tel,c.c_name 
from stu_info s left join class_info c on s.c_id = c.c_id;

-- 3)查询年龄在19到21之间的学员信息
select s_name,s_age,s_sex,s_origin,s_tel 
from stu_info where s_age between 19 and 21;

-- 4)查询ST01班所有学生各科成绩
select s.s_name,sub.sub_name,sc.score
from stu_info s left join class_info c on s.c_id = c.c_id 
inner join score_info sc on s.s_id = sc.s_id
inner join subject_info sub on sub.sub_id = sc.sub_id 
where c.c_name = 'ST01';

-- 5)统计各个班级男生和女生的总人数
select c.c_name as 班级,s.s_sex as 性别, count(*) as 总人数 
from class_info c inner join stu_info s on c.c_id = s.c_id
group by c.c_name,s.s_sex order by c.c_name;

-- 6)统计各班级各科目的平均分
select c.c_name as 班级, sub.sub_name as 科目, avg(score) as 平均分
from class_info c left join stu_info s on c.c_id = s.c_id
inner join score_info sc on s.s_id = sc.s_id 
inner join subject_info sub on sc.sub_id = sub.sub_id 
group by c.c_name,sub.sub_name;

-- 7)查询所有科目的平均分最高的值
select sub.sub_name, avg(sc.score) as avg_score 
from stu_info s left join score_info sc on s.s_id = sc.s_id 
inner join subject_info sub on sc.sub_id = sub.sub_id 
group by sub.sub_name
having avg_score >=all(select avg(sc.score) as avg_score 
from stu_info s left join score_info sc 
on s.s_id = sc.s_id inner join subject_info sub 
on sc.sub_id = sub.sub_id group by sub_name);

-- 8)分页查询学生信息表
select * from stu_info s limit 0,3;

-- 9)每页显示3条,共7条记录,共多少页?
select 7/3 from dual;

2、课程表、成绩表、学生表、教师表多表关联查询

-- 课程表
DROP TABLE IF EXISTS `course`;
CREATE TABLE `course` (
  `c` int(11) NOT NULL AUTO_INCREMENT,
  `cname` varchar(32) DEFAULT NULL,
  `t` int(11) DEFAULT NULL,
  PRIMARY KEY (`c`),
  KEY `t` (`t`),
  CONSTRAINT `course_ibfk_1` FOREIGN KEY (`t`) REFERENCES `teacher` (`t`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- 新增数据
INSERT INTO `course` VALUES ('1', '语文', '1');
INSERT INTO `course` VALUES ('2', '数学', '2');
INSERT INTO `course` VALUES ('3', '英语', '3');
INSERT INTO `course` VALUES ('4', '物理', '4');

-- 成绩表
DROP TABLE IF EXISTS `sc`;
CREATE TABLE `sc` (
  `s` int(11) DEFAULT NULL,
  `c` int(11) DEFAULT NULL,
  `score` int(11) DEFAULT NULL,
  KEY `s` (`s`),
  KEY `c` (`c`),
  CONSTRAINT `sc_ibfk_1` FOREIGN KEY (`s`) REFERENCES `student` (`s`),
  CONSTRAINT `sc_ibfk_2` FOREIGN KEY (`c`) REFERENCES `course` (`c`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 新增数据
INSERT INTO `sc` VALUES ('1', '1', '56');
INSERT INTO `sc` VALUES ('1', '2', '78');
INSERT INTO `sc` VALUES ('1', '3', '67');
INSERT INTO `sc` VALUES ('1', '4', '58');
INSERT INTO `sc` VALUES ('2', '1', '79');
INSERT INTO `sc` VALUES ('2', '2', '81');
INSERT INTO `sc` VALUES ('2', '3', '92');
INSERT INTO `sc` VALUES ('2', '4', '68');
INSERT INTO `sc` VALUES ('3', '1', '91');
INSERT INTO `sc` VALUES ('3', '2', '47');
INSERT INTO `sc` VALUES ('3', '3', '88');
INSERT INTO `sc` VALUES ('3', '4', '56');
INSERT INTO `sc` VALUES ('4', '2', '88');
INSERT INTO `sc` VALUES ('4', '3', '90');
INSERT INTO `sc` VALUES ('4', '4', '93');
INSERT INTO `sc` VALUES ('5', '1', '46');
INSERT INTO `sc` VALUES ('5', '3', '78');
INSERT INTO `sc` VALUES ('5', '4', '53');
INSERT INTO `sc` VALUES ('6', '1', '35');
INSERT INTO `sc` VALUES ('6', '2', '68');
INSERT INTO `sc` VALUES ('6', '4', '71');

-- 学生表
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `s` int(11) NOT NULL AUTO_INCREMENT,
  `sname` varchar(32) DEFAULT NULL,
  `sage` int(11) DEFAULT NULL,
  `ssex` varchar(8) DEFAULT NULL,
  PRIMARY KEY (`s`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

-- 新增数据
INSERT INTO `student` VALUES ('1', '刘一', '18', '男');
INSERT INTO `student` VALUES ('2', '钱二', '19', '女');
INSERT INTO `student` VALUES ('3', '张三', '17', '男');
INSERT INTO `student` VALUES ('4', '李四', '18', '女');
INSERT INTO `student` VALUES ('5', '王五', '17', '男');
INSERT INTO `student` VALUES ('6', '赵六', '19', '女');

-- 教师表
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
  `t` int(11) NOT NULL AUTO_INCREMENT,
  `tname` varchar(16) DEFAULT NULL,
  PRIMARY KEY (`t`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- 新增数据
INSERT INTO `teacher` VALUES ('1', '叶平');
INSERT INTO `teacher` VALUES ('2', '贺高');
INSERT INTO `teacher` VALUES ('3', '杨艳');
INSERT INTO `teacher` VALUES ('4', '周磊');

-- 1)查询“001”课程比“002”课程成绩高的所有学生的学号
select a.s from (select s,score from sc where c='001') a,
(select s,score from sc where c='002') b 
where a.score>b.score and a.s=b.s

-- 2)查询平均成绩大于60分的同学的学号和平均成绩
select s,avg(score) from sc
group by s having avg(score)>60

-- 3)查询所有同学的学号、姓名、选课数、总成绩
select s.s,s.sname,count(sc.c),sum(score) from student s left join sc on s.s=sc.s
group by s.s,sname

-- 4)查询姓“李”的老师的个数
-- distinct:忽略重复值
select count(distinct(tname)) num from teacher
where tname like '叶%'

-- 5)查询没学过“叶平”老师课的同学的学号、姓名
select student.s,student.sname from student where s not in
(select distinct(sc.s) from sc,course,teacher 
where sc.c=course.c and teacher.t=course.t and teacher.tname='叶平')

-- 6)查询学过“001”并且也学过编号“002”课程的同学的学号、姓名
select student.s,student.sname from student,sc where student.s=sc.s and sc.c='001'
and exists(select * from sc sc2 where sc.s=sc2.s and sc2.c='002')

-- 7)查询学过“叶平”老师所教的所有课的同学的学号、姓名
select s,sname from student
where s in (select s from sc,course,teacher 
where sc.c=course.c and teacher.t=course.t and teacher.tname='叶平'
group by s having count(sc.c)=
(select count(c) from course,teacher where teacher.t=course.t and tname='叶平'))

-- 8)查询课程编号“002”的成绩比课程编号“001”课程低的所有同学的学号、姓名
select s,sname from (select student.s,student.sname,score,
(select score from sc sc2 where sc2.s=student.s and sc2.c='002') score2
from student,sc where student.s=sc.s and c='001') s2 where score2

数据库事务

事务的基本概念

事务是一个操作序列,这些操作要么都做,要么都不做,是数据库环境中不可分割的逻辑工作单位。事务和程序是两个不同的概念,一般一个程序可包含多个事务。在SQL语言中,事务定义的语句有以下三条:

BEGIN TRANSACTION:事务开始。
COMMIT:事务提交。该操作表示事务成功地结束,它将通知事务管理器该事务的所有更新操作现在可以被提交或永久地保留。
ROLLBACK:事务回滚。该操作表示事务非成功地结束,它将通知事务管理器出故障了,数据库可能处于不一致状态,该事务的所有更新操作必须回滚或撤销。

事务的四大特征(ACID):

原子性(Atomicity):事务是原子的,要么都做,要么都不做。
一致性(Consistency):事务执行的结果必须保证数据库从一个一致性状态变到另一个一致性状态。因此,当数据库只包含成功事务提交的结果时,称数据库处于一致性状态。
隔离性(Isolation):事务相互隔离。当多个事务并发执行时,任一事务的更新操作直到其成功提
交的整个过程,对其他事务都是不可见的。
持久性(Durability):一旦事务成功提交,即使数据库崩溃,其对数据库的更新操作也将永久有效。

并发事务影响

脏读(一个事务的数据库操作有部分已经执行,别人这时看见了,后面异常操作进行回滚,看到的就不是准确的):是指在一个事务处理过程里读取了另一个未提交的事务中的数据。当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:用户A向用户B转账100元,当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱其实并没有转。对应SQL命令如下:

-- A通知B,B看到转入100元
update account set money=money+100 where name=’B’;
-- 同一个事务,下面出错时回滚,转账失败,B没有真正转账成功
update account set money=money-100 where name=’A’;

不可重复读(数据库数据修改前后都进行了查询,看到了不一样的结果):是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发生了不可重复读。不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了。

幻读(批量修改了某些数据字段,后面又加了一条,结果看到还有一条没有修改):是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从1修改为2的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为1并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

丢失修改(2个人同时进行相同修改操作,前面的事务会丢失):是指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。

事务的隔离级别

MySQL数据库为我们提供的四种隔离级别,由低到高:

隔离级别 解决问题
READ-UNCOMMITTED(读未提交) 最低级别,任何情况都无法保证。
READ-COMMITTED (读已提交) 可避免脏读的发生。
REPEATABLE-READ(可重复读) 可避免脏读、不可重复读的发生。
SERIALIZABLE (串行化) 可避免脏读、不可重复读、幻读的发生。

以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。而在Oracle数据库中,只支持Serializable(串行化)级别和Read committed(读已提交)这两种级别,其中默认的为Read committed(读已提交)级别。InnoDB 存储引擎在分布式事务的情况下一般会用到Serializable (串行化)隔离级别。

设置隔离级别

-- 查看当前事务的隔离级别
select @@tx_isolation;
-- 设置事务的隔离级别
set tx_isolation='REPEATABLE-READ';

数据库索引

索引的基本概念

索引用于快速找出在某个列中有一特定值的行,不使用索引,MySQL必须从第一条记录开始读完整个表,直到找出相关的行,表越大,查询数据所花费的时间就越多,如果表中查询的列有一个索引,MySQL能够快速到达一个位置去搜索数据文件,而不必查看所有数据,那么将会节省很大一部分时间。索引并非是越多越好,创建索引也需要耗费资源,一是增加了数据库的存储空间,二是在插入和删除时要花费较多的时间维护索引。任何标准表最多可以创建16个索引列 。索引我们分为四类:

索引名称 说明
单列索引 一个索引只包含单个列,但一个表中可以有多个单列索引。单列索引又分为:
普通索引:MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一点。
唯一索引:索引列中的值必须是唯一的,但是允许为空值。
主键索引:是一种特殊的唯一索引,不允许有空值。
组合索引 在表中的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,使用组合索引时遵循最左前缀集合。
全文索引 全文索引,只有在MyIsam引擎上才能使用,只能在CHAR、VARCHAR、TEXT类型字段上使用全文索引,介绍了要求,说说什么是全文索引,就是在一堆文字中,通过其中的某个关键字等,就能找到该字段所属的记录行,比如有"你是个大煞笔,二货 ..." 通过大煞笔,可能就可以找到该条记录。
空间索引 空间索引是对空间数据类型的字段建立的索引,MySQL中的空间数据类型有四种,GEOMETRY、POINT、LINESTRING、POLYGON。在创建空间索引时,使用SPATIAL关键字。要求,引擎为MyIsam,创建空间索引的列,必须将其声明为NOT NULL。

索引操作语句

-- 创建索引,name是索引名称,括号里面是字段名称
-- CREATE INDEX可对表增加普通索引或UNIQUE索引,但是不能创建PRIMARY KEY索引
CREATE INDEX name ON stu_info(sName);
-- 添加索引,age是索引名称,括号里面是字段名称
-- ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY KEY索引。
ALTER TABLE stu_info ADD index age(sAge);

-- 删除索引方法,age是索引名称
ALTER TABLE stu_info DROP INDEX age;
-- 删除索引方法,name是索引名称
DROP INDEX name ON stu_info;

-- 查看索引
SHOW INDEX FROM stu_info;

-- 创建前缀索引
-- 使用字段值的前10个字符建立索引,默认是使用字段的全部内容建立索引
ALTER TABLE stu_info add index name(sName(100))
-- 通过从调整prefixLen的值(即下面的100,从1自增)查看不同前缀长度的一个平均匹配度,接近1时就可以了(表示一个密码的前prefixLen个字符几乎能确定唯一一条记录)
SELECT COUNT(DISTINCT sName)/count(*) AS a,COUNT(DISTINCT LEFT(sName,100)) AS b, COUNT(DISTINCT LEFT(sName,110)) AS c FROM stu_info

使用原则和失效情况

使用原则

1)当数据多且字段值有相同的值得时候用普通索引,当字段多且字段值没有重复的时候用唯一索引。

2)当有多个字段名都经常被查询的话用复合索引,普通索引不支持空值,唯一索引支持空值。

4)若是这张表增删改多而查询较少的话,就不要创建索引了,因为如果你给一列创建了索引,那么对该列进行增删改的时候,都会先访问这一列的索引,若是增,则在这一列的索引内以新填入的这个字段名的值为名创建索引的子集,若是改,则会把原来的删掉,再添入一个以这个字段名的新值为名创建索引的子集,若是删,则会把索引中以这个字段为名的索引的子集删掉。所以,索引会减慢增删改的执行速度,若是这张表增删改多而查询较少的话,就不要创建索引了。

5)更新太频繁地字段不适合创建索引,不会出现在where条件中的字段不该建立索引。

失效情况

1、使用以%开头的LIKE模糊匹配语句,%加在后面是可以的

2、使用OR语句前后搜索条件没有同时加上索引

3、数据类型出现隐式转化,如varchar不加单引号的话可能会自动转换为int型

最左前缀原则和聚簇索引

最左前缀原则:就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边,数据库会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立abcd顺序的索引,d是用不到索引的,如果建立abdc的索引则都可以用到,a,b,d的顺序可以任意调整。=和in可以乱序,比如 a = 1 and b = 2 and c = 3 建立abc索引可以任意顺序,数据库的查询优化器会帮你优化成索引可以识别的形式。

聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据。非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因。B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据,非聚簇索引也不一定要回表查询,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询。

索引实现原理(B+树)

数据库索引底层常用就是用就是B树或者是B+树这种结构,索引的原理很简单,就是把无序的数据变成有序的查询,先把创建了索引的列的内容进行排序,再对排序结果生成倒排表,在倒排表内容上拼上数据地址链,在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据。

树结构 分析
其实树就是从一个根节点出发,其可以有很多子节点,而子节点又可以有很多子节点,这样就像我们现实生活中的树一样,不过我们这颗树是倒立的!因为树的分支太多且没有规律所以很难控制,要想让树发挥他的作用就得在基本的树结构上加上一些特性,让有了特性的树成为帮助我们解决问题的结构,最常用的就是二叉树了,二叉树听名字就知道是一个节点至多只有两个节点,这样对数进行了一定的限制,整棵树看起来就顺眼多了。
二叉搜索树 二叉搜索树的节点满足一个规律,父节点的左孩子的键值小于父节点的键值,而右孩子的键值大于父节点的键值,这样当我们在这颗数中查询某个键值时就可以根据当前节点的键值和要寻找的键值的大小比较,确定该忘哪条路走下去。二叉搜索树还有一个特点就是中序遍历的时候其键值是按大小排序的。
平衡二叉树 由于我们要插入的数据可能是本身就排好序的,所以会导致插入数据时树变成线性的结构,只有一条路。于是我们需要保证二叉树的平衡,当发现这棵树要出现往一边倒的情况时就要想某种方式让其保持平衡(叶子节点的高度差最大为1),这就涉及到一些节点的旋转,变换了。
红黑树 红黑树也是一种平衡二叉树,不过加入了一些新的特性,听名字就知道,在红黑树中节点的颜色要么是红色要么是黑色的,当然还有其他的一些特性,当插入或者删除数据破坏了红黑树的这些特性时,我们需要进行一些操作(一般是颜色改变和树的旋转)红黑树保持其原有的特性。
B树 由于二叉树是二叉的,所以当树的节点不断增加时就会导致树的高度不断的增加,所以查询的效率就很低了,当我们面对海量数据(像数据库中保存的数据)的时候这种结构是不行的,所以我们又衍生出了新的树结构。B数一样拥有自平衡的特性,最大的区别在于B树不是二叉的,而是多叉的,具体有多少个叉要根据树的阶数来判断。
B+树 和B树相比,B+树又增加了一些特性,B+树主要是为了方便查询一个区间的数据集合,因为我们使用B树的时候要想查询某个区间内的数据得使用中序遍历将树中的数据全部遍历一遍,这样的时间复杂度是O(n),效率太低了。而B+树只用叶子节点保存具体值的地址,非叶子节点只保存其子节点的指针,叶子节点之间通过指针链接起来,是有序的,所以在查找一个范围内的数据是很有效的。其时间复杂度为O(logn+M),M为要查找的数据个数。

数据库锁

当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,锁机制就是这样的一个机制。数据库锁分类:

类别 说明
共享锁 又叫做读锁,当用户要进行数据的读取时,对数据加上共享锁。共享锁就是让多个线程同时获取一个锁。
排他锁 又叫做写锁,当用户要进行数据的写入时,对数据加上排他锁。排它锁也称作独占锁,一个锁在某一时刻只能被一个线程占有,其它线程必须等待锁被释放之后才可能获取到锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥。
行级锁 行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁和排他锁。特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
表级锁 表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。InnoDB是基于索引来完成行锁,例: select * from tab_with_index where id = 1 for update,for update 可以根据条件来完成行锁锁定,并且id是有索引键的列,如果id不是索引键那么InnoDB将完成表锁,并发将无从谈起。
页级锁 页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
乐观锁 乐观锁认为一个用户读数据的时候,别人不会去写自己所读的数据;悲观锁就刚好相反,觉得自己读数据库的时候,别人可能刚好在写自己刚读的数据,其实就是持一种比较保守的态度;时间戳就是不加锁,通过时间戳来控制并发出现的问题(每行记录都标记了最后修改和读取它的事务的timestamp。当事务的timestamp小于记录的timestamp时(不能读到”未来的”数据),需要回滚后重新执行)。一般会使用版本号机制或CAS算法实现。乐观锁适用于读多写少,核心SQL:
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
悲观锁 悲观锁就是在读取数据的时候,为了不让别人修改自己读取的数据,就会先对自己读取的数据加锁,只有自己把数据读完了,才允许别人修改那部分数据,或者反过来说,就是自己修改某条数据的时候,不允许别人读取该数据,只有等自己的整个事务提交了,才释放自己加上的锁,才允许其他用户访问那部分数据。悲观锁适用于写多读少,核心SQL:
select status from t_goods where id=1 for update;

数据库优化

数据库语句优化

1)有外键约束的话会影响增删改的性能,如果应用程序可以保证数据库的完整性那就去除外键。

2)Sql语句全部大写,特别是列名大写,因为数据库的机制是这样的,sql语句发送到数据库服务器,数据库首先就会把sql编译成大写在执行,如果一开始就编译成大写就不需要了把sql编译成大写这个步骤了。

3)如果应用程序可以保证数据库的完整性,可以不需要按照三大范式来设计数据库,反范式设计。

4)其实可以不必要创建很多索引,索引可以加快查询速度,但是索引会消耗磁盘空间。

5)如果是jdbc的话,使用PreparedStatement不使用Statement,来创建SQl,PreparedStatement的性能比Statement的速度要快,使用PreparedStatement对象SQL语句会预编译在此对象中,PreparedStatement对象可以多次高效的执行。

6)对查询进行优化,应尽量避免全表扫描,首先应考虑在where及order by涉及的列上建立索引
用索引可以提高查询。

7)SELECT子句中避免使用*号,获取什么字段就写入什么字段,尽量全部大写SQL。

8)应尽量避免在where子句中对字段进行is null值判断,否则将导致引擎放弃使用索引而进行全表扫描,使用IS NOT NULL。where子句中使用or来连接条件,也会导致引擎放弃使用索引而进行全表扫描。

9)in和not in也要慎用,否则会导致全表扫描。

大表查询优化

1)限定数据的范围:务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。

2)读写分离:经典的数据库拆分方案,主库负责写,从库负责读。

3)添加数据缓存机制,使用redis等中间件。

4)垂直拆分数据库表:垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。

5)水平拆分数据库表:水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以水平拆分最好分库 。