使用mysql以及连接数据库

MySQL

Table of Contents

1 安装与配置

MySQL/MariaDB:

比如,MariaDB 的下载页面:

下载安装即可:

  • 注意,要注意勾选“将utf8作为默认字符集”
  • 安装过程中需要提供 root 用户的密码,需要记住
  • 安装完后,需要将 bin 目录添加到环境变量中
  • 可以选择将 mysqld 安装为 windows 服务,如果没有勾选,那么也可以手动创建服务

创建服务的方式:

# 如果要删除相关服务,只需要在“管理员身份”的命令行窗口下面运行:
# sc delete 服务名字

# 将 MySQL 安装为服务的方式: "C:\Program Files\MariaDB 10.3\bin\mysqld.exe" install MyDB --defaults-file="C:\Program Files\MariaDB 10.3\my.ini" 

在连接之前,需要启动服务器守护程序,可以通过下面两种方式之一:

  1. 启动相关 windows 服务,比如 MySQL 服务
  2. 通过命令行窗口,执行 mysqld --defaults-file=my.ini 命令

服务启动后,就可以通过客户端访问了:

mysql -u root -p
mysql -uroot -proot
mysql -h 45.42.32.1 -P 9999 -uroot -p

2 数据库与账户

MySQL 在用户登录后(默认使用 root 用户登录),需要切换到一个 database才能进行表的创建等操作。所有的表和数据都隶属于某个数据库。

刚安装完之后,MySQL 中有若干内置数据库:

clip_2018-09-05_07-24-23.png

想要切换到某个数据库下,使用 use 指令:

use mysql

我们可以为自己的业务创建单独的数据库,比如,为我们的书店项目我们创建一个单独的数据库:

create database bookstore;
create database bookstore character set UTF8; -- 创建时可以指定字符集
use bookstore;  -- 切换到数据库下 

虽然 root 用户强大无比,但是使用 root 用户进行数据库操作不是一个好的行为。所以我们需要为数据库的操作创建相关的用户:

-- 特别需要注意,在 MySQL 中,账号由两部分组成:
-- 1. user
-- 2. host
-- 即使 user 相同,只要 host 不同,也会被认为是不同账号。
-- 这样可以非常方便对来自不同 ip 地址的访问进行精细的权限控制。
-- 默认情况下,创建的用户 host 为 '%',这是一个匹配符,跟模糊查询里的意思一样,表示匹配所有 create user vip identified by 'vippp'; -- 所有连接 create user vip@'127.0.0.1' identified by 'xxx'; -- 本地连接 create user vip@'192.168.%' identified by 'yyy'; -- 192.168 网段的连接 

新创建的用户没有多少权限,登录之后,只能访问操作 test 以及以 test 开头的数据库。如果想让新用户有更多权限,就需要用到授权语句(DCL):

grant all on *.* to vip@'127.0.0.1';        -- 将所有数据库上的所有权利都授予通过本机连接的 vip 用户! grant all privileges on lagou.* to vip@'%'; -- 将数据库 lagou 上的所有权利都授予所有连接的 vip 用户! grant select on lagou.position to vip@'%'; -- 将 lagou 数据库上的 position 表的访问权限开放给所有 vip 用户。 

如果想查看某个用户拥有的权限,只需要:

show grants for vip@'%';

3 用户跟权限

用户跟权限的相关信息,都是保存在下面表中:

  • mysql.user
  • mysql.db/host/table/priv…

mysql.user 是一个非常重要非常特殊的表,它保存了所有的账号信息,以及保存了账号的权限信息。

如果要操作用户和权限,有两种方式:

  1. 使用相关的授权语句
  2. 直接修改 mysql.user 表

第一种方式:

-- 查看用户
select current_user(), user();
select * from mysql.user; -- 创建与授权 create user 'xxx'@'host' identified by '密码'; grant all on ttt.* to 'user'@'host' with grant options; -- 查看授权情况 show grants for 'user'@'host'; -- 用户的管理,删除密码等 set password for 'user'@'host' = password('新密码'); drop user vip; -- 授权的相对完整语法为: grant all/alter/create/drop/select/update/delete on *.* -- db.*/db.table to 'user'@'host' identified by '密码' with max_user_connections 2 max_connections_pser_hour 5; 

第二种方式:

-- 增加用户
insert into mysql.user(host, user, password) values (xx, yy, zz); -- 修改密码 update mysql.user set password=password('新密码') where user='vip' and host='%'; -- 修改权限 update mysql.user set event_priv='Y' where user='vip' and host='%'; -- 注意,使用 sql 语句修改用户跟权限之后,需要手动刷新权限表 flush privileges; 

4 常用命令

基本命令:

-- 查询当前日期
select now(), current_date, current_time;

-- 查询当前用户
select user(), current_user(); -- 数据库 show databases; use test; 

更多命令:

show engines; -- 所有引擎
show plugins; -- 所有插件
show variables; -- 所有系统变量
show variables like '%char%'; -- 可以使用 like 进行过滤, 注意 % _ 的使用 set names utf8; -- 一个快捷方式,用来快速批量设置字符集 show full processlist; kill 109; 

5 表的创建

create table book1 (
   bookid int primary key ); create table book2 ( bookid int, primary key (bookid) ); create table book3 ( bookid int auto_increment primary key ); create table book4 ( bookid int auto_increment primary key, name varchar(30) not null, price float check (price >= 0) ); create table book5 ( bookid int auto_increment primary key, name varchar(30) not null, price float check (price >= 0), press varchar(50), authorid int references author (authorid) -- foreign key(author) references author(authorid), ); create table book6 ( bookid int auto_increment, name varchar(30) not null, price float check (price >= 0), press varchar(50), authorid int, primary key (bookid), foreign key (authorid) references author (authorid) ); create table book7 ( bookid int auto_increment primary key, name varchar(30) not null, price float check (price >= 0), press varchar(50), authorid int ); create table author ( authorid int auto_increment, name varchar(30) not null, tel varchar(20), birth date, primary key (authorid) ); -- 实际的导入情况,首先创建表,先不添加任何外键约束 create table book ( bookid int auto_increment primary key, name varchar(30) not null, price float check (price >= 0), press varchar(50), authorid int, publish_at datetime, created_at timestamp ); create table author ( authorid int auto_increment primary key, name varchar(30) not null, tel varchar(20), birth date ); -- 在这里,执行所有的数据插入操作 -- insert into book ... -- insert into author ... alter table book add constraint fk_authorid foreign key (authorid) references author (authorid); -- 如果需要添加主键的话,语法如下 alter table book add constraint pk_bookid primary key (bookid); -- 如果想删除掉的话 alter table book drop foreign key fk_authorid; 

其他:

create table xxx if not exists (id int); create temporary table yyy (id int); create temporary table if not exists yyy ( id bigint auto_increment, name varchar(20) not null unique, -- 唯一,不重复的 birth datetime, gender enum('f', 'm'), -- Female Male minzu set('汉', '俄罗斯'), created_at timestamp, primary key (id) ); create table aaa (id int auto_increment primary key, name varchar(20)) default charset = gbk engine = InnoDB auto_increment = 7 comment = `这只是一个测试表而已`; create table gbk_test2 (vvv varchar(4)) default charset = gbk; create table gbk_test3 (vvv varchar(4)) default charset = utf8; insert into gbk_test2 values ('中国'); insert into gbk_test3 values ('中国'); select * from gbk_test2; select * from gbk_test3; select length(vvv) from gbk_test2; select length(vvv) from gbk_test3; select char_length(vvv) from gbk_test2; select char_length(vvv) from gbk_test3; 

一致性,完整性

id 名字 xxx
1 Java 2
2 太极剑法 1
3 MySQL指南 22
id 名字 生日
1 张三丰 x
2 y
3 你好 z

6 数据类型

大致分为 6 类:

  1. 整数: bit / bool / tinyint / smallint / mediumint / int / bitint
  2. 浮点数: float / double / decimal
  3. 字符串: char / varchar / tinytext / text / mediumtext / longtext
  4. BLOB(Binary Large Object): tinyblob / blob / mediumblob / longblob
  5. 日期: date(2018-9-6) / time (03:33:49) / datetime(2018-9-6 03:34:08) / timestamp
  6. 其他: binary / varbinary / enum / set / geometry / point / LineString …

问题:

  1. char 跟 varchar 有什么区别?
  2. varchar 最多存储的长度是多少?
  3. varchar 跟 text 类型有什么区别?
  4. set/enum 该怎么去使用?
create table char_demo 
(
  aaa char(40),
  bbb varchar(40),
  ccc text(40)
);

insert into char_demo values ('123', '123', '123'); select length(aaa), length(bbb), length(ccc) from char_demo; SET sql_mode = 'PAD_CHAR_TO_FULL_LENGTH'; select length(aaa), length(bbb), length(ccc) from char_demo; 

总结 Char vs Varchar vs Text:

  1. char(n) 和 varchar(n) 中间的 n 代表字符的个数,不是字节的个数
  2. 如果数据超过 n 的限制,那么数据将会被截断
  3. char 是固定宽度,如果数据长度不满足 n,那么将会在右边用空格补齐;varchar 是变长宽度
  4. varchar 是标准类型;text 不是标准类型
  5. varchar 跟 text 都最大保存 65535 字节长度的数据。但是 text 还有 mediumtext/longtext 它可以支持存储更多的内容
  6. varchar 的存储格式是 stored-inline, text 的存储格式是 off-record。一般情况来说,varchar 的速度要比 text 快

BLOB 类型,可以存储大字段。

ENUM 跟 SET 是两种比较特殊的字符串类型,他们对字段进行了一定约束:

create table person (
       id int auto_increment primary key, name varchar(30) not null, gender char(2) -- 限制,只有 男、女、未知 ); create table person ( id int auto_increment primary key, name varchar(30) not null, gender enum('男', '女', '未知') -- 限制,只有 男、女、未知 ); create table person2 ( id int auto_increment primary key, name varchar(30) not null, gender enum('男', '女', '未知'), -- 限制,只有 男、女、未知 country set('中国', '美国', '南非') ); insert into person3 (name, gender, country) values ('灭绝', '女', '美国,南非'); 

7 主键约束

主键,primary key,从性能和实际出发,应该遵循下面几点:

  1. 尽量短,避免占用太多空间
  2. 唯一,不能跟其他行的数据重复
  3. 不会改变
  4. 代理键

所谓代理键是指跟业务无关的字段:

  • 因为和业务无关,所以可以使用 int 等类型尽量短
  • 因为和业务无关,所以可以避免因为业务变动引起的主键变化
  • 不要相信自己的直觉,因为在直觉上感觉不会变的东西,往往都是经不起实践考验的

8 表的修改

基本语法:

ALTER [ONLINE|OFFLINE] [IGNORE] TABLE tbl_name
    [alter_specification [, alter_specification] ...]
    [partition_options]

alter_specification:
    table_options
  | ADD [COLUMN] col_name column_definition
        [FIRST | AFTER col_name] | ADD [COLUMN] (col_name column_definition,...) | ADD {INDEX|KEY} [index_name] [index_type] (key_part,...) [index_option] ... | ADD [CONSTRAINT [symbol]] PRIMARY KEY [index_type] (key_part,...) [index_option] ... | ADD [CONSTRAINT [symbol]] UNIQUE [INDEX|KEY] [index_name] [index_type] (key_part,...) [index_option] ... | ADD FULLTEXT [INDEX|KEY] [index_name] (key_part,...) [index_option] ... | ADD SPATIAL [INDEX|KEY] [index_name] (key_part,...) [index_option] ... | ADD [CONSTRAINT [symbol]] FOREIGN KEY [index_name] (col_name,...) reference_definition | ALGORITHM [=] {DEFAULT|INPLACE|COPY} | ALTER [COLUMN] col_name {SET DEFAULT literal | DROP DEFAULT} | CHANGE [COLUMN] old_col_name new_col_name column_definition [FIRST|AFTER col_name] | [DEFAULT] CHARACTER SET [=] charset_name [COLLATE [=] collation_name] | CONVERT TO CHARACTER SET charset_name [COLLATE collation_name] | {DISABLE|ENABLE} KEYS | {DISCARD|IMPORT} TABLESPACE | DROP [COLUMN] col_name | DROP {INDEX|KEY} index_name | DROP PRIMARY KEY | DROP FOREIGN KEY fk_symbol | FORCE | LOCK [=] {DEFAULT|NONE|SHARED|EXCLUSIVE} | MODIFY [COLUMN] col_name column_definition [FIRST | AFTER col_name] | ORDER BY col_name [, col_name] ... | RENAME [TO|AS] new_tbl_name | ADD PARTITION (partition_definition) | DROP PARTITION partition_names | TRUNCATE PARTITION {partition_names | ALL} | COALESCE PARTITION number | REORGANIZE PARTITION partition_names INTO (partition_definitions) | EXCHANGE PARTITION partition_name WITH TABLE tbl_name | ANALYZE PARTITION {partition_names | ALL} | CHECK PARTITION {partition_names | ALL} | OPTIMIZE PARTITION {partition_names | ALL} | REBUILD PARTITION {partition_names | ALL} | REPAIR PARTITION {partition_names | ALL} | REMOVE PARTITIONING key_part: col_name [(length)] [ASC | DESC] index_type: USING {BTREE | HASH} index_option: KEY_BLOCK_SIZE [=] value | index_type | WITH PARSER parser_name | COMMENT 'string' table_options: table_option [[,] table_option] ... table_option: AUTO_INCREMENT [=] value | AVG_ROW_LENGTH [=] value | [DEFAULT] CHARACTER SET [=] charset_name | CHECKSUM [=] {0 | 1} | [DEFAULT] COLLATE [=] collation_name | COMMENT [=] 'string' | CONNECTION [=] 'connect_string' | {DATA|INDEX} DIRECTORY [=] 'absolute path to directory' | DELAY_KEY_WRITE [=] {0 | 1} | ENGINE [=] engine_name | INSERT_METHOD [=] { NO | FIRST | LAST } | KEY_BLOCK_SIZE [=] value | MAX_ROWS [=] value | MIN_ROWS [=] value | PACK_KEYS [=] {0 | 1 | DEFAULT} | PASSWORD [=] 'string' | ROW_FORMAT [=] {DEFAULT|DYNAMIC|FIXED|COMPRESSED|REDUNDANT|COMPACT} | STATS_AUTO_RECALC [=] {DEFAULT|0|1} | STATS_PERSISTENT [=] {DEFAULT|0|1} | STATS_SAMPLE_PAGES [=] value | TABLESPACE tablespace_name [STORAGE {DISK|MEMORY|DEFAULT}] | UNION [=] (tbl_name[,tbl_name]...) 

基本示例:

-- 添加列
alter table person add column qq varchar(20); alter table person add qq varchar(20); -- 可以去掉 column 关键词 alter table person add tel varchar(20) after gender; -- 指定位置 alter table person add (xxx int, yyy int); -- 修改列 ALTER TABLE t2 MODIFY a TINYINT NOT NULL, CHANGE b c CHAR(20); -- 删除主键 alter table person change id id int; -- 需要将 auto_increment 去掉 alter table person drop primary key; -- 增加主键 alter table person add primary key (id); -- 增加/删除外键 alter table book add constraint fk_authorid foreign key (authorid) references author (authorid); -- 增加/删除索引 ALTER TABLE t2 ADD INDEX (d), ADD UNIQUE (a); -- 重命名 alter table rename to people; 

9 引擎(Engine)

  • MySQL 为了满足各种需求,提供了不同的引擎实现,比如 MyISAM/InnoDB 等。

    show engines
    
  • 可以为数据库指定默认引擎,只需要在 my.ini 配置文件中增加 default-storage-engine=InnoDB 就可以了
  • 也可以在创建表的时候,手动指定使用的引擎:

    create table xxx (id int) engine=MyISAM;
    
  • 在早期(<5.0)MySQL 的默认引擎是 MyISAM,它的特点是快,它的缺点是功能简单,不支持事务包括外键等功能
  • 目前(>5)默认引擎是 InnoDB,它为数据库提供了事务支持、外键支持、锁的支持等特性

事务相关的语法有:

start transaction; -- begin;
savepoint xxx;     -- 存档
rollback; -- 回滚 rollback to xxx; -- 回档到某个保存点 commit; -- 提交事务 -- 创表 create table xxx_inno (id int) engine=InnoDB; create table xxx_isam (id int) engine=MyISAM; -- 查看 show create table xxx_inno; show create table xxx_isam; -- 查看 xxx_inno 的事务支持情况 -- 首先,开启事务 start transaction; -- 然后,插入数据 insert into xxx_inno values (111); insert into xxx_inno values (222); -- 现在,在其他的连接中,是查询不到这数据的 select * from xxx_inno; -- 建立保存点(存档) savepoint s1; -- 再插入更多数据 insert into xxx_inno values (333); -- 现在,在当前事务中,可以查到3条数据 select * from xxx_inno; -- 使用 rollback 回档,将回到插入 333 前的状态 rollback to s1; -- 提交事务 commit; -- 现在,在其他的连接中,就能查询到这些数据了 select * from xxx_inno; -- 查看 xxx_isam 的事务支持情况 start transaction; insert into xxx_isam values (666); -- 在没有 commit 的时候,在其他连接中竟然能查询这数据 -- 说明 MyISAM 是不支持事务的 select * from xxx_isam; 

10 存储过程

[WARNNING] 存储过程不建议使用:

  1. 难以调试,难以维护
  2. 业务逻辑跟数据库耦合度过高,不便于迁移
  3. 对数据库的压力过大,需要分拆
  4. MySQL 对存储过程的支持有些弱鸡

10.1 基本语句

-- 存储过程的定义
delimiter //                   -- 使用 delimiter 指令,将默认的分隔符(;)换为我们自定义的分隔符
create procedure ppx1 (p int) -- 创建存储过程,名字 ppx1, 一个入参 int 类型的 p begin -- 使用 begin 跟 end 将所有语句包括起来,相当于 {} set @x = 0; -- set 是用来定义或者设置变量的值的,用户级别的变量,需要使用 @ 作为前缀 repeat -- repeat .. until .. end repeat 这是使用循环的其中一种方式 set @x = @x + 1; -- 在循环体中,我们就可以进行我们不断重复的业务、事情了 until @x > p end repeat; -- 使用 until 表示循环结束的条件。类似于 while 循环中的 break end delimiter ; -- 过程定义完成后,需要将分隔符还原为默认(;) -- 调用存储过程 call ppx1(100); -- 查看用户定义变量的值 select @x; 

求和 1-n:

create procedure sum2 (y int)
begin
  declare x int; -- 声明局部变量 set x = 0; -- 为局部变量赋值 set @sum = 0; repeat set x = x + 1; set @sum = @sum + x; until x >= y end repeat; end 

10.2 变量定义

变量分类:

  1. 局部变量,使用在存储过程或函数中,这样的变量使用 declare 进行声明,使用 set 进行赋值。作用域是过程中。
  2. 用户变量,定义 set @xxx = 333; select @xxx := 333; 需要用 @ 符号进行修饰
  3. 系统变量,系统自带的,内建的变量
    • 可以通过命令行的方式进行设置 mysqld –default_storage_engine=MyISAM
    • 可以在 my.ini 配置文件中进行配置 default_storage_engine=MyISAM
    • 可以通过 set 的方式进行动态设置
    • 系统变量,分为两类:
      1. 全局的(global)

        show global variables;
        set global default_storage_engine=MyISAM;
        set @@global.default_storage_engine=MyISAM;
        
      2. 会话的(session)

        show session variables;
        show local variables;
        show variables;
        set session default_storage_engine=MyISAM;
        set local default_storage_engine=MyISAM; set @@session.default_storage_engine=MyISAM; set @@local.default_storage_engine=MyISAM; set default_storage_engine=MyISAM; set @@default_storage_engine=MyISAM; 

10.3 参数与游标

如果让存储过程跟外边进行数据交互,需要用到参数

第一种,在过程内部使用用户变量 @xxx

delimiter //
create procedure pp1 ()
  begin
    declare v1 int default 100; set @aaa = v1*101; end // delimiter ; -- 调用 call pp1(); select @aaa; 

另外,可以将表中查询到的数据赋予参数:

delimiter //
drop procedure if exists pp2;
create procedure pp2 () begin select count(*) into @bbb from t_person; select weixin into @ccc from t_person limit 1; end // delimiter ; -- 调用 call pp2(); select @bbb, @ccc; 

使用过程内部定义用户变量的方式,容易在调用的时候产生冲突、混淆,所以最好使用外部定义,结合 out/inout 类型参数:

delimiter //
drop procedure if exists pp3;
create procedure pp3 (inout bbb varchar(30), inout ccc varchar(20)) -- in out in out begin insert into t_person (name, weixin) value (bbb, ccc); select count(*) into bbb from t_person; select weixin into ccc from t_person limit 1; end // delimiter ; -- 调用 set @ddd = '3333'; set @eee = 'sdfsfsd'; call pp3(@ddd, @eee); select @ddd, @eee; 

如果需要处理表中的多行数据,就需要用到游标(Cursor):

delimiter //
drop procedure if exists pp4;
create procedure pp4 (out sum bigint) begin declare iii int; declare done int default 0; declare c_person cursor for select weixin from t_person; -- 1. 游标的定义 declare continue handler for not found set done = 1; -- 捕获系统抛出的 not found 错误,如果捕获到,将 done 设置为 1 set sum = 0; open c_person; -- 2. 打开游标 xxx:loop fetch c_person into iii; -- 3. 使用游标 if done = 1 then -- 4. 设定退出条件 leave xxx; end if; set sum = sum + iii; end loop; close c_person; -- 5. 关闭游标 end // delimiter ; -- 调用 set @sum = 0; call pp4(@sum); select @sum; 

10.4 JDBC 操作存储过程示例

存储过程示例:

delimiter //
drop procedure if exists pp6;
create procedure pp6 (inout bbb varchar(30), inout ccc varchar(20)) -- in out in out begin insert into t_person (name, weixin) value (bbb, ccc); select count(*) into bbb from t_person; select weixin into ccc from t_person limit 1; select * from lagou_city where province='广东省'; select * from lagou_pos order by salary_min desc limit 10; end // delimiter ; 

JDBC代码:

 // Statement PrepareStatement CallableStatement

 public static void main(String[] args) { Connection connection = null; CallableStatement ps = null; ResultSet rs = null; try { connection = DBUtil.getMySQLConnection(); ps = connection.prepareCall("call pp6(?, ?)"); ps.setString(1, "dfasd"); ps.setString(2, "16463"); ps.registerOutParameter(1, Types.VARCHAR); ps.registerOutParameter(2, Types.VARCHAR); boolean hasMore = ps.execute(); while (hasMore) { rs = ps.getResultSet(); while (rs.next()) { System.out.println(rs.getObject(1) + "/" + rs.getObject(2)); } rs.close(); hasMore = ps.getMoreResults(); } System.out.println(ps.getObject(1)); System.out.println(ps.getObject(2)); } catch (Exception e) { e.printStackTrace(); } finally { DBUtil.tryRelease(connection, ps, rs); } } 

11 优化(Optimization)

 

11.1 解释计划(Explain)

可以查看数据内部是怎么分析执行相关语句的,根据这些可以对语句进行优化

explain select c.city as city2, p.* from lagou_pos p
        join lagou_city c on p.city = c.cid where c.province = '广东省' order by salary_min desc limit 10; 

11.2 索引

索引是跟引擎相关的,索引的主要分类有:

  • B-Tree 索引,这是默认的索引形式,是最普遍的方式
  • R-Tree 索引,主要用于空间数据库字段上
  • Hash 索引,只用在 Memory 引擎上,使用场景比较简单

MySQL 不支持函数索引,但是可以支持前缀索引:

-- 创建

create index idx_pos_city_01 on lagou_pos(city);  -- 普通索引
create unique index skdfjks on lagou_pos(pid); -- 唯一索引 create index idex_city_p01 on lagou_city (province(2)); -- 前缀索引 create index idex_city_p01 on lagou_city (province, city); -- 组合索引 -- 索引的创建原则: -- 1. 频繁读取的字段,适合创建 -- 2. 更新频繁的字段,不适合 -- 3. 数据差异性大的字段才适合,比如性别就不适合 -- 4. 索引要适当,因为索引会占用更多空间,并让查询的过程变得复杂。如果不恰当使用,非但不能增加效率,反而会拖累效率 -- 删除 drop index idx_pos_city_01 on lagou_pos; alter table lagou_pos drop index idx_pos_city_01; -- 查看表上的所有索引 show index from lagou_pos; 

12 UseCases

 

12.1 使用存储过程批量插入数据

 

创建表,使用存储过程插入 10w 条数据

首先,创建表,比如:

create table t_person (
  pid int auto_increment primary key, name varchar(20) not null, weixin varchar(20) not null ); 

其次,编写函数,求取随机字符串。将求取随机字符串的方法提取出来的目的是为了复用,这也是函数的目的:

drop function if exists rand_str;
delimiter $$
create function rand_str(size int, type int) returns varchar(255) begin declare seed varchar(255); declare seed_no varchar(255) default '1234567890'; declare seed_en varchar(255) default 'qwertryuiopasdfghjklzxcvbnm'; declare ret varchar(255) default ''; declare i int default 0; set seed = case type -- if .. else / case when when 1 then seed_en when 2 then seed_no else concat(seed_en, seed_no) end; while i < size do set ret = concat(ret, substring(seed, floor(length(seed) * rand() + 1), 1)); set i = i + 1; end while; return ret; end$$ delimiter ; -- 调用 select rand_str(5, 1); -- 根据上述函数,求取 5 到 12 位长度的一个随机字符串 -- 思路一 -- 第一步,求取 0-9 之间的一个随机数字 select rand_str(1, 2); -- 第二步,将这个随机数字加上 5 ,得到的结果就是 5 到 14 之间的一个随机数字 select rand_str(1, 2) + 5; -- 第三步,求取 5 到 14 位长度的一个随机数字 select rand_str(rand_str(1, 2) + 5, 1); -- 第四步,将上述字符串,超过长度的部分,截取掉,让其最长的长度是 12。任务完成 select left(rand_str(rand_str(1, 2) + 5, 1), 12); -- 思路二 -- 第一步,得到一个随机字符串,长度为 5 select rand_str(5, 1); -- 第二步,得到一个随机字符串,长度为 0 到 9 之间 select rand_str(rand_str(1, 2), 1); -- 第三步,将两个字符串拼接,那么长度是 5 到 14 之间 select concat(rand_str(5, 1), rand_str(rand_str(1, 2), 1)); -- 最后一步,截断 select left(concat(rand_str(5, 1), rand_str(rand_str(1, 2), 1)), 12); -- 思路三 select concat(rand_str(5, 1), left(rand_str(rand_str(1, 2), 1), 7)); -- TODO 题目 -- 封装一个函数 rand_str2(m, n) -- 传入两个数字,返回一个 m~n 长度的随机字符串 -- 在实现中,尽量使用刚才封装好的 rand_str 函数 

然后,编写存储过程,实现数据的插入:

delimiter //
drop procedure if exists proc_person_data;
create procedure proc_person_data (num int) begin declare ii int default 0; -- while .. end while -- repeat .. until .. end repeat -- loop_label:loop ... leave loop_label .. end loop; set autocommit = 0; while ii < num do insert into t_person (name, weixin) values (concat('user_', rand_str(6, 1)), rand_str(8, 2)); set ii = ii + 1; if ii % 100000 = 1 then commit ; end if; end while; commit ; set autocommit = 1; end; // 

12.2 行列转换

表中的数据是这样的:

INSERT INTO `t_score` VALUES
    ('王海', '语文', '86'), ('王海', '数学', '83'), ('王海', '英语', '93'), ('陶俊', '语文', '88'), ('陶俊', '数学', '84'), ('陶俊', '英语', '94'), ('刘可', '语文', '80'), ('刘可', '数学', '86'), ('刘可', '英语', '88'), ('李春', '语文', '89'), ('李春', '数学', '80'), ('李春', '英语', '87'); 

要求查询的结果是这样:

Name Chinese Math English score
刘可 80 86 88 254
李春 89 80 87 256
王海 86 83 93 262
陶俊 88 84 94 266
TOTAL 343 333 362 1038

方法不拘,但是要思考,要实现。

实现方法:

-- 1
select name,
       max(case subject when '数学' then score else 0 end) Math, max(case subject when '语文' then score else 0 end) Chinese, max(case subject when '英语' then score else 0 end) English, sum(score) score from t_score group by name union all select 'TOTAL', sum(case subject when '数学' then score else 0 end), sum(case subject when '语文' then score else 0 end), sum(case subject when '英语' then score else 0 end), sum(score) from t_score; -- 2 select IFNULL(NAME,'TOTAL') name, -- name or 'TOTAL' SUM(case subject when '数学' then score else 0 end) Math, SUM(case subject when '语文' then score else 0 end) Chinese, SUM(case subject when '英语' then score else 0 end) English, sum(score) score from t_score group by name WITH ROLLUP; -- 3 select IFNULL(NAME,'TOTAL') name, -- name or 'TOTAL' SUM(if (subject = '数学',score, 0) ) Math, SUM(if (subject = '语文',score, 0) ) Chinese, SUM(if (subject = '英语',score, 0) ) English, sum(score) score from t_score group by name WITH ROLLUP; 

13 备份与恢复

各种灾难防不胜防:

  • 操作系统崩溃
  • 突然的断电
  • 文件系统损坏
  • 硬盘故障

所以需要多种不同的数据备份策略:

  • 热备份、冷备份
  • 全量备份、增量备份
  • 多机备份,灾后重建

不仅为了灾难,数据的迁移,也需要掌握相关的备份、恢复技巧。

在 MySQL 中:

  • 不同引擎的备份机制是不一样的
  • 最常用的备份方式是 mysqldump 命令

基本的导入、导出:

# 数据库备份
mysqldump -uroot -proot > ~/xxx.sql  # > 和 < 是管道语句
mysqldump --all-databases --master-data --single-transaction > ~/xxx.sql
mysqldump -uroot -proot --databases lagou test > ~/lagou.sql

# 数据库恢复 mysql -uroot -proot < ~/xxx.sql mysql> source ~/xxx.sql 

在 InnoDB 引擎下,为了提高 source 导入效率,需要:

set names utf8;       -- 需要正确的客户端编码

SET autocommit=0;     -- 将自动提交关闭,避免每一条语句都会出发日志写入操作
SET unique_checks=0; -- 将唯一约束检查关闭 SET foreign_key_checks=0; -- 将外键约束检查关闭 ... SQL import statements ... INSERT INTO yourtable VALUES (1,2), (5,5), ...; -- 尽量使用这种方式的导入语句 COMMIT; -- 提交事务 -- 恢复设置 set autocommit=1; SET unique_checks=1; SET foreign_key_checks=1; 

https://dev.mysql.com/doc/refman/5.6/en/optimizing-innodb-bulk-data-loading.html

14 数据迁移示例

将 sqlite3 格式的 lagou.db 中的数据迁移到 MySQL 中

14.1 命令行的方式

首先,从 sqlite 中导出:

sqlite3 lagou.db

> .output d:/data/lagou.sql
> .dump

然后,修改 lagou.sql 文件,让其兼容 MySQL 数据库。

之后就可以连接 MySQL 进行导入了:

drop table if exists lagou_position;

set names utf8; set autocommit=0; source d:/data/lagou.sql commit; select count(*) from lagou_position; 

14.2 JDBC 的方式

需要注意的问题:

  • 如何使用 jdbc 同时连接操作两个数据库
  • 如何在 jdbc 中合理释放资源
  • 异常的捕获与抛出,如何正确处理异常逻辑
  • jdbc 中批量导入数据的方式
  • MySQL 驱动中的 rewriteBatchedStatements 参数
  • 代码的格式与规范问题

代码示例:

import java.sql.*;

public class DataMigrateFromSQLiteToMySQL { // create table lagou_p1 as select * from lagou_position limit 0; public static void main(String[] args) { DataMigrateFromSQLiteToMySQL migrate = new DataMigrateFromSQLiteToMySQL(); migrate.doMigrate(); } public void doMigrate() { // 数据从哪里来? Connection liteConn = null; Statement liteSt = null; ResultSet liteRs = null; // 数据到哪里去? Connection mysqlConn = null; PreparedStatement mysqlPs = null; try { // 数据从这里来 liteConn = this.getSQLiteConnection(); liteSt = liteConn.createStatement(); liteRs = liteSt.executeQuery("select * from lagou_position"); // 数据到这里去 mysqlConn = this.getMySQLConnection(); mysqlPs = mysqlConn.prepareStatement("insert into lagou_p1 values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); // 关闭自动提交,也就是开启事务 mysqlConn.setAutoCommit(false); int i = 0; long startTime = System.currentTimeMillis(); // 计时开始 // 来吧,迁移吧! while(liteRs.next()) { for (int j = 1; j < 20; j++) { mysqlPs.setObject(j, liteRs.getObject(j)); } mysqlPs.addBatch(); // 每 10000 条,向数据库发送一次执行请求 if (i++ % 10000 == 0) { mysqlPs.executeBatch(); } } mysqlPs.executeBatch(); mysqlConn.commit(); // 提交事务 long stopTime = System.currentTimeMillis(); // 计时结束 // 输出结果 System.out.println("总共多少数据:" + i); System.out.println("一共花费 " + (stopTime - startTime) / 1000.0 + " 秒"); } catch (Exception e) { DBUtil.tryRollback(mysqlConn); } finally { DBUtil.tryRelease(liteConn, liteSt, liteRs); DBUtil.tryRelease(mysqlConn, mysqlPs, null); } } private Connection getSQLiteConnection() throws Exception { Class.forName("org.sqlite.JDBC"); return DriverManager.getConnection("jdbc:sqlite:d:/data/lagou.db"); } private Connection getMySQLConnection() throws Exception { Class.forName("org.mariadb.jdbc.Driver"); // 注意 rewriteBatchedStatements 参数! return DriverManager.getConnection("jdbc:mariadb://127.0.0.1:3306/lagou?rewriteBatchedStatements=true", "root", "xxx"); } } class DBUtil { // 释放资源 // 注意关闭顺序 // 注意异常处理方式 public static void tryRelease(Connection conn, Statement st, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException ignored) { } } if (st != null) { try { st.close(); } catch (SQLException ignored) { } } if (conn != null) { try { conn.close(); } catch (SQLException ignored) { } } } // 尝试回滚 public static void tryRollback(Connection conn) { try { if (conn != null) { conn.rollback(); } } catch (SQLException ignored) { } } } 

15 数据处理示例

 

15.1 数据清理

create table lagou_position_result as
  select distinct * from lagou_position where pid is not null and city is not null and position is not null and field is not null and salary_min is not null and salary_max is not null and workyear is not null and education is not null and ptype is not null and pnature is not null and advantage is not null and company_id is not null and company_short_name is not null and company_full_name is not null and company_size is not null and financestage is not null; 

15.2 表的分离

-- 源数据
select * from china_city limit 4;
select * from lagou_position_bk limit 2; -- lagou_position_bk (工作信息,地区信息,分类信息,公司信息) -- 要求,创建三张表 -- lagou_city (cityid, province, city, district) # 全国的省市县信息,需要从 china_city 表中提取出来 -- lagou_company (cid, name, short_name, size, financestage) # 所有的公司表,从 lagou_position_bk 中分离出来 -- lagou_position (pid, cityid, cid, position, field, salary_min, salary_max, workyear, education, ptype, pnature, advantage, published_at, updated_at) 

lagou_city 创建过程:

-- 373 市 3341 县 总和 3714
select count(*) from china_city where depth=2; select count(*) from china_city where depth=3; -- 分步创建 create table lagou_city01 as select d.id, p.cityName as province, c.cityName as city, d.cityName as district from (select * from china_city where depth=3) d join china_city c on d.parentId = c.id and c.depth=2 join china_city p on c.parentId = p.id and p.depth=1; insert into lagou_city01 select c.id, p.cityName as province, c.cityName as city, null as district from (select * from china_city where depth=2) c join china_city p on c.parentId = p.id and p.depth = 1; select count(*) from lagou_city01; -- 或者使用 union 语句 create table lagou_city as select d.id, p.cityName as province, c.cityName as city, d.cityName as district from (select * from china_city where depth=3) d join china_city c on d.parentId = c.id and c.depth=2 join china_city p on c.parentId = p.id and p.depth=1 union select c.id, p.cityName as province, c.cityName as city, null as district from (select * from china_city where depth=2) c join china_city p on c.parentId = p.id and p.depth = 1; 

lagou_company 公司分离:

drop table if exists lagou_company;
create table lagou_company as select distinct t.company_id as cid, t.company_short_name as short_name, t.company_full_name as full_name, t.company_size as size, t.financestage from lagou_position_bk t; 

将公司、城市信息从主表分离出去:

create table lagou_position
as
select pid, cid as city, company_id as company, position, field, salary_min, salary_max, workyear, education, ptype, pnature, advantage, published_at, updated_at from ( -- position 表中 district 为空的数据 select p.*, c.cid from (select * from lagou_position_bk where district is null) p join lagou_city c on c.city like concat(p.city, '%') and c.district is null union all -- position 表中 district 不为空的数据 select p.*, c.cid from (select * from lagou_position_bk where district is not null) p join lagou_city c on c.city like concat(p.city, '%') and c.district like concat(p.district, '%') ) as ppp; -- 也可以分步进行。使用 union 语句虽然会简化语句,但效率会比较低 create table lagou_position as select pid, cid as city, company_id as company, position, field, salary_min, salary_max, workyear, education, ptype, pnature, advantage, published_at, updated_at from (select * from lagou_position_bk where district is null) p join lagou_city c on c.city like concat(p.city, '%') and c.district is null; insert into lagou_position select pid, cid as city, company_id as company, position, field, salary_min, salary_max, workyear, education, ptype, pnature, advantage, published_at, updated_at from (select * from lagou_position_bk where district is not null) p join lagou_city c on c.city like concat(p.city, '%') and c.district like concat(p.district, '%'); 

注意:

  • 分表过程中、分完的表,需要添加主键或索引,否则关联查询会特别特别慢
  • 使用 create as 语句分表会比较简单,但这个过程存在数据的复制,会比较占用硬盘存储
  • 除了 create as 语句,也可以在基表上进行数据操作(删改)。但是注意数据的备份,当心不慎操作导致的数据永久丢失
  • 备份迁移需要在系统停机的情况下进行

16 省市县模式数据库设计

最朴素的实现方式:

  create table area_demo1 (
    area_id int auto_increment primary key, sheng varchar(20) not null, shi varchar(20) not null, xian varchar(20) not null, quhao varchar(20) not null, youbian varchar(20), jingdu decimal(10, 10), weidu decimal(10, 10) ); insert into area_demo1 (sheng, shi, xian, quhao) values ('广东省', '珠海市', '斗门区', '22323'), ('广东省', '珠海市', '香洲区', '22324'), ('广东省', '珠海市', '金湾区', '22325'), ('广东省', '广州市', '天河区', '11155'), ('广东省', '广州市', '越秀区', '11156'), ('广东省', '广州市', '白云区', '11157'); select * from area_demo1 where shi='珠海市' or shi='广州市'; select distinct shi from area_demo1 where sheng='广东省'; select * from area_demo1; 

按照范式,需要分表:

create table s_sheng (pid int auto_increment primary key, p_name varchar(20)); insert into s_sheng (p_name) values ('广东省'), ('山东省'), ('广西省'), ('湖南省'); create table s_shi (sid int auto_increment primary key, s_name varchar(20), sheng int references s_sheng(id)); insert into s_shi (s_name, sheng) values ('珠海市', 1), ('广州市', 1), ('湛江', 1), ('桂林市', 3), ('南宁市', 3), ('柳州', 3); create table s_xian (xid int auto_increment primary key, x_name varchar(20), shi int references s_shi(id), youbian varchar(20), jingdu varchar(20), weidu varchar(20)); insert into s_xian (x_name, shi) values ('斗门区', 1), ('金湾区', 1), ('香洲区', 1), ('白云区', 2), ('越秀区', 2), ('天河区', 2), ('锦绣区', 5), ('风景区', 5); select * from s_sheng; select * from s_shi; select * from s_xian; delete from s_xian where xid > 8; 

查询,以及语句:

-- 全连接,笛卡尔积
select * from s_shi full join s_sheng;

select * from s_shi inner join s_sheng on s_shi.sheng = s_sheng.id; select * from s_shi s inner join s_sheng t on s.sheng = t.id; select * from (select * from s_shi s inner join s_sheng t on s.sheng = t.id) w where w.name = '广东省'; select * from (select * from s_shi s inner join s_sheng t on s.sheng = t.pid) w where w.p_name = '广东省'; select sid, s_name from ( select s.sid, s.s_name, p.p_name from s_shi s join s_sheng p on s.sheng = p.pid ) a where p_name = '广东省'; select * from s_xian x inner join s_shi s on x.shi = s.sid inner join s_sheng sh on s.sheng = sh.pid where sh.p_name = '广东省'; select x.* from s_xian x join s_shi on s_shi.sid = x.shi join s_sheng on s_shi.sheng = s_sheng.pid where p_name = '广西省'; select x.* from s_xian x join s_shi on s_shi.sid = x.shi where s_name = '珠海市'; select * from s_sheng; select * from s_shi; select * from s_xian; select x.* from s_xian x, s_shi s, s_sheng p where x.shi = s.sid and s.sheng = p.pid and p.p_name = '广西省'; select * from s_shi s left join s_sheng p on s.sheng = p.pid; select * from lagou_position_result limit 5; select * from company_result limit 5; select * from s_provinces limit 5; select count(*) from lagou_position_result; desc s_provinces; select * from s_sheng limit 1; select * from s_shi limit 1; select * from s_provinces limit 5; select * from lagou_position_result order by salary_min desc limit 10; 

合并为一张表的方式,连接自己:

CREATE TABLE `s_provinces` (
  `id` int(11) NOT NULL,
  `cityName` varchar(30) NOT NULL, `parentId` int(11) NOT NULL, `shortName` varchar(30) NOT NULL, `depth` int(1) NOT NULL, `cityCode` varchar(4) NOT NULL, `zipCode` varchar(6) NOT NULL, `mergerName` varchar(50) NOT NULL, `longitude` varchar(16) NOT NULL, `latitude` varchar(16) NOT NULL, `pinyin` varchar(30) NOT NULL, `isUse` int(1) unsigned zerofill DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 

查询广东省所有县市:

-- 所有县
select x.id, p.cityName, s.cityName, x.cityName
from s_provinces x
       join s_provinces s on x.parentId = s.id
       join s_provinces p on s.parentId = p.id where p.cityName = '广东省'; -- 所有市 select x.id, p.cityName, s.cityName, x.cityName from s_provinces x join s_provinces s on x.parentId = s.id join s_provinces p on s.parentId = p.id where p.cityName = '广东省' union all select s.id, p.cityName, s.cityName, null from s_provinces s join s_provinces p on s.parentId = p.id where p.cityName = '广东省'; 

请总结各种方式的优劣。

17 查询练习

创建表:

DROP TABLE IF EXISTS emp;

CREATE TABLE emp ( empno decimal(4,0) NOT NULL, ename varchar(10) default NULL, job varchar(9) default NULL, mgr decimal(4,0) default NULL, hiredate date default NULL, sal decimal(7,2) default NULL, comm decimal(7,2) default NULL, deptno decimal(2,0) default NULL ); DROP TABLE IF EXISTS dept; CREATE TABLE dept ( deptno decimal(2,0) default NULL, dname varchar(14) default NULL, loc varchar(13) default NULL ); INSERT INTO emp VALUES ('7369','SMITH','CLERK','7902','1980-12-17','800.00',NULL,'20'); INSERT INTO emp VALUES ('7499','ALLEN','SALESMAN','7698','1981-02-20','1600.00','300.00','30'); INSERT INTO emp VALUES ('7521','WARD','SALESMAN','7698','1981-02-22','1250.00','500.00','30'); INSERT INTO emp VALUES ('7566','JONES','MANAGER','7839','1981-04-02','2975.00',NULL,'20'); INSERT INTO emp VALUES ('7654','MARTIN','SALESMAN','7698','1981-09-28','1250.00','1400.00','30'); INSERT INTO emp VALUES ('7698','BLAKE','MANAGER','7839','1981-05-01','2850.00',NULL,'30'); INSERT INTO emp VALUES ('7782','CLARK','MANAGER','7839','1981-06-09','2450.00',NULL,'10'); INSERT INTO emp VALUES ('7788','SCOTT','ANALYST','7566','1982-12-09','3000.00',NULL,'20'); INSERT INTO emp VALUES ('7839','KING','PRESIDENT',NULL,'1981-11-17','5000.00',NULL,'10'); INSERT INTO emp VALUES ('7844','TURNER','SALESMAN','7698','1981-09-08','1500.00','0.00','30'); INSERT INTO emp VALUES ('7876','ADAMS','CLERK','7788','1983-01-12','1100.00',NULL,'20'); INSERT INTO emp VALUES ('7900','JAMES','CLERK','7698','1981-12-03','950.00',NULL,'30'); INSERT INTO emp VALUES ('7902','FORD','ANALYST','7566','1981-12-03','3000.00',NULL,'20'); INSERT INTO emp VALUES ('7934','MILLER','CLERK','7782','1982-01-23','1300.00',NULL,'10'); INSERT INTO dept VALUES ('10','ACCOUNTING','NEW YORK'); INSERT INTO dept VALUES ('20','RESEARCH','DALLAS'); INSERT INTO dept VALUES ('30','SALES','CHICAGO'); INSERT INTO dept VALUES ('40','OPERATIONS','BOSTON'); 

1:

  • 在芝加哥工作的人中,谁的工资最高
  • 查询每个部门下有多少员工
  • 查询除去 salesman 所有平均工资超过 1500 的部门
  • 查询在 new york 工作的所有员工的姓名,部门名称和工资信息
  • 查询姓名为 King 的员工的编号,名称跟部门
  • 查询各种工作的最低工资
  • 查询工龄大于10年的所有员工信息
  • 查询每个部门员工数量,平均工资和平均工作年限
  • 统计各部门每个工种的人数,平均工资。
  • 查询从事同一种工作但不属于同一部门的员工信息。
  • 查询所有员工工资都大于1000的部门的信息及员工信息
  • 查询入职日期早于其直接上级的所有员工信息。
  • 列出雇员中(除去mgr为空的人)工资第二高的人。
  • 列出1981年来公司所有员工的总收入(包括sal和comm)

2:

  • 查询工资为 2500 到 4000 的人的数量(用不同方式查询)
  • 查询部门编号为 10 和 30 的所有人(用不同方式查询)
  • 查询部门编号为 10 和 30 中名字中不含有 ‘C’ 的所有人
  • 查询部门编号为 10 和 30 中名字首字母之外不含有 ‘C’ 的所有人
  • 查询部门编号为 10 和 30 中所有的经理以及名字首字母之外不含有 ‘C’ 的所有人
  • 查询纽约和芝加哥地区所有的经理以及名字首字母之外不含有 ‘C’ 的所有人
  • 查询纽约和芝加哥地区所有的经理以及顶头上司名字的首字母之外不含有 ‘C’ 的所有人
  • 查询每个人的工资等级
  • 查询每个部门的平均工资的等级

3:

  • 查询每个组最高工资的那些人
  • 有下面一个表,写一条 sql 语句计算男女之差

    gender number
    46
    10
  • 给 emp 中的人加工资,请写出相关语句:

    条件 加多少
    1000元以下 50%
    2000元以下 30%
    3000元以下 20%
    其他 5%

猜你喜欢

转载自www.cnblogs.com/nongzihong/p/10036160.html