mysql3

DB设计

指导db设计的思想,避免表中出现大量的冗余的数据.但是实际开发中表中是允许出现冗余的字段的.

冗余数据的好处 - 安全,方便查询.坏处->delete和update操作

  1. 1NF - 第一范式-保证列的原子性,保证列不可再切割

    s_emp(id,name) - 发现name不具备原子性,可以被再次切割(fist_name,last_name)

    s_ord(id,address) - address显示为省市区 - 江苏省苏州市园区

    address再次分成(province,city,area)

    1
    2
    3
    4
    5
    6
    7
    8
    主要是为了查询.

    select * from s_ord where address like '%苏州%';

    select * from s_ord where city = '苏州';

    -- address,city加索引.
    -- 模糊查询可能导致索引失效.但是精确匹配是走索引的.
  2. 2NF - 基于1NF,要求非关键列要全部依赖于关键列. 不存在非关键列部分依赖于关键列

    关键列 - 主键列 - 非空且唯一 - table中的id设置成主键列.

    student(sid,sname,cid,cname);//关键列(sid,cid) - 确定唯一的元组(行) - 复合主键

    sname,cname - 非关键列.

    (sid,cid) -> 唯一确定 -> sname/cname

    sid -> sname,cid -> cname

    1
    2
    3
    student(sid,sname)
    course(cid,cname);
    sc(sid,cid,kpi);//(sid,cid) -> kpi
  3. 3NF- 基于2NF,要求非关键列不能传递依赖于关键列.

    student(sid,sname,uid,uv_name,u_phone);

    u_phone传递依赖于sid - 原因sid -> uid -> u_phone

    1
    2
    student(sid,sname,uid);//uid就是foreign key 外键列
    university(uid,u_name,u_phone);

练习

  1. 用户表t_user
  2. 视频表t_video
  3. 评论表和回复表设计

用户对视频进行评论(t_comment)和回复(t_repy)的场景:

tom 发布了 搞笑的视频

admin 评论: 😁😎

​ li评论admin: 你笑什么!

​ lei回复@li: 管你什么事情!

​ x回复@li: 你想表达什么!

​ y回复@x: ….

​ k 评论admin:你笑什么!

jack 评论: 😄

t_user

id usernmae
1 tom
2 admin
3 li
4 lei
5 x
6 y
7 k

t_video

一个用户可以发布多个视频

id video user_id
1 佩洛西 1

t_comment

id video_id content comment_id user_id
1 1 😁😎 2
2 1 你笑什么! 1 3
3 1 你笑什么! 1 7

t_reply

id comment_id conent reply_id user_id
1 2 管你什么事情! 4
2 2 你想表达什么! 5
3 2 2 6

DDL语言

Data Definition Language - 数据定义语言.

包括的命令:create drop alter comment rename truncate

数据类型

  1. 数字类型

    • 整数 - int/bigint
    • 小数 - float/double/decimal

    要点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    drop table t_type;
    create table t_type(
    id int(7)
    );
    此处的7并不是代表此列的最大长度.误区:最大可保存的值是9999999
    实际上该列可以存储的最大值是由类型决定了.

    配置zerofill 0填充
    create table t_type(
    id int(7) zerofill
    );
    insert into t_type values(1);

    不足7位,会采用0来填充

    +---------+
    | id |
    +---------+
    | 0000001 |
    +---------+

    drop table t_type;
    create table t_type(
    c1 float(5,2),
    c2 double(5,2),
    c3 decimal(5,2)
    );
    float(m,n) => m总长度,n代表的小数点
    insert into t_type(c1) values(12.345);
    mysql> select * from t_type;

    -- 小数保留2位,并且四舍五入
    +-------+------+------+
    | c1 | c2 | c3 |
    +-------+------+------+
    | 12.35 | NULL | NULL |
    +-------+------+------+

    mysql> insert into t_type(c2) values(123.5); //ok
    mysql> select * from t_type;
    +-------+--------+------+
    | c1 | c2 | c3 |
    +-------+--------+------+
    | 12.35 | NULL | NULL |
    | NULL | 123.50 | NULL |
    +-------+--------+------+

    mysql> insert into t_type(c2) values(1234.5);

    `原因:1234.5 => 1234.50 已经超过5位了`
    ERROR 1264 (22003): Out of range value for column 'c2' at row 1

    float/double => 非标准 decimal标准 - 对精度有特别要求的

    insert into t_type(c3) values(123.589);
  2. 关于日期类型

    date

    datetime

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    drop table t_type;
    create table t_type(
    id int,
    create_date date default now()
    );
    ERROR 1067 (42000): Invalid default value for 'create_date'

    如果某个列期望默认值是now(),一定要设置成datetime
    drop table t_type;
    create table t_type(
    id int,
    create_date datetime default now()
    );
    insert into t_type(id) values(1);

    -- 满足日期能够支持的字符串格式
    drop table t_type;
    create table t_type(
    id int,
    create_date date default '2012-09-08 12:12:12'
    );
    insert into t_type(id) values(1);
  3. 字符串类型

    • char和varchar区别

      char(n),varchar(n) => 代表的字符的个数

      区别: char是定长,varchar是可变长.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      drop table t_type;
      create table t_type(
      s1 char(2),
      s2 varchar(2)
      );
      insert into t_type(s1) values('ab');

      insert into t_type(s2) values('abc');

      char(2) => 实际存储1个'a',实际消耗了2个
      varchar(2) => 假设'a',实际仅仅消耗了1个,最大消耗2个.
      varchar更加节约空间. char效率会更高一点 - 不推荐用char

      -- oracle中数据中
      drop table t_type;
      create table t_type(
      s1 char(2)
      );
      insert into t_type(s1) values('a');

      -- oracle查询为empty
      -- mysql中空直接比较的时候剔除,大小写比较都是一样的.
      select * from t_type where s1='a';
    • text - 0-65 535 bytes - 长文本

约束类型

  1. 主键约束 primary key 非空且唯一

    主键列可以是由多列共同组成 - 符合主键列

    但是表中只能出现一个主键

  2. 外键约束 foreign key

    • 外键列允许为null

    • 外键列是多出现在多的一方中.建立表与表之间的关系的

    • 开发中不需要设置外键 - 造成表与表之间的耦合.

      表和表之间的关系是在”心中”!

  3. 非空约束 - not null

  4. 唯一约束 - unique

  5. default - 默认值

  6. mysql中不支持自检约束,oracle中支持的check检查约束.但是mysql中可以使用触发器来实现.

创建表语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DROP table 表名;
-- 约束列级添加
CREATE TABLE 表名(
列名 数据类型(n) 约束类型 COMMNET '注释',
列名 数据类型(n) 约束类型 COMMNET '注释'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

engine 指定存储引擎,默认的就是innodb(外键约束,事务,行锁,表锁)
之前的版本应该是myisam(仅仅支持表锁)

-- 约束表级
-- 约束命名规范:表名_列名_约束类型缩写
CREATE TABLE 表名(
列名 数据类型(n) COMMNET '注释',
列名 数据类型(n) COMMNET '注释',
[CONSTRAINT 约束名称] 约束类型(列名),
[CONSTRAINT 约束名称] 约束类型(列名)
);

列级添加方式

直接在列的后面的增加关于该列的约束

1
2
3
4
5
6
7
8
drop table t_user;
create table t_user(
id int(7) primary key,
username varchar(20) unique not null,
birthday date default '1991-09-08'
);

insert into t_user(id) values(1);

表级添加方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
-- not null仅仅支持列级添加
drop table t_user;
create table t_user(
id int(7),
username varchar(20) not null,
birthday date,
constraint t_user_id_pk primary key(id),
constraint t_user_username_uq unique(username)
);

-- 约束名 - 提高错的可读性
insert into t_user(id) values(1);

-- 数据字典 - 描述表的表
-- 数据字典就是用来存放用户信息/用户创建的这表的信息.
show tables;
desc 表名;

-- 使用系统自带的数据库mysql
use information_schema

-- TABLE_CONSTRAINTS 表名 - 数据字典 - 专门存储每个用户的表的约束信息
desc table_constraints
+--------------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+--------------+------+-----+---------+-------+
| CONSTRAINT_CATALOG | varchar(512) | NO | | | |
| CONSTRAINT_SCHEMA | varchar(64) | NO | | | |
| CONSTRAINT_NAME | varchar(64) | NO | | | |
| TABLE_SCHEMA | varchar(64) | NO | | | |
| TABLE_NAME | varchar(64) | NO | | | |
| CONSTRAINT_TYPE | varchar(64) | NO | | | |
+--------------------+--------------+------+-----+---------+-------+
CONSTRAINT_NAME - 约束名称
TABLE_NAME - 表名
CONSTRAINT_TYPE - 约束类型

-- 查询出T_USER表中的约束信息
select constraint_name,constraint_type,table_name from table_constraints
where table_name='T_USER' and table_schema='dy';

-- 查询t_user表中的所有的列的名称以及列的数据类型
select column_name,column_type,data_type,column_key from columns where table_schema='dy' and table_name='t_user';

建表的其他语法

  1. 利用一张已经存在的表来构建另外一张表

    • 保留原来表中的数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      drop table t_user;
      create table t_user(
      id int(7),
      username varchar(20) not null,
      birthday date,
      constraint t_user_id_pk primary key(id),
      constraint t_user_username_uq unique(username)
      );
      insert into t_user values(1,'tom','2021-09-08');
      insert into t_user values(2,'jack','2022-09-08');

      -- 克隆
      CREATE TABLE 表名 AS SELECT查询语句;

      create table t_user_copy as select * from t_user;
    • 保留原表的结构,但是不保留数据

      1
      2
      3
      drop table t_user_copy;

      create table t_user_copy as select * from t_user where 1=2;
  1. 查看建表语句-DDL

    1
    show create table 表名;
  2. 如果是一个1:n

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    drop table t_ord;
    drop table t_customer;
    create table t_customer(
    id int(7),
    cname varchar(20) not null,
    constraint t_customer_id_pk primary key(id)
    );

    create table t_ord(
    id int(7) primary key,
    ord_no varchar(20) not null unique,
    total double(7,2),
    customer_id int(7),
    constraint t_ord_customer_id_fk foreign key(customer_id) references t_customer(id)
    );

    -- 一定是父级表t_customer
    -- 删表 - 先删除子表,多的一方,外键所在的那张表.
  3. n:n

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    drop table t_sc;
    drop table t_student;
    drop table t_course;

    create table t_student(
    id int primary key
    );
    create table t_course(
    id int primary key
    );
    create table t_sc(
    sid int,
    cid int,
    primary key(sid,cid),
    foreign key(sid) references t_student(id),
    foreign key(cid) references t_course(id)
    );

自学的知识点

四种分区方式 - 测试

DML操作

insert语句

  • 给表中所有的列都插入数据,但是需要注意的是顺序/类型/约束

    1
    insert into 表名 values(列1,列2,列3);
  • 给指定的列插入数据

    1
    insert into 表名(列名1,列名2) values(列1,列2);
  • 批量插入

    1
    insert into 表名 values(列1,列2,列3),(列1,列2,列3),(列1,列2,列3);

delete语句

  • delete from 表名;//删除表中的数据

  • delete from 表名 where 条件;

  • 外键约束会对delete产生影响,一定是先删除子记录,然后才能够删除父记录.

    先删除多的一方,然后才能够删除一的一方.

  • 级联删除 - 删除的一方之前先将这个一方的子记录全部删除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    drop table t_ord;
    drop table t_customer;
    create table t_customer(
    id int(7),
    cname varchar(20) not null,
    constraint t_customer_id_pk primary key(id)
    );
    insert into t_customer values(1,'admin');
    insert into t_customer values(2,'tom');

    create table t_ord(
    id int(7) primary key,
    ord_no varchar(20) not null unique,
    total double(7,2),
    customer_id int(7),
    constraint t_ord_customer_id_fk foreign key(customer_id) references t_customer(id) on delete cascade
    );
    insert into t_ord values(1,'1001',300,1);
    insert into t_ord values(2,'1002',300,null);

    delete from t_customer where id=1;

    1

update语句

  • update 表 set 列名=列值,列名2=列值;//更新表中所有行
  • update 表 set 列名=列值,列名2=列值 WHERE条件;//

truncate和delete和drop

共同点

1.truncate和不带where子句的delete、以及drop都会删除表内的数据。

2.drop、truncate都是DDL语句(数据定义语言),执行后会自动提交。

不同点

delete 语句是数据库操作语言(dml),这个操作会放到 rollback segement 中,事务提交之后才生效;如果有相应的 trigger,执行的时候将被触发。

truncate、drop 是数据库定义语言(ddl),操作立即生效,原数据不放到 rollback segment 中,不能回滚,操作不触发 trigger

速度,一般来说: drop> truncate > delete

分页

  • limit m // 前m行
  • limit m,n // m从下标0开始(第一行),n显示行数

分页的公式

1
2
3
4
5
6
public Page<Book> page(Integer pageNum,Integer pageSize){
// limit (pageNum-1)*pageSize,pageSize

//1,3 0
//2,2 3
}

分页的查询效率问题

limit m,n 其中的m叫做偏移量. 偏移量越大,分页的性能越低.

比如表1-100000,正好id=1,10000

select * from 表 where id>条件 limit 2;

条件查询(减少偏移量) + limit

事务

用来执行一组SQL,是db中完成业务的基本单位.

事务ACID特性

  1. 原子性(Atomicity) - 事务不可再分割.

    事务要么同时成功,同时失败 - 比如转账业务.

  2. 一致性(Consistency) - 事务一旦提交.内存中的数据要和磁盘上的数据要一致,保证数据的完整性.

    比如tom-500,jack-500.无论转多少,转多少次.反正总的钱是1000

  3. 隔离性(isolation): 事务与事务之间的彼此隔离的,互不干扰的.一个事务的结束,意味着下一个事务的开始.

  4. 持久性(Durability) - 事务一旦提交,数据应该永久保存在磁盘中.即使发生一些故障,应该可以用一些恢复的技术进行恢复.

事务的分类

  1. 本地事务 - 一个项目(单体架构)连接一个数据库
  2. 分布式事务
    • 消息中间件 - rabbitmq,rocketmq
    • 阿里的框架seata

事务的隔离级别

  1. ① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

    锁表.

  2. ② Repeatable read (可重复读):可避免脏读、不可重复读的发生。

  3. ③ Read committed (读已提交):可避免脏读的发生。

    一个事务只能读取到另外一个事务已经提交的数据.

  4. ④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。产生脏读.

    一个事务可以读取到另外一个事务尚未提交的数据

查询当前会话的隔离级别

mysql默认的是可重复读的隔离级别

1
2
3
4
5
6
7
8
9
10
select @@tx_isolation;

+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+

临时设置一下当前会话的事务的隔离级别
set session transaction isolation level read uncommitted;

事务TCL命令

begin(开启一个事务),commit(提交一个事务),savepoint 事务点

set autocommit=0;//手动开启一个事务

DML操作默认都是会自动提交事务.

补充一点

  1. rollback - 回滚事务 - 结束一个事务

    不能回滚已经commit之后的事务.

  2. savepoint 事务点

    rollback to 事务点

    如果么有设置事务点 - 回滚当前事务中所有的操作.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)

    mysql> update t_acc set balance=balance+1000 where id=1;
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1 Changed: 1 Warnings: 0

    mysql> savepoint t1;
    Query OK, 0 rows affected (0.00 sec)

    mysql> update t_acc set balance=balance+1000 where id=2;
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1 Changed: 1 Warnings: 0

    mysql> rollback to t1;
    Query OK, 0 rows affected (0.01 sec)

    mysql> commit;
    Query OK, 0 rows affected (0.00 sec)

多事务并发带来的问题

多个事务对同一个数据进行操作的时候,会产生一些问题

1
2
3
4
5
6
drop table t_acc;
create table t_acc(
id int,
balance double(7,2)
);
insert into t_acc values(1,10000),(2,10000);

脏读

一个事务读到了另外一个事务没有提交的数据 - 绝对不允许的.

不可重复读

set session transaction isolation level read committed;

一个事务A在读取一次之后,另外一个事务B在进行update操作.并且commit.

A事务在没有自己没有提交之前,读取到了B事务提交之后的数据.导致A事务在当前事务中多次读取到的结果不一样.

幻读

它和不可重复读类似.侧重点不同.不可重复读强调的是一个事务在查询.另外一个事务在修改(update)

幻读强调的是一个事务在修改.另外一个事务在插入(insert).

set session transaction isolation level read committed;

事务A在修改,事务B-insert并且提交了.事务A在本事务中再次查询,发现了好像有”更新失败”的数据.就像发生了幻觉一样.

可重复读

默认的隔离级别

set session transaction isolation level repeatable read;

事务A读取一次,事务B执行update操作.事务A在没有结束当前事务之前,多次读取到的结果是一样的.

必须要commit之后,才能够读取到B事务提交之后的修改数据.

总结

脏读 不可重复读 幻读
读未提交
读已提交 ×
可重复读 × ×
串行化 × × ×

mysql2

视图-view

我们使用create命令来构建database,user,table(基本单位),view,索引,函数,存储过程,触发器 - 数据库的单位.

视图实际上就是一张”虚拟表”,实际上是不存在的.只是逻辑上的查询结果的集合.

视图的分类

  1. 简单视图

    视图来自于单表查询的集合 - 允许执行DML操作

  2. 复杂视图

    视图来自于多表关联查询的集合 - 不一定允许执行DML操作.

创建的语法

1
2
3
4
5
DROP view 视图名;

CREATE VIEW 视图名
AS
SELECT查询语句;
1
2
3
create view t_acc_view as select * from t_acc;

select * from t_acc_view;

复杂视图

1
2
3
4
5
6
7
8
9
10
11
12
13
drop view co_view;
create view co_view as
select c.cname,o.ord_no from t_customer c join t_ord o on c.id = o.customer_id;

update co_view set cname='xx'; //允许update

drop view co_view;
create view co_view as
select customer_id,count(*) c_ from t_ord group by customer_id;

-- 不允许执行update操作.
mysql> update co_view set c_=1;
ERROR 1288 (HY000): The target table co_view of the UPDATE is not updatable

测试

  1. 修改原表,视图肯定变化.有可能还会导致视图数据全部丢失.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    drop view t_acc_view;
    create view t_acc_view
    as
    select * from t_acc where balance=16000;

    -- 更新原表

    -- 更新了这个视图来源的那个条件列.
    update t_acc set balance = balance+1000 where id=2;

    mysql> select * from t_acc_view;
    Empty set (0.00 sec)
  2. 修改视图 - 同样也会对原表造成影响.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    drop view t_acc_view;
    create view t_acc_view
    as
    select * from t_acc where id=1;

    -- 更新视图
    update t_acc_view set balance=1000 where id=1;

    mysql> select * from t_acc;
    +------+----------+
    | id | balance |
    +------+----------+
    | 1 | 1000.00 |
    | 2 | 17000.00 |
    | 3 | 15000.00 |
    +------+----------+
  3. 针对题1的情况,更新列可能导致视图失效.

    1
    2
    3
    4
    drop view t_acc_view;
    create view t_acc_view
    as
    SELECT带where的条件查询语句 with check option

    with check option作用:不允许更新视图的来源的那个条件列. 肯定是要配合where语句一起使用的,否则没有任何意义

    1
    2
    3
    4
    5
    6
    7
    drop view t_acc_view;
    create view t_acc_view
    as
    select * from t_acc where balance=1000 with check option;

    mysql> update t_acc_view set balance=2000 where id=1;
    ERROR 1369 (HY000): CHECK OPTION failed 'dy.t_acc_view'

视图的好处

  1. 封装SQL语句

    封装比较复杂的SQL语句,在Java中直接查询视图即可.

  2. 定制数据

    系统不同的角色 - 看到的数据应该是不一样的.

  3. 安全性

    数据库应该有多个user账号的,可以给不同的用户授予不同的视图的权限(select,insert,update,delete)

    授权grant

  4. 合并抽离出去的数据 - 对用户屏蔽的底层的数据库的结构的设计.

索引-index

作用:提高查询的效率,类似于书的那个目录.

介绍

  1. Myisam存储引擎

    索引文件和数据文件是分开存储的.是俩个独立的文件

    数据结构:B+树

  2. Innodb存储引擎

    数据和索引是合二为一的,是一个文件.

    B+树

  • 索引也是占用物理空间的.
  • 对表中的数据进行DML操作的时候,维护索引的-消耗一点额外的时间
  • 如果表中某列重复的数据比较多,没有必要创建索引 - 查找的时候接近于全表扫描花费的时间.
  • 当表中没有指定索引列,那么默认的索引列就是主键列.
  • show index from 表名 \G;

索引的分类

  • 主键索引

    1
    2
    3
    4
    Key_name: PRIMARY

    -- 非要删除主键约束
    alter table 表名 drop primary key;
  • 唯一索引

    1
    某列增加unique唯一约束,自动创建唯一索引
  • 全文索引

    后期会使用elasticsearch来进行全文搜索 - 搜索引擎.

  • 复合索引 - 遵守最左匹配原则.

    1
    create index index_name on 表名(列1,列2,列3);
  • 普通索引

    1
    2
    create index index_name on 表名(列);
    drop index index_name;

索引失效场景※

如何查看索引是否生效 - 索引执行计划explain select语句;

type - const>eq_ref>ref>range>index>all)

type-all 全表扫描 - 索引是失效的.

1
2
3
4
5
6
explain select * from index_test where id=1;
+----+-------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | index_test | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
drop table index_test;
create table index_test(
id int(7) primary key,
a int(7),
b int(7),
c varchar(20),
d varchar(20)
);
insert into index_test values(1,100,10,'aaa','AAA');
insert into index_test values(2,200,30,'aab','BBB');
insert into index_test values(3,300,20,'caa','CCC');
insert into index_test values(4,100,10,'daa','DDD');
insert into index_test values(5,500,50,'aad','FFF');

drop index index_test_index;
create index index_test_index on index_test(a,b,c);

create index index_test_d on index_test(d(10));//10 - 索引长度

1. 复合索引需要遵守最左匹配原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- a,b,c都是走了索引的.
explain select * from index_test where a = 2 and b=200 and c='aaa';

type:ref key_len: 73

-- a走了索引
explain select * from index_test where a = 2;
type: ref key_len:5

-- a,b都是走了索引
explain select * from index_test where a = 2 and b=200;
type:ref key_len:10

-- b是不走的
explain select * from index_test where b=200;
type:ALL key_len:NULL

-- type:ref key_len: 73
-- mysql底层发现where条件列a,b,c都是复合索引,先进行优化,a,b,c,进行查询.
explain select * from index_test where b=200 and c='aaa' and a=2;

2. 模糊匹配

1
2
3
4
5
6
7
8
-- %在末尾 - 生效
explain select * from index_test where a = 2 and b=200 and c like 'a%';

-- %在开头,a,b是走索引,但是c没有走索引
explain select * from index_test where a = 2 and b=200 and c like '%a';

-- %?%,a,b是走索引,但是c没有走索引
explain select * from index_test where a = 2 and b=200 and c like '%a%';

3. 查询范围之后

1
2
3
4
5
-- b>=10 条件成立,范围之后的索引是生效的.  a,b,c都是走索引
explain select * from index_test where a = 100 and b>=10 and c='aaa';

-- b>10 条件不成立,范围之后的索引是失效的. 只有a,b是走索引的
explain select * from index_test where a = 100 and b>10 and c='aaa';

4. 索引列参加了计算

1
2
-- type:ALL
explain select * from index_test where id+1=1;

5. 索引列使用了函数

1
2
-- type:ALL
explain select * from index_test where abs(id)=1;

6. 查询的数据超过整张表的30%

7. is null和is not null

1
2
3
4
5
6
7
create index emp_index on s_emp(commission_pct);

-- typ:ALL is null 没有走索引
explain select * from s_emp where commission_pct is null;

-- type:range is not 走索引.
explain select * from s_emp where commission_pct is not null;

8. in和not in

1
2
3
4
5
6
7
8
explain select * from s_emp where id in(1,2,3);
explain select * from s_emp where id not in(1,2,3);

-- mysql5.7之前
in - 走索引
not in - 不走索引

-- 8.x not in允许走索引

关于索引的长度key_len

计算方式

  1. 数值类型 - int

    4 + 1(该列是否为null,没有设置not null,就需要加1,存储null也占1个)

  2. 字符串类型 - varchar(n) - utf8 - n*3+2+1(没有设置not null,就需要加1,存储null也占1个)

索引长度作用

1
2
3
4
5
6
7
8
site: www.baidu.com
www.baobao.com
www.baxy.com

site索引长度是5,不够,前缀重复比较多.
索引长度要有区分度.

推荐反转之后进行存储.减少索引长度.

索引的创建策略

  • 哪些列创建索引

    • 主键列,唯一性列

    • 重复性比较少,经常被查询但是同时更新不是特别频繁的列.

    • order by + 排序列 - 创建索引.

      1
      2
      order by语句本身性能就很低下的.  根据索引进行排序.
      如果是id列索引,插入的时候就会先排序了.
    • join on 列

      1
      2
      用来连接表的列适合建索引的.本身join越多,性能越低.
      开发中几乎都是单表查询.尽量不要去使用复杂查询.
  • 不适合创建索引

    • null值太多的列
    • 重复值太多的列
    • 更新比较频繁的列

  1. 行锁

    1
    2
    3
    4
    5
    6
    begin;
    update s_emp set first_name='xxx' where id=1;//条件列是索引列 - 行锁 - 降低锁的粒度

    行锁和表锁
    1. 行锁粒度小,涉及到频繁的加锁和释放锁的过程.
    3. 表锁会影响到查询效率
    1
    2
    3
    4
    begin;
    update s_emp set first_name='xxx' where id=1;//如果上面的事务没有commit,此处会阻塞

    如果此处更新的是另外一行,可以直接执行update s_emp set first_name='xxx' where id=2;
  2. 表锁

    1
    2
    非索引列条件更新的时候 - 锁表.
    所有的DML操作默认的申请表锁
  3. 页锁

    介于行锁和表锁之间的.

  4. 共享锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    select语句默认申请的共享锁.
    select申请排他锁.

    select语句 for update;

    实现悲观锁

    更新库存stock

    伪代码:

    begin;

    select stock from xxx where id=1 for update;

    //更新stock


    commit;

mysql1

数据库介绍

按照一定的数据结构来组织,管理和存储数据的仓库

用仓库的方式来管理数据的.一个仓库下会有很多表.一个表由行和列构成.

数据的分类

  1. 关系型数据库

    表格型数据库 - 有行和列来构成一张表,表中存放数据

    常见的产品:MS-sqlserver,主流的免费的Oracle-mysql(中小型的应用),收费的Oracle-oracle(大型的应用)

  2. nosql - 不仅仅是sql - 非关系型数据库

    • 键值对数据库 - redis
    • 文档型数据库 - mongodb
    • 列式数据库 - hbase
    • 图形数据库

数据库安装注意点

  1. server和client

    当我们安装好db之后,自带安装好了mysql-server端和mysql-client端

    db正常使用,一定要保障mysql-server端处于运行状态

    • 右击计算机 - 管理 - 服务 - 服务和应用程序 - 👉🏻右边窗口 - Mysql - 启动方式设置开启自启.

    • 可以通过终端输入命令来启动mysql-server端

      1
      2
      net start mysql
      net stop mysql
  1. mysql-client客户端
    • 第三方的收费navicat
    • idea
    • java代码
    • 自带的控制台

专业术语

  1. DB - DataBase 数据库

  2. DBA - 数据库管理员(侧重于运维),DE - 数据库研发工程师

  3. DBMS - 数据库管理系统,安装在操作系统.用户通过dbms来操作db,dba通过dbms来管理db,保证db在7*24h高效运转

    人 - dbms - db(os)

  4. RDBMS - 关系型数据库管理系统

  5. DBS - 数据库系统,大的范畴,dbs = dba+db+dbms

连接数据库

前提:mysql安装并且配置好了环境变量

1
mysql -u用户名 -p密码
1
mysql -uroot -proot

修改密码

1
2
3
4
5
6
7
8
alter user '用户名'@'db所在主机ip' identified by '新的密码';

use mysql

alter user 'root'@'localhost' identified by 'root';

-- 退出重新登录
exit;

导入脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-- 1. 创建数据库  create dabatase 数据库名
create database dy;

-- 查看所有的db
show databases;

-- 2. 使用数据库
use dy

-- 3. 导入脚本 - .sql为后缀的sql文件
-- 3-1. 在dy数据库中生成5张表
-- 3-2. 每张表中模拟一些数据
source 脚本文件的绝对路径;

-- 4. 查看该库下所有的表
show tables;

+--------------+
| Tables_in_dy |
+--------------+
| s_customer |
| s_dept |
| s_emp |
| s_ord |
| s_region |
+--------------+

--5. 查看表结构
desc s_emp

设置数据库编码

  1. 查看编码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    show variables like 'character_set%';

    +--------------------------+-----------------------------------------------------------+
    | Variable_name | Value |
    +--------------------------+-----------------------------------------------------------+
    | character_set_client | utf8 |
    | character_set_connection | utf8 |
    | character_set_database | utf8 |
    | character_set_filesystem | binary |
    | character_set_results | utf8 |
    | character_set_server | utf8 |
    | character_set_system | utf8 |
    | character_sets_dir | /usr/local/mysql-5.7.28-macos10.14-x86_64/share/charsets/ |
    +--------------------------+-----------------------------------------------------------+
  2. 修改编码

    2-1. 临时修改 - set命令 - 仅仅是对当前会话(一次连接,窗口)有效

    1
    set names utf8;

    2-2. 永久生效 - 找到my.ini文件

    1
    2
    3
    4
    5
    [client]
    # 设置mysql客户端默认字符集
    default-character-set=utf8
    [mysqld]
    character_set_server=utf8

    需要重启mysql-server(mysql服务器端),才能生效.

    1. 通过gui方式

    2. 通过命令

      1
      2
      net stop mysql
      net start mysql

SQL介绍

SQL 是 Structured Query Language 的缩写,中文译为“结构化查询语言”。SQL 是一种计算机语言,用来存储、检索和修改关系型数据库中存储的数据。

①mysql-client输入合法的sql语句,如果sql非法,直接报错

②输入回车- 客户端将sql语句发送到mysql-server端,编译和解析sql语句的.

③mysql-server将执行完sql语句的结果返回到mysql-client进行展示.

按照功能SQL分类

  1. DDL(Data Definition Language) - 数据定义语言

    create drop alter truncate rename comment(注释)

  2. DML(Data Manipulation Language) - 数据操纵语言

    update delete insert

  3. DQL(Data Query Language) - 数据查询语言

    select

  4. TCL/DTL(Transaction Control Language):事务控制语言

    commit savepoint rollback

简单查询

  1. 查询的本质

    • 到哪里查
    • 查什么
  2. 语法 - 关键字是大小写不敏感

    1
    2
    SELECT 列名1,列名2,..列n
    FROM 表名;

列取别名

  1. 查询员工的名字,薪水以及职称.

    1
    select first_name,salary,title from s_emp;

    方式一

    1
    2
    3
    4
    SELECT 列名1 列别名,列名2 列别名,..列n
    FROM 表名;

    select first_name 名字,salary 薪水,title 职称 from s_emp;

    方式二

    1
    2
    3
    4
    SELECT 列名1 as 列别名,列名2 as 列别名,..列n
    FROM 表名;

    select first_name as 名字,salary as 薪水,title as 职称 from s_emp;

    方式三

    1
    2
    列别名中间有空格,需要使用双引号来包裹格式
    select first_name "名 字",salary "薪 水",title as "职 称" from s_emp;
  2. 查询所有列*

    1
    2
    3
    4
    -- 格式化输出
    select * from s_emp;

    select * from s_emp \G;

去重关键字

distinct

  1. 查询员工表中所有的职称的信息

    1
    select distinct title from s_emp;

列的计算

列是允许计算的

  1. 查询员工的名字,年薪(不带提成commission_pct)

    1
    select first_name,salary*12 年薪 from s_emp;
  2. 查询员工的名字,年薪(带提成commission_pct)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    select first_name,salary*(1+commission_pct/100)*12 年薪 from s_emp;

    查询出来只有5个人有年薪,原因是因为只有5个人有提成.其他人的提成都是null值
    mysql - null值计算的结果还是一个null值

    使用到mysql中提供的空值置换函数
    coalesce(m,n) - 如果m为null,返回n,否则返回m

    select first_name,salary*(1+coalesce(commission_pct,0)/100)*12 年薪 from s_emp;

练习

  1. 找出员工的姓名,工资以及入职日期

    1
    2
    3
    4
    5
    select last_name,first_name,salary,start_date from s_emp;

    -- 列的拼接 concat(m,n,a,b,c,..)
    -- mysql中的字符串统一使用单引号
    select concat(last_name,' ',first_name) 姓名,salary,start_date from s_emp;
  2. 找出所有的客户名以及他的联系方式

    1
    2
    3
    4
    select name,phone from s_customer;

    -- 查询姓名的时候
    -- 蔡根花 - 蔡**
  3. 找出每个订单的费用,支付方式以及出货日期

    1
    select total,payment_type,date_ordered from s_ord;

条件查询

1
2
3
SELECT 列名1,列名2,..列n
FROM 表名
WHERE 条件表达式;

运算符

比较运算符

1
> < >= <= != <> =

其他运算符

  1. between … and .. 在…到…之间
  2. in(集合) - 在某个集合内
  3. not in(集合) - 不在某个集合内
  4. is - 判断是否为null,判断null值不能用=
  5. is not - 判断是否不为null

逻辑连接符

  1. and 条件同时成立,返回true
  2. or 有一个条件成立,就返回true

练习

  1. 查询部门编号(dept_id)为41,42,45的员工找出来

    1
    2
    3
    4
    select first_name,dept_id from s_emp
    where dept_id=41 or dept_id=42 or dept_id=45;

    select first_name,dept_id from s_emp where dept_id in(41,42,45);
  2. 查询没有提成的员工

    1
    select first_name,commission_pct from s_emp where commission_pct is null;
  3. 找出工资在[1200,1500]之间的员工

    1
    2
    3
    4
    select first_name,salary from s_emp where salary between 1200 and 1500;

    -- 更加灵活
    select first_name,salary from s_emp where salary>=1200 and salary<=1500;
  4. 查找工资大于1500并且没有提成的员工

    1
    select first_name,salary from s_emp where salary>1500 and commission_pct is null;
  5. 查找年薪(包括提成)超过15000的员工

    1
    2
    3
    4
    5
    6
    7
    8
    9
    select first_name,salary*(1+coalesce(commission_pct,0)/100)*12 _year from s_emp
    where salary*(1+coalesce(commission_pct,0)/100)*12>15000;

    -- 给列取别名的意义
    -- 子查询 - 查询的结果作为虚拟表 - 子查询一定要放在()中
    select * from X where _year>15000;

    查询的结果来替代X,子查询的结果作为表的话 - 需要给表取别名
    select * from (select first_name,salary*(1+coalesce(commission_pct,0)/100)*12 _year from s_emp) core_ where _year>15000;
  6. 找出除了41部门的员工

    1
    2
    3
    select first_name,dept_id from s_emp where dept_id <> 41;

    select first_name,dept_id from s_emp where dept_id not in(41);
  7. 找出亚洲区域的信息

    1
    2
    -- mysql中采用单引号
    select * from s_region where name='Asia';

模糊查询

大小写不敏感的

  1. 关键字like
  2. 占位符
    • % 代表的任意多个任意字符
    • _ 代表的是任意单个字符

转义字符

  • first_name名称正好包含了%或者_

    1
    update s_emp set first_name='Mar%k' where id=4;
  • 查询first_name包含%的员工

    1
    2
    3
    4
    5
    -- 定义转义字符,默认的是\
    select first_name from s_emp where first_name like '%\%%';

    -- 自定义转义字符,用escape 新的转义字符
    select first_name from s_emp where first_name like '%?%%' escape '?';

练习

  1. 查询first_name包含m的

    1
    select first_name from s_emp where first_name like '%m%';
  2. 查询以C开头的员工

    1
    select first_name from s_emp where first_name like 'C%';
  3. 查询第三个字母是’e’的员工

    1
    select first_name from s_emp where first_name like '__e%';
  4. 查询入职时间是6月份的员工 - 模糊查询 - 不是正规的

    1
    select first_name,start_date from s_emp where start_date like '%-06-%';

排序

1
2
3
4
5
6
7
SELECT 列名1,列名2,..列n
FROM 表名
WHERE 条件表达式
ORDER By语句 [asc|desc]

asc可以省略不写 - 升序
desc必须要写 - 降序
  1. 根据年薪降序排列

    方式一: order by 列名

    1
    2
    select first_name,salary*(1+coalesce(commission_pct,0)/100)*12 from s_emp 
    order by salary*(1+coalesce(commission_pct,0)/100)*12 desc;

    方式二: order by 列别名

    1
    2
    select first_name,salary*(1+coalesce(commission_pct,0)/100)*12 年薪 from s_emp 
    order by 年薪 desc;

    方式三: order by 列的序号 列的序号从1开始 => 推荐方式

    1
    2
    select first_name,salary*(1+coalesce(commission_pct,0)/100)*12 年薪 from s_emp 
    order by 2 desc;
  2. 根据年薪降序排.如果年薪一样,则继续按照first_name升序排列

    1
    2
    select first_name,salary*(1+coalesce(commission_pct,0)/100)*12 年薪 from s_emp 
    order by 2 desc,1;

转换函数

数字和字符串

  1. 字符串转换成数字

    1
    2
    3
    select '1'+0;

    mysql中字符数字遇到数字进行+,自动将字符转换成数字来进行计算.
  2. 数字转换成字符串 - concat函数 - 拼接删除

    1
    select concat(1,2);  // 12

字符串和日期

关于日期模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(在format字符串中可用标志符:  
 %m 月名字(january……december)
 %w 星期名字(sunday……saturday)
 %d 有英语前缀的月份的日期(1st, 2nd, 3rd, 等等。)
 %Y 年, 数字, 4 位
 %y 年, 数字, 2 位
 %a 缩写的星期名字(sun……sat)
 %d 月份中的天数, 数字(00……31)
 %e 月份中的天数, 数字(0……31)
 %m 月, 数字(01……12)
 %c 月, 数字(1……12)
 %b 缩写的月份名字(jan……dec)
 %j 一年中的天数(001……366)
 %H 24时制小时(00……23)
 %k 小时(0……23)
 %h 12时小时(01……12)
 %i 小时(01……12)
 %l 小时(1……12)
 %i 分钟, 数字(00……59)
 %r 时间,12 小时(hh:mm:ss [ap]m)
 %t 时间,24 小时(hh:mm:ss)
 %s 秒(00……59)
 %p am或pm
 %w 一个星期中的天数(0=sunday ……6=saturday )
  1. 获取当前系统的日期

    1
    2
    3
    4
    5
    6
    7
    select now();

    +---------------------+
    | now() |
    +---------------------+
    | 2022-08-05 11:09:49 |
    +---------------------+
  2. 日期转换成指定格式的字符串 - date_format(日期,模板)

    1
    2
    3
    4
    5
    模板  %Y   %m    %d  %H  %i   %s

    select date_format(now(),'%Y/%m/%d %H:%i:%s');

    select date_format(now(),'%Y年%m月%d日 %H时%i分%s秒');
  3. 字符串转成日期 - str_to_date(字符串,模板) - 字符串格式要和模板的格式匹配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    -- 删除表
    drop table tt;
    -- 创建表
    create table tt(
    id int(7) primary key,
    name varchar(20),
    birthday date
    );

    -- date - 年月日
    -- 插入数据
    -- mysql中默认支持的格式 - 会将字符串自动转成date类型
    insert into tt values(1,'admin','2022-09-01');//ok
    insert into tt values(2,'tom','2022/09/01');//ok
    insert into tt values(3,'jack','20221001');//ok

    -- 不支持的时候
    insert into tt values(4,'jack','aa');
    ERROR 1292 (22007): Incorrect date value: 'aa' for column 'birthday' at row 1

    insert into tt values(4,'jack','01-05-2022');
    ERROR 1292 (22007): Incorrect date value: '01-05-2022' for column 'birthday' at row 1

    -- 非要使用'01-05-2022'字符串格式的,才需要使用str_to_date函数进行转换
    insert into tt values(4,'jack',str_to_date('01-05-2022','%d-%m-%Y'));

    导入脚本的时候 - 失败场景 - 字符串日期格式不支持

单行函数

  • select 函数(参数);

  • 函数是可以应用在查询列上的.

  • 函数是允许嵌套使用的.

1. 字符串函数

  1. instr(str,substr) - 返回字符串substr在字符串str第一次出现的位置(str不包含substr时返回0)

  2. lpad(str,len,padstr) - 用字符串padstr填补str左端直到字串长度为len并返回

    1
    select lpad('ab',5,'cd');//cdcab
  3. rpad(str,len,padstr) - 用字符串padstr填补str右端直到字串长度为len并返回

    1
    select rpad('ab',5,'cd');//abcdc   5代表的是总的长度
  4. left(str,len) - 返回字符串str的左端len个字符

  5. right(str,len) - 返回字符串str的右端len个字符

  6. substring(str,pos,len) - 返回字符串str的位置pos起len个字符

  7. substring(str,pos) - 返回字符串str的位置pos起后面的子串

    1
    2
    序号从1开始
    select substring('mysql',2); //ysql
  8. ltrim(str) - 返回删除了左空格的字符串str

  9. rtrim(str) - 返回删除了右空格的字符串str

  10. space(n) - 返回由n个空格字符组成的一个字符串

  11. replace(str,from_str,to_str) - 用字符串to_str替换字符串str中的子串from_str并返回

    1
    select replace('i love java','java','mysql');
  12. reverse(str) - 颠倒字符串str的字符顺序并返回

  13. insert(str,pos,len,newstr) - 把字符串str由位置pos起len个字符长的子串替换为字符串

    1
    select insert('xxxxx',1,2,'**');
  14. lower(str) - 返回小写的字符串str

  15. upper(str) - 返回大写的字符串str

  16. char_length(str) - 不管汉字还是数字或者是字母都算是一个字符。

  17. length(str);//汉字占3个,其他占1个.

练习

  1. 配合表 - 将first_name全部转换成大写

    1
    select first_name,upper(first_name) from s_emp;
  2. first_name - 模拟银行账户显示,只显示首字母,其余每个字母用*表示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    -- substring和rpad
    select first_name,rpad(substring(first_name,1,1),char_length(first_name),'*') from s_emp;

    -- concat + substring + rpad
    select first_name,concat(substring(first_name,1,1),rpad('*',char_length(first_name)-1,'*')) from s_emp;

    -- replace + substring + rpad
    select first_name,replace(first_name,substring(first_name,2),rpad('*',char_length(first_name)-1,'*'))
    from s_emp;

    -- insert + rpad
    select first_name,insert(first_name,2,char_length(first_name)-1,rpad('*',char_length(first_name)-1,'*'))
    from s_emp;

2. 数字函数

  1. abs(n) - 求绝对值
  2. mod(n,m) - 取模运算,返回n被m除的余数(同%操作符)
  3. floor(n) - 返回不大于n的最大整数值 - 向下取整
  4. ceiling(n) - 返回不小于n的最小整数值 - 向上取整
  5. round(n[,d]) - 返回n的四舍五入值,保留d位小数(d的默认值为0)
  6. pow(x,y) - 返回值x的y次幂
  7. sqrt(n) - 返回非负数n的平方根
  8. pi() - 返回圆周率
  9. rand() - 返回在范围[0到1.0)内的随机浮点值
  10. truncate(n,d) - 保留数字n的d位小数并返回 - 直接截取

3. 日期函数

查询当前系统的日期 - select now();

  1. dayofweek(date) - 返回日期date是星期几(1=星期天,2=星期一,……7=星期六,odbc标准)

  2. weekday(date) - 返回日期date是星期几(0=星期一,1=星期二,……6= 星期天)

  3. year(date) - 返回date的年份(范围在1000到9999)

  4. month(date) - 返回date中的月份数值

  5. dayofmonth(date) - 返回date是一月中的第几日(在1到31范围内)

  6. hour(time) - 返回time的小时数(范围是0到23)

  7. minute(time) - 返回time的分钟数(范围是0到59)

  8. second(time) - 返回time的秒数(范围是0到59)

  9. period_add(p,n) - 增加n个月到时期p并返回(p的格式yymm或yyyymm)

    mysql>select period_add(‘202008’,3) ;

    注意:p可以是字符串,一定要满足一定的格式,或者可以直接使用date类型.

  10. period_diff(p1,p2) - 返回在时期p1和p2之间月数(p1和p2的格式yymm或yyyymm) p1-p2

  11. curdate() - 以’yyyy-mm-dd’或yyyymmdd格式返回当前日期值(根据返回值所处上下文是字符串或数字)

  12. curtime() - 以’hh:mm:ss’或hhmmss格式返回当前时间值(根据返回值所处上下文是字符串或数字)

  13. now() - 以’yyyy-mm-dd hh:mm:ss’或yyyymmddhhmmss格式返回当前日期时间(根据返回值所处上下文是字符串或数字)

  14. last_day(date) - date日期所在月的最后一天是什么时候

  15. datediff(d1,d2) - 两个日期d1,d2之间相差的天数

  16. timestampdiff(type,d1,d2) - type是YEAR,d1和d2相差的年份,MONTH,月份

    d1,d2 - yyyymmdd

3-1. 补充

  • date_add(date,interval expr type)

  • date_sub(date,interval expr type)

  • 关于type

    1
    2
    3
    4
    5
    6
    7
    ype:
    year
    month
    day
    hour
    minute
    second

#关联查询

关联查询就是多表查询

错误的写法

s_emp表中25rows,s_dept表中12rows

  1. 查询员工的名字以及这个员工的部门名称

    1
    2
    3
    4
    5
    select first_name,name from s_emp,s_dept;

    300 rows

    发生了笛卡尔积 - 错误的结果

纠正 - 传统的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
select first_name,name from s_emp,s_dept where dept_id=id;

ERROR 1052 (23000): Column 'id' in where clause is ambiguous

多张表中出现同名的列,需要对表取别名

正确的写法:
select e.first_name,d.name from s_emp e,s_dept d where e.dept_id=d.id;

-- 用来表示表与表之间的关系foreign key - 外键

-- 查询这个员工的first_name以及这个员工所在的区域名.
-- 一个区域对应多个部门,一个部门对应多个员工.
select e.first_name,r.name from s_emp e,s_dept d,s_region r
where d.region_id = r.id and e.dept_id = d.id;

for( ... s_emp e){
for(... s_dept d){
if(e.dept_id==d.id){
e+":"+d
}
}
}

join和on写法

如果一条sql语句涉及到的表比较多的话,传统的写法,就不能很直观的看出来哪个表和哪个表之间有直接的关系.

表 join 表 on 去除笛卡尔积

外键一定是出现在多的一方

on 多的一方的外键 = 一的一方主键

但是也不一定非要使用外键来去除笛卡尔积

规则: 能够用join连接的表之间必须要有直接的关系.

练习

  1. 找出查询员工的名字以及这个员工的部门名称

    1
    select e.first_name,d.name from s_emp e join s_dept d on e.dept_id = d.id;
  2. 找出’Asia’区域上的所有的员工

    1
    2
    3
    4
    5
    6
    select e.first_name,r.name from 
    s_emp e join s_dept d
    on e.dept_id = d.id
    join s_region r
    on d.region_id = r.id
    where r.name='Asia';
  3. 找出客户的id,名称,联系方式以及这个客户的订单信息

    ‘问题 - 15rows - [201,215]’

    1
    2
    3
    4
    select c.id,c.name,c.phone,o.* from s_customer c
    join s_ord o on c.id = o.customer_id;

    查询出来的结果缺了id=207和215俩个客户,因为这俩个客户没有订单,所以这俩个客户没有查询出来.

连接的方式

  1. 内连接 - [inner] join

    如果join左边的表在右边的表中如果不存记录,则不会被查询出来.

    1
    2
    select c.id,c.name,c.phone,o.id from s_customer c
    inner join s_ord o on c.id = o.customer_id;
  2. 左连接 - left join

    以左边的表为基准表.即使左边的表在右边的表中没有记录,那么左边表中的这条记录也要能够被查询出来

    查询客户和订单信息,即使这个客户没有订单,那么这个客户也要能够被查询出来

    1
    2
    select c.id,c.name,c.phone,o.id from s_customer c
    left join s_ord o on c.id = o.customer_id;
  3. 右连接 - right join

    以右边的表为基准表.即使右边的表在左边的表中没有记录,那么右边表中的这条记录也要能够被查询出来

    查询客户和订单信息,即使这个客户没有订单,那么这个客户也要能够被查询出来

    1
    2
    select c.id,c.name,c.phone,o.id from s_ord o
    right join s_customer c on c.id = o.customer_id;
  4. 交叉连接 - cross join - 不会使用的,产生的是一个错误的结果集 - 笛卡尔积

    1
    2
    select c.id,c.name,c.phone,o.id from s_customer c
    cross join s_ord o;
  5. 全连接 full outer join - mysql不支持

    全连接 = 左连接 + 右连接

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    drop table t_user;
    drop table t_info;
    create table t_user(
    id int primary key,
    name varchar(20)
    );
    insert into t_user values(1,'admin');
    insert into t_user values(2,'python');

    create table t_info(
    id int primary key,
    remark varchar(20),
    user_id int
    );
    insert into t_info values(1,'aaa',1);
    insert into t_info values(2,'bbb',3);

    -- 分析 - 左连接
    select u.*,info.* from t_user u left join t_info info on u.id = info.user_id;
    +----+--------+------+--------+---------+
    | id | name | id | remark | user_id |
    +----+--------+------+--------+---------+
    | 1 | admin | 1 | aaa | 1 |
    | 2 | python | NULL | NULL | NULL |
    +----+--------+------+--------+---------+

    -- 右连接
    select info.*,u.* from t_user u right join t_info info on u.id = info.user_id;
    +----+--------+---------+------+-------+
    | id | remark | user_id | id | name |
    +----+--------+---------+------+-------+
    | 1 | aaa | 1 | 1 | admin |
    | 2 | bbb | 3 | NULL | NULL |
    +----+--------+---------+------+-------+

    -- 全连接
    union - 两张表中的重复的结果保留了一条

    union all - 两张表中的重复的结果都会展示出来 , 并集

    select u.*,info.* from t_user u left join t_info info on u.id = info.user_id
    union all
    select u.*,info.* from t_user u right join t_info info on u.id = info.user_id;

    id | name | id | remark | user_id |
    +------+--------+------+--------+---------+
    | 1 | admin | 1 | aaa | 1 |
    | 2 | python | NULL | NULL | NULL |
    | 1 | admin | 1 | aaa | 1 |
    | NULL | NULL | 2 | bbb | 3 |
    +------+--------+------+--------+---------+


    select u.*,info.* from t_user u left join t_info info on u.id = info.user_id
    union
    select u.*,info.* from t_user u right join t_info info on u.id = info.user_id;

    +------+--------+------+--------+---------+
    | id | name | id | remark | user_id |
    +------+--------+------+--------+---------+
    | 1 | admin | 1 | aaa | 1 |
    | 2 | python | NULL | NULL | NULL |
    | NULL | NULL | 2 | bbb | 3 |
    +------+--------+------+--------+---------+

练习

查询员工的名称以及他对应的上司的名称,即使这个员工没有上司,那么也要查询出来 - 自关联

1
2
select e1.first_name 员工,e2.first_name 上司
from s_emp e1 left join s_emp e2 on e2.id = e1.manager_id;

多行函数

  1. count - 统计个数,null值不会统计,重复的数据,有一个算一个

    1
    2
    3
    4
    5
    6
    7
    8
    select count(*) from s_emp;

    select count(e.id) from s_emp e;

    select count(e.commission_pct) from s_emp;//5个

    -- 统计职称的个数,但是重复的职称只能算1个
    select count(distinct title) from s_emp;
  2. sum - 总和,忽略null值

  3. avg,max,min

分组查询

分组统计

语法

1
2
3
4
5
SELECT 语句
WHERE 语句
GROUP BY 语句
HAVING 语句
ORDER BY 语句

练习

  1. 统计每个部门的员工的个数 - 单表

    1
    select e.dept_id,count(e.id) from s_emp e group by e.dept_id;
  1. 统计部门的id,name以及这个部门的员工的个数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    -- group by + d.id  --- ok   id是主键列 - 唯一性
    select d.id,d.name,count(e.id) from s_emp e
    join s_dept d on e.dept_id = d.id group by d.id;


    select d.id,d.name,count(e.id) from s_emp e
    join s_dept d on e.dept_id = d.id group by d.name;
    ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'dy.d.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

    -- 推荐select后面跟的列(除了组函数/多行函数)应该和group by 跟的列保持一致
    select d.id,d.name,count(e.id) from s_emp e
    join s_dept d on e.dept_id = d.id group by d.id,d.name;
  2. 统计每个职称的人数

    1
    select title,count(*) from s_emp group by title;
  3. 统计区域id,区域名,以及这个区域上的员工的个数

    1
    2
    select r.id,r.name,count(e.id) from s_region r join s_dept d
    on d.region_id = r.id join s_emp e on e.dept_id = d.id group by r.id,r.name;
  4. 统计客户的id,name以及这个客户的订单的数量,没有订单,则显示0

    1
    2
    select c.id,c.name,count(o.id) from s_customer c left join s_ord o
    on o.customer_id = c.id group by c.id,c.name;
  5. 统计各年份入职的员工(年份,员工数量)

    1
    2
    select year(start_date),count(e.id) from s_emp e
    group by year(start_date);

having使用方式

having和where区别

  1. where条件筛选,但是分组之前,并且where不会跟组函数
  2. having分组之后的进一步过滤,使用组函数进行过滤的

练习

  1. 查询出平均工资超过1300的部门 - 部门id,name,平均工资

    1
    2
    select d.id,d.name,avg(e.salary) from s_dept d join s_emp e
    on d.id = e.dept_id group by d.id,d.name having avg(e.salary)>1300;
  2. 统计除了’Asia’区域之外的各个区域的id,name和区域上的人数[大于3个],最后再根据区域的id进行降序排列

    1
    2
    3
    4
    5
    6
    7
    select r.id,r.name,count(e.id) from s_region r 
    join s_dept d on r.id = d.region_id
    join s_emp e on e.dept_id = d.id
    where r.name<>'Asia'
    group by r.id,r.name
    having count(e.id)>3
    order by 1 desc;
  3. 查询没有订单的客户

    1
    2
    select c.id,c.name,count(o.id) from s_customer c
    left join s_ord o on c.id = o.customer_id group by c.id,c.name having count(o.id)=0;

子查询

子查询必须使用()括起来

  • 条件列
  • 查询列
  • 子查询的结果看做是”一张表”

练习

  1. 查找和Mark工资一样的员工.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    -- 分解 - mark工资 - 1450
    select salary from s_emp where first_name='Mark';

    -- 找出1450薪资的员工
    select first_name,salary from s_emp where salary = 1450 and first_name<>'Mark';

    -- 整合
    -- 子查询出现在条件部分
    select first_name,salary from s_emp where salary = (select salary from s_emp where first_name='Mark') and first_name<>'Mark';
  2. 统计每个部门的id,name以及部门的员工的个数

    1
    2
    3
    select d.id,d.name,(
    select count(e.id) from s_emp e where e.dept_id = d.id
    ) from s_dept d;

练习2

  1. 统计客户的id,name以及客户的订单数量
1
select c.id,c.name,(select count(o.id) from s_ord o where o.customer_id = c.id) from s_customer c;
  1. 找出部门平均工资高于公司平均工资的部门信息
1
select dept_id,avg(salary) from s_emp group by dept_id having avg(salary)>(select avg(salary) from s_emp);
  1. 找出与’Ben’同部门的员工信息
1
2
3
4
5
6
7
8
9
10
-- 方式一
select first_name,dept_id from s_emp where dept_id=(select dept_id from s_emp where first_name='Ben')
and first_name<>'Ben';

-- 方式二
select e.first_name,(select e.dept_id from s_emp e1 where e1.first_name='Ben' and e1.dept_id=e.dept_id) did from s_emp e where e.first_name<>'Ben';

-- 方式二整合 - 把子查询的结果作为一张表,必须要别名
select * from (select e.first_name,(select e.dept_id from s_emp e1 where e1.first_name='Ben' and e1.dept_id=e.dept_id) did from s_emp e where e.first_name<>'Ben') core_
where core_.did is not null;

总结

  1. 能用分组查询查询就不要使用子查询(嵌套越多,查询效率越低)

  2. 无关子查询(子查询可以独立运行)和相关子查询(子查询使用到了外部查询的东西)

    无关子查询>相关子查询

  3. 子查询会比分组查询更加灵活.

exists和not exists

1
2
3
4
5
6
7
SELECT 语句
WHERE exists (子查询)

规则: 不关心子查询查到的内容是什么,子查询是否能够查询出结果.
如果查询出结果,等同于where true

select id,first_name from s_emp where exists(select 1 from s_emp where 1=1);

找出和'Ben'同一个部门的员工

1
2
3
4
select e.first_name,e.dept_id from s_emp e 
where exists(
select 1 from s_emp e1 where e1.first_name='Ben' and e1.dept_id = e.dept_id
) and e.first_name<>'Ben';

练习

找出各个部门工资排名前二的员工

思路: 在’我’这个部门中,有人的工资比我高,但是数量不能超过1个

1
2
3
4
5
select e.id,e.salary,e.dept_id from s_emp e
where exists(
select 1 from s_emp e1 where e1.dept_id = e.dept_id and
e1.salary>e.salary having count(*)<=1
) order by 3;

练习

找出每个部门中工资最高的那个人

索引

view

  • create view

    1
    2
    3
    4
    5
    6
    7
    8
    9
    drop view student_view;
    create view student_view as select *from t_student where id%2=0;

    -- insert into t_student
    insert into t_student values(5,'java',20),(6,'javab',25),(7,'sad',20),(8,'jksd',56);

    -- update student_view
    update student_view set age=100 where id=8;

  • create complex view

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    -- 有关系的两张表(fk)
    drop view student_view2;
    create view student_view2 as select s.*,sc.c_id from t_student s left join t_sc sc on s.id=sc.s_id;

    -- update view
    update student_view2 set name='as' ;

    ERROR 1288 (HY000): The target table student_view2 of the UPDATE is not updatable


    -- 无关系的两张表
    drop table tt;
    drop table ta;
    create table tt(
    id int(7) primary key,
    tname varchar(20)
    );

    create table ta(
    taid int(7) primary key,
    tt_id int(7)
    );

    insert into tt values(1,'aa');
    insert into tt values(2,'aa');
    insert into tt values(3,'aa');
    insert into tt values(4,'aa');

    insert into ta values(1,1);
    insert into ta values(2,2);
    insert into ta values(3,3);
    insert into ta values(4,4);
    drop view t_view;
    create view t_view as select * from tt t join ta a on a.tt_id=t.id;
    update t_view set tname='as';

    create view t_view2 as select count(*)from tt t join ta a on a.tt_id=t.id;
    update t_view set count=10;

    视图作用:1.封装sql 2.屏蔽底层数据库结构 3.定制数据 4.安全性(grant)

    索引

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    -- function:提高查询效率
    底层:b+数 myisam(数据,索引分别存储),innodb(数据,索引合二为一)
    -- 主键列默认索引列
    show index from 表名

    -- 索引分类
    1.主键索引(primary key)
    2.unique
    3.全文索引(elasticsearch)
    4.复合索引
    create index index_name on tablename(column 1,column 2)
    5.普通索引
    create index index_name on tablename(colunmn)

    drop table index_test;
    create table index_test(
    id int(7) primary key auto_increment,
    a int(7),
    b int(7),
    c varchar(20),
    d varchar(20)
    );
    insert into index_test values(1,2,3,'20w','cc'),(null,22,93,'2s0','dd'),(null,22,33,'w20','ss'),(null,22,53,'201','hh'),(null,72,43,'200','gg'),(null,2,3,'20i','ff');

    drop index t_index;
    create index t_index on index_test(a,b,c);

    -- jude index valid
    explain +select....

    explain select * from index_test where a=2 and b=3 and c='20w';(ref) len=93
    +----+-------------+------------+------------+------+---------------+---------+---------+-------------------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+------------+------------+------+---------------+---------+---------+-------------------+------+----------+-------+
    | 1 | SIMPLE | index_test | NULL | ref | t_index | t_index | 93 | const,const,const | 1 | 100.00 | NULL |
    +----+-------------+------------+------------+------+---------------+---------+---------+-------------------+------+----------+-------+
    -- 索引失效*
    1.复合索引最左匹配原则
    explain select *from index_test where b=3 and c='20w';(All len=0)
    explain select *from index_test where a=3 and b=2;(ref len=10)
    explain select *from index_test where c='sd' and a=10 and b=2;(ref len=93),调优
    2.模糊匹配
    -- %在末尾生效
    explain select * from index_test where a=1 and b=2 and c like 's%';(ref len=93)
    %在前部分生效同(%?%)
    explain select * from index_test where a=1 and b=2 and c like '%s';(ref len=10)-- a,b走索引
    3.查询范围
    explain select * from index_test where a=1 and b>2 and c='21';(ref len=10)
    4.索引计算
    explain select* from index_test where id+1=1;(all)
    5.索引使用函数
    explain select* from index_test where abs(id)=1;(all)
    6.c查询数据超过百分之30(数据量足够大)
    7.is null and is not null
    (is null 不走索引,is not null 走索引)
    8.in and not in
    before 5.7 in(走索引) not in(不走)
    after 8.0 not in ( allow index)

    -- 索引的创建策略
    -- fit create index
    1.primary key or unique
    2.重复性少,经常被查询,但是更新少
    3.order by 列
    4.join on 列
    -- unfit create index
    1.null值列
    2.重复列
    3.更新频繁列

1
2
3
4
5
6
7
8
9
1.行锁(两线程不能同时修改某一行的值)
update XX set xx=xx where id=1;//必须是索引列,降低锁的粒度
2.表锁(非索引列为条件更新 会触发表锁)
所有的DML操作都会阻塞
3.页锁(介于行锁与表锁之间)
一页:由若干行组成的数据(B+树)
4.共享锁(select,类似Java的读锁)
-- select语句申请排他锁
select stock from xxx where id=1 for update;(悲观锁:干啥都悲观,总担心有人改数据,索性给全锁了)

索引底层原理

1
2
3
4
5
6
7
8
9
10
11
12
13
myisam(mai se meng),innoDB----->B+树


二叉查找树->二叉搜索树->B+树

二叉查找树(失衡)-->(产生)二叉平衡树(数据节点过多)->B树(储存一页数据,key和value都存)->b+树

B+树
1.非页节点存储key
2.数据存叶子,且相对有序
3.页与页(树节点)之间是双向链表
4.叶子内部数据是单向链表

B+树

– 聚簇索引

B+

聚簇索引与非聚簇索引

1
2
3
4
5
6
innoDB为引擎的表,表中的数据都会有主键,即使不主动声明,系统会创建一个隐式的主键.B+树的键值就是主键.这种**以主键为B+树索引而构成的B+树***索引,称为聚簇索引

非聚簇索引:以其他列值构建B+树的索引

-- 非聚集索引的叶子节点并不存储数据,而是存储对应的主键,因此还需要通过索引查数据,称之为回表
(注,若所查数据为主键列,或者该列,则不需要回表)

jusearch

1
select * from xx where id>=18 and id<41;

非聚簇索引查找图

x

myisam查找图

z

1
2
3
4
5
6
7
8
9
10
11
12
key - 索引列值,value - 数据行的物理地址.
innodb中索引和数据合并到一个文件中

myisam中索引和数据是单独的俩个文件,分别是索引文件和数据文件.

myisam中采用的是"非聚集的方式"

无论是聚簇索引还是非聚簇索引,查找方式是一样.

采用的也是B+树的结构**.只是叶节点中存储的是索引的列值以及该对应的行记录的地址.**

需要再根据行记录地址到表中进行定位[回表]
  • 函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    delimiter $$
    create function dy_add(a int ,b int) returns int
    begin
    return a+b;
    end $$
    delimiter ;

    select dy_add(5,2);


    delimiter $$
    create function dy_add(a int ,b int) returns int
    begin
    return a+b;
    end $$
    delimiter ;

    select dy_add(5,2);


    -- 触发器
    drop trigger customer_trriger;
    delimiter //
    create trigger customer_trriger
    before delete on s_customer
    for each row
    begin
    delete from s_ord where customer_id=old.id;
    end //

    delimiter ;

    -- 触发器
    drop trigger customer_trriger;
    delimiter //
    create trigger customer_trriger
    before insert on s_customer
    for each row
    begin
    if new.id<5
    select 'error';
    end //

    delimiter ;

mysql四种分区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
drop table t_user;
create table t_user(
id int(7) primary key comment '主键',
name varchar(20) not null unique comment '姓名',
birthday date default '2022-02-08'
);

drop table t_account;
create table t_account(
id int(7),
blance double(5,2),
u_id int(7) ,
constraint t_account_id_pk primary key(id),
constraint t_account_id_fk foreign key(u_id) references t_user(id)
);



drop table t_student;
drop table t_course;
drop table t_sc

create table t_student(
id int(7) primary key,
name varchar(20) unique not null,
age int(7) default 20
);

create table t_course(
id int(7) primary key,
name varchar(20) not null
);

create table t_sc(
id int(7),
s_id int (7),
c_id int(7),
constraint t_sc_id_pk primary key(id),
constraint t_sc_s_id_fk foreign key(s_id) references t_student(id),
constraint t_sc_c_id_fk foreign key(c_id) references t_course(id)
);

insert into t_student values(1,'tom',20),(2,'cca',22),(3,'kkp',21),(4,'see',null);
insert into t_course values(1,'math'),(2,'english'),(3,'chinses'),(4,'chesmatical');
insert into t_sc values(1,1,1),(2,2,3),(3,4,2),(4,3,4);

1
2
3
4
5
6
7
8
9
10
11
12
13
--测试表格及数据
drop table hashTest;
create table hashTest(
id int(7) primary key,
name varchar(20),
age int(7)
)PARTITION BY HASH(id) PARTITIONS 4;;
insert into hashTest values(1,'tom',20),(2,'cca',22),(3,'kkp',21),(4,'see',null),(5,'tom',20),(6,'cca',22),(7,'kkp',21),(8,'see',null),(9,'tom',20),(10,'cca',22),(11,'kkp',21),(12,'see',null);

--查询分区结构
SELECT PARTITION_NAME,PARTITION_METHOD,PARTITION_EXPRESSION,PARTITION_DESCRIPTION,TABLE_ROWS
FROM `information_schema`.`PARTITIONS`
WHERE TABLE_SCHEMA=SCHEMA() AND TABLE_NAME='hashTest';

hash分区 PARTITIONS=4(4个分区,根据取模确定不同id的占据区域)

1
2
3
4
5
6
7
8
9
+----------------+------------------+----------------------+-----------------------+------------+
| PARTITION_NAME | PARTITION_METHOD | PARTITION_EXPRESSION | PARTITION_DESCRIPTION | TABLE_ROWS |
+----------------+------------------+----------------------+-----------------------+------------+
| p0 | HASH | `id` | NULL | 3 |
| p1 | HASH | `id` | NULL | 3 |
| p2 | HASH | `id` | NULL | 3 |
| p3 | HASH | `id` | NULL | 3 |
+----------------+------------------+----------------------+-----------------------+------------+

Range分区 分成6个分区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
drop table hashTest;
create table hashTest(
id int(7) primary key,
name varchar(20),
age int(7)
)
PARTITION BY RANGE (id) PARTITIONS 6 (
PARTITION part0 VALUES LESS THAN (2) ,
PARTITION part1 VALUES LESS THAN (4) ,
PARTITION part2 VALUES LESS THAN (6) ,
PARTITION part3 VALUES LESS THAN (8) ,
PARTITION part4 VALUES LESS THAN (10),
PARTITION part5 VALUES LESS THAN (13)
);
insert into hashTest values(1,'tom',20),(2,'cca',22),(3,'kkp',21),(4,'see',null),(5,'tom',20),(6,'cca',22),(7,'kkp',21),(8,'see',null),(9,'tom',20),(10,'cca',22),(11,'kkp',21),(12,'see',null);

分区结果:
+----------------+------------------+----------------------+-----------------------+------------+
| PARTITION_NAME | PARTITION_METHOD | PARTITION_EXPRESSION | PARTITION_DESCRIPTION | TABLE_ROWS |
+----------------+------------------+----------------------+-----------------------+------------+
| part0 | RANGE | `id` | 2 | 1 |
| part1 | RANGE | `id` | 4 | 2 |
| part2 | RANGE | `id` | 6 | 2 |
| part3 | RANGE | `id` | 8 | 2 |
| part4 | RANGE | `id` | 10 | 2 |
| part5 | RANGE | `id` | 13 | 3 |
+----------------+------------------+----------------------+-----------------------+------------+

list分区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
drop table hashTest;
create table hashTest(
id int(7) primary key,
name varchar(20),
age int(7)
)

PARTITION BY LIST(id) (
PARTITION pNorth VALUES IN (1,2,3),
PARTITION pEast VALUES IN (5,6,7),
PARTITION pWest VALUES IN (4,8,9),
PARTITION pCentral VALUES IN (10,11,12)
);
insert into hashTest values(1,'tom',20),(2,'cca',22),(3,'kkp',21),(4,'see',null),(5,'tom',20),(6,'cca',22),(7,'kkp',21),(8,'see',null),(9,'tom',20),(10,'cca',22),(11,'kkp',21),(12,'see',null);
--分区结果:
+----------------+------------------+----------------------+-----------------------+------------+
| PARTITION_NAME | PARTITION_METHOD | PARTITION_EXPRESSION | PARTITION_DESCRIPTION | TABLE_ROWS |
+----------------+------------------+----------------------+-----------------------+------------+
| pCentral | LIST | `id` | 10,11,12 | 3 |
| pEast | LIST | `id` | 5,6,7 | 3 |
| pNorth | LIST | `id` | 1,2,3 | 3 |
| pWest | LIST | `id` | 4,8,9 | 3 |
+----------------+------------------+----------------------+-----------------------+------------+

key分区(和hash分区类似,其分区函数是由mysql内部确定的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
drop table hashTest;
create table hashTest(
id int(7) primary key,
name varchar(20),
age int(7)
)
PARTITION BY KEY()
PARTITIONS 2;
insert into hashTest values(1,'tom',20),(2,'cca',22),(3,'kkp',21),(4,'see',null),(5,'tom',20),(6,'cca',22),(7,'kkp',21),(8,'see',null),(9,'tom',20),(10,'cca',22),(11,'kkp',21),(12,'see',null);

--分区结果
+----------------+------------------+----------------------+-----------------------+------------+
| PARTITION_NAME | PARTITION_METHOD | PARTITION_EXPRESSION | PARTITION_DESCRIPTION | TABLE_ROWS |
+----------------+------------------+----------------------+-----------------------+------------+
| p0 | KEY | NULL | NULL | 6 |
| p1 | KEY | NULL | NULL | 6 |
+----------------+------------------+----------------------+-----------------------+------------+

集合

集合框架

就是一个内存中的”容器”,用来存储数据.实际开发中用来替代数组的使用

不同的集合框架底层使用到的数据结构不一样.

api包:java.util

  1. Collection[I]

    • List[I] - 有序,可重复

      • ArrayList[C] - “底层就是动态数组 - 内存中是连续的,有序的 - 便于使用下标来访问元素.但是增删效率略低”

      • LinkedList[C] - “底层是一个双向链表,访问比ArrayList慢,但是增删效率高”

        适合解决栈列和队列的业务 - 贪吃蛇

      • Vector[C] - 线程安全的,使用方式和ArrayList一致.

    • Set[I] - 无序,不可重复

      • HashSet[C] - “底层使用的哈希算法,底层就是HashMap”

      • SortedSet[I]

        • TreeSet[C] - “可以使用可比较接口或者比较器接口来实现排序的功能,但是仍然是不可重复的”

          “底层是TreeMap”

  2. Map[I] - 采用key-value键值对形式来存储数据

    • HashMap[C] - 针对key无序不可重复
    • Hashtable[C]
      • Properties[C] - .properties属性文件在内存中映射的那个对象

迭代器Iterator

作用 - 为了访问不同数据结构的集合,提供了一种统一的方式.

ArrayList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public Iterator<E> iterator() {
return new Itr();
}

private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;

public boolean hasNext() {
return cursor != size;
}

@SuppressWarnings("unchecked")
public E next() {
checkForComodification();//检测expectedModCount == modCount;
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
//集合存储的数据
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i]; // 数组对象[下标]
}
}

LinkedList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0;

/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1;

/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;

public boolean hasNext() {
return cursor != size();
}

public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
}

ArrayList练习

集合中添加N个图书对象,将图书名包含java的图书全部删除!

ArrayList扩容机制

  • 第一次调用add方法的时候,才会初始化数组elementData
  • 默认的数组的长度是10个
  • 扩容的倍数是1.5倍

ArrayList和Vector区别

要想回答这个问题,可以先把各种都讲特性,然后再从底层存储结构,线程安全,默认大小,扩容机制,迭代器,增删改查效率这几个方向入手。

  • 底层存储数据结构 - 本质上都是数组,Vector是使用队列[先进先出]来存储数据,但是本质仍然是数组.

  • 线程安全性 - 前者是线程不安全,后者是线程安全

  • 默认大小 - 俩者都是长度为10

  • 扩容机制 - 前者是1.5倍,后者默认是扩容2倍,可以扩容系数可以设置的.

  • ArrayListVector检索元素,由于是数组,时间复杂度是O(1),在集合的尾部插入或者删除是O(1),但是其他的地方增加,删除,都是O(n),因为涉及到了数组元素的移动

    ArrayList的删除和插入的效率一定会比LinkedList低吗? - 不一定,看是否操作的是集合的尾部

LinkedList

jdk8.0用到的是双向链表结构

链表肯定会比数组占用更多的内存

阔以模拟栈列和队列的业务

单向链表结构 - 必须从头节点开始

element - 真实的数据

next - 下一个节点的地址

单向循环结构

get(int index)底层

1
2
3
4
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}

内部类

1
2
3
4
5
6
7
8
9
10
11
12
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;

Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

核心方法node(index)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Node<E> node(int index) {
// index=2
// 1 2 3 4 5 6 = size = 6

if (index < (size >> 1)) { // 如果在中间位置的左边
Node<E> x = first; // 头节点开始遍历
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else { // 从尾部
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}

remove(int index)底层

1
2
3
4
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}

unlink(node(index));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
E unlink(Node<E> x) { // x-即将删除的节点
// assert x != null;
final E element = x.item;
final Node<E> next = x.next; // 下一个节点
final Node<E> prev = x.prev; //上一个节点

if (prev == null) { // 判断x是否为头节点
first = next; // 原来第二个节点设置成头节点
} else {
prev.next = next; //原来上一个节点的下一个地址应该指向原来这个节点的下一个节点
x.prev = null;//gc
}

if (next == null) { // 判断x是否为尾节点
last = prev; // 倒数第二个节点设置成尾结点
} else {
next.prev = prev;
x.next = null;//gc
}

x.item = null;//gc
size--; // 集合的长度
modCount++; // 并发修改
return element;//返回刚刚删除的元素
}

作业

  1. 括号匹配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ()
    []
    {}
    ()[]{}
    ([{}])
    [()]{}


    (]
    ([)]{}
    ​~~~java

    arr = ( [ { } ] )

    永远将第一个元素压入栈顶 push
    从arr的第2个位置开始遍历
    i下标对应的元素,和栈顶元素进行比较getFast();
    不匹配 - 继续压入栈顶
    匹配 - 弹出栈顶
    集合是否为空 isEmpty();

    ( [ ) ] { }

    ]
    )
    [
    (

    1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Purcase{ //购买类
private String brand; //品牌
private String name; //产品名
private double cost; // 费用

//构造,getter/setter,toString...
}

List<Purcase> list = new ArrayList<>();
Purcase p1 = new Purcase("宝洁","洗手粉",18.5);
Purcase p2 = new Purcase("联合利华","肥皂",4.5);
Purcase p3 = new Purcase("宝洁","牙膏",32.5);
Purcase p4 = new Purcase("宝洁","毛巾",14.5);
Purcase p5 = new Purcase("洁利","洗面奶",26.0);
Purcase p6 = new Purcase("好迪","洗发水",27.5);
Purcase p7 = new Purcase("多芬","沐浴露",38.5);
Purcase p8 = new Purcase("宝洁","洗洁精",3.4);

list.add(p1);
list.add(p2);
....

要求:写一个程序,打印出各品牌所花费的总费用①.
[可选,排好序后再打印输出,按花费总费用的降序排序②]

map => 统计每个随机数出现的次数. 15个随机数 1~5

“python java 123 15901121 dfdfd fdfd”

贪吃蛇的实现步骤

  1. 输出一个主界面

    蛇身(LinkedList->存储了N个Node) - 长度,蛇头,方向

    随机生成n个食物 - 使用HashSet来进行存储

    1
    2
    3
    4
    5
    6
    * * * * * * * * * * *
    * 0
    * # # #
    * 0 0
    *
    *
  1. 创建一个对象Node(int x,int y);

  2. 创建一个类SnakeGame

    3-1. 创建一个内部类Snake - 维护一个LinkedList

    3-2. Set foods = new HashSet<>();//存放食物

  1. 走一步算法

    4-1. 没有吃到食物

    1
    2
    3
    4
    1. 确定新的方向 - w a s d
    2. 新的坐标Node(i,j)
    3. addFirst - 将新的坐标放入到蛇头
    4. 判断新的坐标是否属于foods,如果不属于removeLast();//删除最后尾节点

    4-2. 吃到食物

HashSet

HashSet和HashMap => 存放数据的性能 - HashMap更高 - HashSet可能是计算整个对象的hashCode方法

1
2
3
4
5
6
7
8
9
10
11
12
Set<Book> set = new HashSet<>();

public HashSet() {
map = new HashMap<>();
}

set.add(10);

//set集合的value就是map集合的key
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

HashMap

线程不安全的.

用到的数据结构

jdk8.0-开始桶数组 + 单向链表 + 红黑树

桶 - 哈希桶

put底层源码剖析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HashMap<Integer,String> map = new HashMap<>();

//扩容因子 - 预扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;

public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

//put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

//针对ke进行一个hash算法 - 扰动函数
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 让哈希值和高16位进行异或运算
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
 transient Node<K,V>[] table;

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//①进来table和tab都是null
if ((tab = table) == null || (n = tab.length) == 0)
//①就会执行扩容操作 n = 16 tab初始化完毕
n = (tab = resize()).length;
//①hash - key的扰动函数得到哈希值
//(n - 1) & hash => 1. 下标肯定不会越界 2. 减少哈希冲突
//p = tab[哈希位置]肯定是null
if ((p = tab[i = (n - 1) & hash]) == null)
//①对这个位置进行赋值操作
//单向链表
tab[i] = newNode(hash, key, value, null);//最后一个参数是next
else {
Node<K,V> e; K k;
//p - 哈希冲突的位置上的Node
//比较node对象的hash值

//俩个确实是同一个对象
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;//e = p是旧的节点
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//OO - 不同对象的hashCode值,不一定
// 哈希碰撞了,但是并不是俩个相同的对象
//b. 相同对象equals肯定返回true
for (int binCount = 0; ; ++binCount) {
//把哈希冲突位置上的旧节点p的next挂载一个新的e
if ((e = p.next) == null) {//①肯定为空
p.next = newNode(hash, key, value, null);
//当链表长度开始>=8的时候,单向链表结构就会转换成红黑树
//删除操作 - 如果节点个数小于等于6的时候,开始转成链表结构
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//链表的节点可能和新插入的节点是一个相同对象

if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;//就是旧的节点的value
if (!onlyIfAbsent || oldValue == null)
e.value = value;//新的value赋值给旧节点的value
afterNodeAccess(e);
return oldValue;
}
}


++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

扩容resize()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
final Node<K,V>[] resize() {
//第一次进来oldTab就是null
Node<K,V>[] oldTab = table;
//oldCap 旧容量 = 0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//oldThr = 0
int oldThr = threshold;

//新的容量,新的扩容因子
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
//newCap = 16
newCap = DEFAULT_INITIAL_CAPACITY;
//newThr = 16*0.75f => 扩容的临界值
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//threshold = 16*0.75f
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//newTab 长度就是16
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}

put过程

排序

  1. 通过比较器接口java.util.Comparator
  2. 通过java.lang.Comparable[I]

面试题

  1. List和Set区别

    1
    2
    3
    4
    5
    1、List,Set都是继承自Collection接口
    2、List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。)
    3.Set和List对比:
    Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
    List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
  2. ArrayList和LinkedList区别

1
2
3
4
5
ArrayList基于动态数组实现的非线程安全的集合;LinkedList基于链表实现的非线程安全的集合。
对于随机index访问的get和set方法,一般ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止。
新增和删除元素,一般LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。
LinkedList集合不支持 高效的随机随机访问(RandomAccess)
ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
  1. ArrayList和Vector区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(1)同步性:
Vector是线程安全的,也就是说是它的方法之间是线程同步的,而ArrayList是线程序不安全的,它

的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程

安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编

写线程安全的代码。
(2)数据增长:
ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需

要增加ArrayList与Vector的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存

储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector默认增长为原
来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。

ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提

供设置增长空间的方法。
  1. HashSet和HashMap区别
1
2
3
4
(1)HashSet实现了Set接口, 仅存储对象; HashMap实现了 Map接口, 存储的是键值对.

(2)HashSet底层其实是用HashMap实现存储的, HashSet封装了一系列HashMap的方法. 依靠HashMap来存储元素值,(利用hashMap的key键进行存储), 而value值默认为Object对象. 所以HashSet也不允许出现重复值, 判断标准和HashMap判断标准相同, 两个元素的hashCode相等并且通过equals()方法返回true.
(3)当使用无参构造创建 HashSet对象时, 其实调用了 HashMap的无参构造创建了一个 HashMap对象, 所以 HashSet 的初始化容量也为16, 负载因子也为 0.75.
  1. TreeSet和TreeMap区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
TreeMap 和 TreeSet 是 Java Collection Framework 的两个重要成员,其中 TreeMap 是 Map 接口的常用实现类,而 TreeSet 是 Set 接口的常用实现类。虽然 TreeMap 和TreeSet 实现的接口规范不同,但 TreeSet 底层是通过 TreeMap 来实现的(如同HashSet底层是是通过HashMap来实现的一样),因此二者的实现方式完全一样。而 TreeMap 的实现就是红黑树算法

TreeSet和TreeMap的关系

  与HashSet完全类似,TreeSet里面绝大部分方法都市直接调用TreeMap方法来实现的。

相同点:

TreeMap和TreeSet都是非同步集合,因此他们不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()来实现同步
运行速度都要比Hash集合慢,他们内部对元素的操作时间复杂度为O(logN),而HashMap/HashSet则为O(1)。
TreeMap和TreeSet都是有序的集合,也就是说他们存储的值都是拍好序的。
不同点:

最主要的区别就是TreeSet和TreeMap分别实现Set和Map接口
TreeSet只存储一个对象,而TreeMap存储两个对象Key和Value(仅仅key对象有序)
TreeSet中不能有重复对象,而TreeMap中可以存在
TreeMap的底层采用红黑树的实现,完成数据有序的插入,排序。
  1. HashMap和Hashtable[线程安全]区别
1
2
3
4
5
6
7
线程安全性不同。HashMap线程不安全;Hashtable 中的方法是Synchronize的。
key、value是否允许null。HashMap的key和value都是可以是null,key只允许一个null;Hashtable的key和value都不可为null。
迭代器不同。HashMap的Iterator是fail-fast迭代器;Hashtable还使用了enumerator迭代器。
hash的计算方式不同。HashMap计算了hash值;Hashtable使用了key的hashCode方法。
默认初始大小和扩容方式不同。HashMap默认初始大小16,容量必须是2的整数次幂,扩容时将容量变为原来的2倍;Hashtable默认初始大小11,扩容时将容量变为原来的2倍加1。
是否有contains方法。HashMap没有contains方法;Hashtable包含contains方法,类似于containsValue。
父类不同。HashMap继承自AbstractMap;Hashtable继承自Dictionary。
  1. Collection和Collections[集合工具类]区别
1
2
3
4
5
6
1、java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。

List,Set,Queue接口都继承Collection。
直接实现该接口的类只有AbstractCollection类,该类也只是一个抽象类,提供了对集合类操作的一些基本实现。List和Set的具体实现类基本上都直接或间接的继承了该类。

2、java.util.Collections 是一个包装类。 它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等),大多数方法都是用来处理线性表的。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

TreeSet

  1. 利用通过java.lang.Comparable[I]

    对象去实现这个接口,并且重写comparaTo方法

  2. 利用构造器中可以传入一个比较器接口 => 更加灵活一点

    1
    2
    3
    public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
    }

泛型

泛型符号

?

1
2
List<?> list = new ArrayList<>();
list.add(null);//只能添加null

K,V - 键,值

T - 类型

E - 元素

用法

  1. 泛型类

  2. 泛型方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public <T> void find(T t){
    System.out.println("find:"+t);
    }

    public <T> T get(T t){
    System.out.println("find:"+t);
    return t;
    }

    public static <T> void find2(T t){
    System.out.println("find:"+t);
    }

泛型好处

1、类型安全

泛型的主要目标是提高Java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在非常高的层次上验证类型假设。没有泛型,这些假设就只存在于系统开发人员的头脑中。

通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误就可以在编译时被捕获了,而不是在运行时当作ClassCastException展示出来。将类型检查从运行时挪到编译时有助于Java开发人员更早、更容易地找到错误,并可提高程序的可靠性。

2、消除强制类型转换

泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。尽管减少强制类型转换可以提高使用泛型类的代码的累赞程度,但是声明泛型变量时却会带来相应的累赞程度。在简单的程序中使用一次泛型变量不会降低代码累赞程度。但是对于多次使用泛型变量的大型程序来说,则可以累积起来降低累赞程度。所以泛型消除了强制类型转换之后,会使得代码加清晰和筒洁。

3、更高的运行效率

在非泛型编程中,将筒单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。

4、潜在的性能收益

泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,Java系统开发人员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的JVM的优化带来可能。

应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Overridde
public int save(Student s){
Session session = HibernateUtil.getSession();

Transaction tx = session.beginTransaction();

tx.save(s);

tx.commit();
}

@Overridde
public Student getById(int id){
Session session = HibernateUtil.getSession();

Transaction tx = session.beginTransaction();

Student s = session.get(Student.class,id);

tx.commit();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//业务接口
public interface ITeacherDao{
Teacher getById(int id);

List<Teacher> loadAll();

int save(Teacher t);
}

//业务接口
public interface IStudentDao{
Student getById(int id);

List<Student> loadAll();

int save(Student s);
}

//考虑抽离出顶级的接口出来
public interface IBaseDao<T> {
T getById(int id);

List<T> loadAll();

int save(T t);
}

//顶级接口的实现类
public class BaseDaoImpl<T> implements IBaseDao<T> {
@Override
public T getById(int id) {
//具体的实现
return null;
}

@Override
public List<T> loadAll() {
//具体的实现
return null;
}

@Override
public int save(T t) {
//具体的实现
return 0;
}
}

//制定业务接口特有的功能
public interface IStudentDao extends IBaseDao<Student>{
void test();
}

//实现类
public class StudentDaoImpl extends BaseDaoImpl<Student> implements IStudentDao {
@Override
public void test() {

}
}

指定上限和下限

  1. 指定下限

    泛型参数可以是E,也可以是E的父类

    1
    ? super E
  2. 指定上限

    泛型参数可以是E,也可以是E的子类

    1
    ? extends E

泛型是没有多态的.

sql常用查询‘

SQL查询练习

  1. 查询id=1课程比id=2课程成绩高的学生的信息[学生id和学生的姓名]及课程分数

    1
    select s.id,s.sname,sc1.score from student s join sc sc1 on s.id=sc1.sid join sc sc2 on sc1.sid=sc2.sid where sc1.cid=1 and sc2.cid=2 and sc1.score >sc2.score; 
  2. 查询同时存在1 课程和2课程的情况

    1
    select s.id,s.sname,sc1.score from student s join sc sc1 on s.id=sc1.sid join sc sc2 on sc1.sid=sc2.sid where  sc2.cid =2 and sc1.cid=1 ;
  3. 查询存在1 课程但可能不存在2课程的情况

    1
    select s.id,s.sname,sc1.score from student s join sc sc1 on s.id=sc1.sid  where  sc1.cid=1 ;
  4. 查询不存在1课程但存在2课程的情况 (不存在时显示为 null)

    1
    select sc2.sid ,sc2.cid ,sc1.score 课程1,sc2.score 课程2 from (select *from sc where cid=1 ) sc1  RIGHT join (select *from sc where cid=2 ) sc2 on sc1.sid=sc2.sid where sc1.cid is null and sc2.cid=2;
  5. 查询平均成绩大于等于 60 分的同学的学生编号和学生姓名和平均成绩

    1
    select s.id,s.sname,avg(sc.score) from student s join sc sc on s.id=sc.sid group by s.id having avg(sc.score)>60 ; 
  6. 查询在 SC 表存在成绩的学生信息

1
select distinct s.id,s.sname from student s join sc sc on s.id=sc.sid where exists(select 1 from sc where score is not null);
  1. 查询所有同学的学生编号、学生姓名、选课总数、所有课程的总成绩

    1
    select s.id ,s.sname,count(sc.cid),sum(sc.score) from student s join sc sc where sc.sid=s.id group by s.id;
  2. 查询「李」姓老师的数量

    查询李老师的带过的学生的数量/授课的数量

    1
    2
    3
    方法一.select (select count( c.id) '授课数' from teacher t join course c on c.tid=t.id join sc sc on sc.cid=c.id  where t.tname like '李%') 授课数,(select count( sc.sid) '学生数' from teacher t join course c on c.tid=t.id join sc sc on sc.cid=c.id  where t.tname like '李%')  带过的学生;select (select count(distinct c.id) '授课数' from teacher t join course c on c.tid=t.id join sc sc on sc.cid=c.id  where t.tname like '李%') 授课数;

    方法2. select t.tname 老师姓名, count(distinct s.id) 学生数量,count(sc.cid) 授课数量 from student s join sc sc on sc.sid=s.id join course c on c.id=sc.cid join teacher t on t.id=c.tid where t.tname like '李%' group by t.id;
  3. 查询学过「张三」老师授课的同学的信息

1
2
3
4
方法一.select s.id,s.sname from student s join sc sc on sc.sid=s.id where sc.cid=
(select c.id from teacher t left join course c on c.tid=t.id where t.tname='张三')

方法二.select distinct s.* from student s join sc sc on sc.sid=s.id join course c on c.id=sc.cid join teacher t on t.id=c.tid where t.tname='张三' ;
  1. 查询没有学全所有课程的同学的信息
1
2
select s.id,s.sname from student s join sc sc on sc.sid=s.id  GROUP BY s.id HAVING count(*)!=(select count(*) from course);

  1. 查询至少有一门课与学号为1的同学所学相同的同学的信息
1
2
select s.id,s.sname from student s join sc sc on sc.sid=s.id where sc.cid in (select sc1.cid from sc sc1 where sc1.sid=1);

  1. 查询和id=1的同学学习的课程 完全相同的其他同学的信息(重要) - 非常重要,非常重要,非常重要,非常重要,非常重要!
1
select s.id,s.sname,s.age,s.sex from student s where s.id in(select sc.sid from sc where sc.sid<>1 and sc.cid in(select sc.cid from sc where sc.sid=1) group by sc.sid having count(*)=(select count(*) from sc where sc.sid=1));
  1. 查询没学过”张三”老师讲授的任一门课程的学生姓名

    1
    select distinct s.id ID ,s.sname 学生姓名 from sc sc2  right join student s on s.id= sc2.sid where s.id not in(select sc.sid from sc where sc.cid=(  select  c.id from teacher t left join  course c on c.tid=t.id where t.tname='张三'));
  2. 查询两门及其以上不及格课程的同学的学号,姓名及其平均成绩

1
select s.sname 姓名,s.id 学号,avg(sc.score) 平均分 from student  s left join sc sc on s.id=sc.sid where sc.score<60 group by s.id having count(*)>=2 ;
  1. 检索” 01 “课程分数小于 60,按分数降序排列的学生信息
1
select s.sname 姓名,s.id 学号,sc.score  from student  s left join sc sc on s.id=sc.sid where sc.score<60  and sc.cid='01' order by  3 desc;
  1. 按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩

    1
    select s.id ID,s.sname 姓名,coalesce(sc.cid,'未选') 课程编号, coalesce(sc.score,0) 分数,coalesce((select avg(sc2.score) from sc sc2 where sc2.sid=s.id),0) 平均分 from student  s left join sc sc on s.id=sc.sid order by 5 desc; 
  2. 查询各科成绩前三名的记录

    1
    select  s.id id,s.sname 姓名,sc.cid 课程号, sc.score 分数 from student  s  join sc sc on s.id=sc.sid where exists (select 1 from sc sc1 where sc.cid=sc1.cid and sc.score<sc1.score  having count(*)<3) order by 4 desc, 3 desc; 
  3. 查询每门课程被选修的学生数

    1
    select c.id 课程号,c.cname 课程名,count(sc.sid) 学生数 from sc  right join course c on c.id=sc.cid group by sc.cid;
  4. 查询出只选修两门课程的学生学号和姓名

    1
    select s.id,s.sname from student s join sc sc on sc.sid=s.id group by s.id having count(sc.cid)=2;
  5. 查询男生、女生人数

    1
    select s.sex 性别,count(s.sex) 人数 from student s group by s.sex;
  6. 查询名字中含有「风」字的学生信息

    1
    select * from student s where s.sname like '%风%';
  7. 查询同名同性学生名单,并统计同名同性人数

1
select * from student s where exists (select 1 from student s2 where s2.sname=s.sname having count(*)>=2);
  1. 查询 1990 年出生的学生名单
1
select *from student s where year(s.age)=1990; 
  1. 查询每门课程的平均成绩,结果按平均成绩降序排列,平均成绩相同时,按课程编号升序排列

    1
    select c.cname 课程名,sc.cid 课程id,avg(sc.score) 平均成绩 from course c left join sc sc on sc.cid=c.id group by c.id order by  3 desc,2;
  2. 查询平均成绩大于等于 85 的所有学生的学号、姓名和平均成绩

1
select s.id id,s.sname 姓名,coalesce(avg(sc.score),0) 平均成绩 from student s left join sc sc on s.id=sc.sid group by s.id having 平均成绩>85;
  1. 查询课程名称为「数学」,且分数低于 60 的学生姓名和分数

    1
    select s.id id,s.sname 姓名,sc.score 分数 from student s join sc sc on s.id=sc.sid join course c on c.id=sc.cid where c.cname='数学' and sc.score<60;
  2. 查询任何一门课程成绩在 70 分以上的姓名、课程名称和分数

    1
    select s.id id,s.sname 姓名 ,sc.cid 课程名 ,sc.score 分数 from student s left join sc sc on s.id=sc.sid where sc.score>70;
  3. 查询存在不及格的课程

    1
    select sc.cid ,c.cname from sc sc join course c on c.id=sc.cid where sc.score<60 group by sc.cid ;
  4. 查询课程编号为 01 且课程成绩在 80 分以上的学生的学号和姓名

    1
    select s.id,s.sname from student s join sc sc on sc.sid=s.id where sc.cid=1 and sc.score>80 group by s.id,s.sname;
  5. 求每门课程的学生人数

    1
    select sc.cid ,count(sc.sid) from sc group by sc.cid ;
  6. 假设成绩不重复,查询选修「张三」老师所授课程的学生中,成绩最高的学生信息及其成绩

    1
    select s.id,s.sname,sc.score from student s join sc on sc.sid=s.id join course c on c.id=sc.cid join teacher t on t.id=c.tid and t.tname ='张三' where exists (select * from sc sc2 where sc.cid=sc2.cid and sc.score<sc2.score having count(*)<1) ;
  7. 假设成绩有重复的情况下,查询选修「张三」老师所授课程的学生中,成绩最高的学生信息及其成绩

1
select * from student s join sc on sc.sid=s.id join course c on c.id=sc.cid join teacher t on t.id=c.tid and t.tname ='张三' where exists (select * from sc sc2 where sc.cid=sc2.cid and sc.score>=sc2.score having count(*)=(select count(sc3.cid) from sc sc3 where sc3.cid=sc.cid)) ;
  1. 查询不同课程成绩相同的学生的学生编号、课程编号、学生成绩

    1
    2
    select s.id,s.sname,sc.cid,sc.score from student s join sc sc on sc.sid=s.id join sc sc2 on sc.sid=sc2.sid where sc.score=sc2.score and
    sc.cid!=sc2.cid;
  2. 统计每门课程的学生选修人数(超过 5 人的课程才统计)

1
select sc.cid,count(*) from sc group by sc.cid having count(*)>5
  1. 检索至少选修两门课程的学生学号
1
select s.id from student s join sc on sc.sid=s.id  group by s.id having count(sc.cid)>=2;
  1. 查询选修了全部课程的学生信息
1
select s.id from student s join sc on sc.sid=s.id  group by s.id having count(sc.cid)=(select count(c.id) from course c );