linux驱动笔记

加载模块失败

insmod xxx.ko报错 insmod: ERROR: could not insert module xxx.ko: Unknown symbol in module

1
2
3
modinfo xxx.ko | grep depends #查看依赖的模块(lsmod 命令可以查看内核中已经的模块)
modprobe <缺少的依赖模块>
insmod xxx.ko

linux网络3_gre

拓扑

TOPO

配置gre

1
2
3
4
5
6
#routerB
remoteip=30.1.1.2
localip=30.1.1.1
greip=10.10.10.1
grepeerip=10.10.10.2
grename=gre1
1
2
3
4
5
6
#routerA
remoteip=30.1.1.1
localip=30.1.1.2
greip=10.10.10.2
grepeerip=10.10.10.1
grename=gre1
1
2
3
4
5
6
7
8
9
#routerB and routerA
lsmod|grep ip_gre || modprobe ip_gre
ping -c1 ${remoteip} || { echo "make sure the network is ok plz"; exit 1; }
ip addr show ${grename} && { echo "${grename} is already exist"; exit 1; }

ip tunnel add ${grename} mode gre remote ${remoteip} local ${localip} ikey 1 okey 1 ttl 255;
ip addr add ${greip} dev ${grename} peer ${grepeerip};
ip link set ${grename} up; #mtu 1400
ip addr show ${grename};

测试隧道连接

routerB ping -I 10.10.10.1 10.10.10.2 -c2 通路
routerA ping -I 10.10.10.2 10.10.10.1 -c2 通路

配置路由表

routeB ip route add 10.2.1.0/24 via 10.10.10.2
routeA ip route add 10.1.1.0/24 via 10.10.10.1

测试私网和隧道

PC2 ping 10.2.1.2 通路
PC1 ping 10.1.1.2 通路

1
2
3
4
5
6
7
# root @ routerA in / [11:37:33]
$ tcpdump -nvvvi gre1
tcpdump: listening on gre1, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
12:31:30.309857 IP (tos 0x0, ttl 63, id 59102, offset 0, flags [DF], proto ICMP (1), length 84)
10.10.10.1 > 10.2.1.2: ICMP echo request, id 6, seq 1, length 64
12:31:30.310258 IP (tos 0x0, ttl 63, id 17143, offset 0, flags [none], proto ICMP (1), length 84)
10.2.1.2 > 10.10.10.1: ICMP echo reply, id 6, seq 1, length 64

删除隧道

1
2
3
4
5
6
7
8
ip link set gre1 down
ip tunnel del gre1
``

## 卸载模块

```sh
rmmod ip_gre

TODO 创建多个隧道

两台设备之间建立多条gre通道

额外问题

  1. 查看内核是否支持gre

    1
    2
    grep GRE /boot/config-$(uname -r)
    sysctl net.netfilter.nf_conntrack_helper=1
  2. kernel: conntrack: generic helper won't handle protocol 47. Please consider loading the specific helper module.

    1
    modprobe nf_conntrack_proto_gre
  3. 若需要grep nf_nat_proto_gre || modprobe nf_nat_proto_gre

  4. GRE是将一个数据包封装到另一个数据包中,因此你可能会遇到GRE的数据报大于网络接口所设定的数据包最大尺寸的情况。解决这种问题的方法是在隧道接口上配置ip tcp adjust-mss 1436。另外,虽然GRE并不支持加密,但是你可以通过tunnel key命令在隧道的两头各设置一个密钥。这个密钥其实就是一个明文的密码。或者使用gre over ipsec,那样就比较复杂了再另说。

  5. GRE隧道没有状态控制,可能隧道的一端已经关闭,而另一端仍然开启。这一问题的解决方案就是在隧道两端开启keepalive数据包。它可以让隧道一端定时向另一端发送keepalive数据,确认端口保持开启状态。如果隧道的某一端没有按时收到keepalive数据,那么这一侧的隧道端口 也会关闭。

调试

代码打印

kernel/net/ipv4/ip_gre.c

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
static int ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi)
{
struct net *net = dev_net(skb->dev);
struct metadata_dst *tun_dst = NULL;
struct ip_tunnel_net *itn;
const struct iphdr *iph;
struct ip_tunnel *tunnel;
char *p = NULL;
char sip[18]={0};
char dip[18]={0};

pr_info("ipgre_rcv\n");
if (tpi->proto == htons(ETH_P_TEB)){
pr_info("tpi->proto == htons(ETH_P_TEB)\n");
itn = net_generic(net, gre_tap_net_id);
}
else{
pr_info("tpi->proto != htons(ETH_P_TEB)\n");
itn = net_generic(net, ipgre_net_id);
}

iph = ip_hdr(skb);
if(iph != NULL){
p = (char *) &iph->saddr;
sprintf(sip, "%d.%d.%d.%d", (p[0] & 255), (p[1] & 255), (p[2] & 255), (p[3] & 255));
p = (char *) &iph->daddr;
sprintf(dip, "%d.%d.%d.%d", (p[0] & 255), (p[1] & 255), (p[2] & 255), (p[3] & 255));
pr_info("sip:%s -> dip:%s\n", sip, dip);
}
pr_info("skb->dev->ifindex:%d tpi->flags%d tpi->key%d\n", skb->dev->ifindex, tpi->flags, tpi->key);
tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, tpi->flags,
iph->saddr, iph->daddr, tpi->key);

if (tunnel) {
pr_info("ip_tunnel_lookup return not NULL\n");
skb_pop_mac_header(skb);
if (tunnel->collect_md) {
__be16 flags;
__be64 tun_id;

pr_info("tunnel->collect_md != 0\n");
flags = tpi->flags & (TUNNEL_CSUM | TUNNEL_KEY);
tun_id = key_to_tunnel_id(tpi->key);
tun_dst = ip_tun_rx_dst(skb, flags, tun_id, 0);
if (!tun_dst){
pr_info("ip_tun_rx_dst return NULL\n");
return PACKET_REJECT;
}
}

pr_info("ip_tunnel_rcv\n");
ip_tunnel_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error);
return PACKET_RCVD;
}
pr_info("ip_tunnel_lookup return NULL\n");
return PACKET_REJECT;
}

static int gre_rcv(struct sk_buff *skb)
{
struct tnl_ptk_info tpi;
bool csum_err = false;
int hdr_len;

pr_info("gre_rcv\n");
#ifdef CONFIG_NET_IPGRE_BROADCAST
pr_info("gre_rcv is multicast?\n");
if (ipv4_is_multicast(ip_hdr(skb)->daddr)) {
pr_info("gre_rcv is multicast yes\n");
/* Looped back packet, drop it! */
if (rt_is_output_route(skb_rtable(skb)))
goto drop;
}
#endif

hdr_len = parse_gre_header(skb, &tpi, &csum_err);
pr_info("parse_gre_header\n");
if (hdr_len < 0) {
pr_info("hdr_len < 0\n");
goto drop;
}
if (iptunnel_pull_header(skb, hdr_len, tpi.proto) < 0) {
pr_info("iptunnel_pull_header < 0\n");
goto drop;
}

if (ipgre_rcv(skb, &tpi) == PACKET_RCVD) {
pr_info("ipgre_rcv == %d\n", PACKET_RCVD);
return 0;
}

icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
pr_info("icmp_send\n");
drop:
pr_info("drop\n");
kfree_skb(skb);
return 0;
}

kernel/net/ipv4/ip_tunnel.c

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/* Fallback tunnel: no source, no destination, no key, no options

Tunnel hash table:
We require exact key match i.e. if a key is present in packet
it will match only tunnel with the same key; if it is not present,
it will match only keyless tunnel.

All keysless packets, if not matched configured keyless tunnels
will match fallback tunnel.
Given src, dst and key, find appropriate for input tunnel.
*/
struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,
int link, __be16 flags,
__be32 remote, __be32 local,
__be32 key)
{
unsigned int hash;
struct ip_tunnel *t, *cand = NULL;
struct hlist_head *head;

pr_info("ip_tunnel_lookup\n");

hash = ip_tunnel_hash(key, remote);
pr_info("ip_tunnel_hash(%d, %d)-1>hash:%d\n", key, remote, hash);

head = &itn->tunnels[hash];
pr_info("1head:%lu\n", (long unsigned int)head);

hlist_for_each_entry_rcu(t, head, hash_node) {
pr_info("1local%d, saddr%d, remote%d, daddr%d, flags%x, IFF_UP%x\n",
local, t->parms.iph.saddr, remote, t->parms.iph.daddr, t->dev->flags, IFF_UP);
if (local != t->parms.iph.saddr ||
remote != t->parms.iph.daddr ||
!(t->dev->flags & IFF_UP)){
pr_info("local%d != t->parms.iph.saddr%d || remote%d != t->parms.iph.daddr%d || !(t->dev->flags%x & IFF_UP%x) continue\n",
local, t->parms.iph.saddr, remote, t->parms.iph.daddr, t->dev->flags, IFF_UP);
continue;
}

if (!ip_tunnel_key_match(&t->parms, flags, key)){
pr_info("!ip_tunnel_key_match(&t->parms, %x, %d) continue\n", flags, key);
continue;
}

if (t->parms.link == link){
pr_info("t->parms.link%d == link%d return t\n", t->parms.link, link);
return t;
}
else{
pr_info("t->parms.link%d != link%d cand=t\n", t->parms.link, link);
cand = t;
}
}

hlist_for_each_entry_rcu(t, head, hash_node) {
pr_info("remote:%d, daddr:%d, saddr:%d, flags%x&%x:%d\n", remote, t->parms.iph.daddr, t->parms.iph.saddr, t->dev->flags, IFF_UP, (t->dev->flags & IFF_UP));
if (remote != t->parms.iph.daddr ||
t->parms.iph.saddr != 0 ||
!(t->dev->flags & IFF_UP)){
pr_info("remote%d != t->parms.iph.daddr%d || t->parms.iph.saddr%d != 0 || !(t->dev->flags%x & IFF_UP%x) continue\n",
remote, t->parms.iph.daddr, t->parms.iph.saddr, t->dev->flags, IFF_UP);
continue;
}

if (!ip_tunnel_key_match(&t->parms, flags, key)){
pr_info("!ip_tunnel_key_match(&t->parms, %x, %d) continue\n", flags, key);
continue;
}

if (t->parms.link == link){
pr_info("t->parms.link%d == link%d return t\n", t->parms.link, link);
return t;
}
else if (!cand){
pr_info("t->parms.link%d != link%d cand=t\n", t->parms.link, link);
cand = t;
}
}

hash = ip_tunnel_hash(key, 0);
pr_info("ip_tunnel_hash(%d, 0)-2>hash:%d\n", remote, hash);
head = &itn->tunnels[hash];
pr_info("2head:%lu\n", (long unsigned int)head);

hlist_for_each_entry_rcu(t, head, hash_node) {
pr_info("local:%d, saddr:%d, daddr:%d\n", local, t->parms.iph.saddr, t->parms.iph.daddr);
if ((local != t->parms.iph.saddr || t->parms.iph.daddr != 0) &&
(local != t->parms.iph.daddr || !ipv4_is_multicast(local))){
pr_info("(local%d != t->parms.iph.saddr%d || t->parms.iph.daddr%d != 0) && (local%d != t->parms.iph.daddr%d || !ipv4_is_multicast(local)) continue\n",
local, t->parms.iph.saddr, t->parms.iph.daddr, local, t->parms.iph.daddr);
continue;
}

if (!(t->dev->flags & IFF_UP)){
pr_info("!(t->dev->flags%x & IFF_UP%x) continue\n", t->dev->flags, IFF_UP);
continue;
}

if (!ip_tunnel_key_match(&t->parms, flags, key)){
pr_info("!ip_tunnel_key_match(&t->parms, %x, %d) continue\n", flags, key);
continue;
}

if (t->parms.link == link){
pr_info("t->parms.link%d == link%d return t\n", t->parms.link, link);
return t;
}
else if (!cand){
pr_info("t->parms.link%d != link%d cand=t\n", t->parms.link, link);
cand = t;
}
}

if (flags & TUNNEL_NO_KEY){
pr_info("flags%x & TUNNEL_NO_KEY%x goto skip_key_lookup\n", flags, TUNNEL_NO_KEY);
goto skip_key_lookup;
}

hlist_for_each_entry_rcu(t, head, hash_node) {
if (t->parms.i_key != key ||
t->parms.iph.saddr != 0 ||
t->parms.iph.daddr != 0 ||
!(t->dev->flags & IFF_UP)){
pr_info("t->parms.i_key%d != key%d || t->parms.iph.saddr%d != 0 || t->parms.iph.daddr%d != 0 || !(t->dev->flags%x & IFF_UP%x) continue\n",
t->parms.i_key, key, t->parms.iph.saddr, t->parms.iph.daddr, t->dev->flags, IFF_UP);
continue;
}

if (t->parms.link == link){
pr_info("t->parms.link%d == link%d return t\n", t->parms.link, link);
return t;
}
else if (!cand){
pr_info("t->parms.link%d != link%d cand=t\n", t->parms.link, link);
cand = t;
}
}

skip_key_lookup:
if (cand){
pr_info("cand not NULL return cand\n");
return cand;
}

t = rcu_dereference(itn->collect_md_tun);
pr_info("t = rcu_dereference(itn->collect_md_tun)\n");
if (t){
pr_info("t not NULL return t\n");
return t;
}

if (itn->fb_tunnel_dev && itn->fb_tunnel_dev->flags & IFF_UP){
pr_info("itn->fb_tunnel_dev && itn->fb_tunnel_dev->flags & IFF_UP return netdev_priv(itn->fb_tunnel_dev);\n");
return netdev_priv(itn->fb_tunnel_dev);
}

return NULL;
}
EXPORT_SYMBOL_GPL(ip_tunnel_lookup);

编译加载模块

1
2
3
4
modinfo ip_gre
modprobe gre
#make M=net/ipv4
make modules SUBDIRS=net/ipv4;rmmod ip_gre;rmmod ip_tunnel;insmod net/ipv4/ip_tunnel.ko;insmod net/ipv4/ip_gre.ko

设备直连时

隧道是正常通信的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[ 2990.916244] output info dev:eth1 protocol:0x0800, 20.1.1.1->20.1.1.2 protocol:47 ip csum:578(1400) len:92
[ 3056.971477] input info dev:eth1 protocol:0x0800, 20.1.1.2->20.1.1.1 protocol:47 ip csum:bacb(47819) len:92
[ 3056.971494] ip_gre: gre_rcv
[ 3056.971496] ip_gre: gre_rcv is multicast?
[ 3056.971499] ip_gre: parse_gre_header
[ 3056.971502] ip_gre: ipgre_rcv
[ 3056.971504] ip_gre: tpi->proto != htons(ETH_P_TEB)
[ 3056.971508] ip_gre: sip:20.1.1.2 -> dip:20.1.1.1
[ 3056.971511] ip_gre: skb->dev->ifindex:3 tpi->flags1024 tpi->key33554432
[ 3056.971513] ip_tunnel: ip_tunnel_lookup
[ 3056.971516] ip_tunnel: ip_tunnel_hash(33554432, 33620244)-1>hash:73
[ 3056.971519] ip_tunnel: 1head:3957364008
[ 3056.971523] ip_tunnel: 1local16843028, saddr16843028, remote33620244, daddr33620244, flags91, IFF_UP1
[ 3056.971525] ip_tunnel: t->parms.link3 == link3 return t
[ 3056.971527] ip_gre: ip_tunnel_lookup return not NULL
[ 3056.971530] ip_gre: ip_tunnel_rcv
[ 3056.971533] ip_gre: ipgre_rcv == 0
[ 3056.971561] input info dev:gre2 protocol:0x0800, 10.10.10.4->10.10.10.3 protocol:1 ip csum:8ca5(36005) len:64, type8
[ 3056.971612] output info dev:gre2 protocol:0x0800, 10.10.10.3->10.10.10.4 protocol:1 ip csum:7f7a(32634) len:64, type0
[ 3056.971632] output info dev:eth1 protocol:0x0800, 20.1.1.1->20.1.1.2 protocol:47 ip csum:f158(61784) len:92

测试跨网段时

隧道是不通的

TOPO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[ 3498.496802] input info dev:eth1 protocol:0x0800, 20.1.1.2->20.1.1.1 protocol:47 ip csum:709a(28826) len:92
[ 3498.496810] ip_gre: gre_rcv
[ 3498.496847] ip_gre: gre_rcv is multicast?
[ 3498.497130] ip_gre: parse_gre_header
[ 3498.497136] ip_gre: ipgre_rcv
[ 3498.497141] ip_gre: tpi->proto != htons(ETH_P_TEB)
[ 3498.497149] ip_gre: sip:20.1.1.2 -> dip:20.1.1.1
[ 3498.497155] ip_gre: skb->dev->ifindex:3 tpi->flags1024 tpi->key16777216
[ 3498.497160] ip_tunnel: ip_tunnel_lookup
[ 3498.497166] ip_tunnel: ip_tunnel_hash(16777216, 33620244)-1>hash:75
[ 3498.497171] ip_tunnel: 1head:3957811504
[ 3498.497177] ip_tunnel: ip_tunnel_hash(33620244, 0)-2>hash:0
[ 3498.497183] ip_tunnel: 2head:3957811204
[ 3498.497189] ip_tunnel: local:16843028, saddr:0, daddr:0
[ 3498.497196] ip_tunnel: (local16843028 != t->parms.iph.saddr0 || t->parms.iph.daddr0 != 0) && (local16843028 != t->parms.iph.daddr0 || !ipv4_is_multicast(local)) continue
[ 3498.497204] ip_tunnel: t->parms.i_key0 != key16777216 || t->parms.iph.saddr0 != 0 || t->parms.iph.daddr0 != 0 || !(t->dev->flags80 & IFF_UP1) continue
[ 3498.497209] ip_tunnel: t = rcu_dereference(itn->collect_md_tun)
[ 3498.497214] ip_gre: ip_tunnel_lookup return NULL
[ 3498.497219] ip_gre: icmp_send
[ 3498.497223] ip_gre: drop

结论

gre用于同网段的A类地址(即公网地址)的两个设备之间,跨网段是无法建立隧道的(除非关闭RouterB的NAT)。

linux网络2_组播

安装pimd

1
apt install pimd

拓扑

TOPO

配置ospf

参考《linux网络1_ospf》分别对routeA和routeB进行ospf功能配置,或者设置默认路由,使得组播源、接收端1、接收端2这三者之间能两两相互ping通

搭建组播源与接收端

参考《vlc组播测试Server及Client使用》

配置RouterA

1
2
3
4
5
6
7
8
9
10
11
# root @ routerA in / [14:27:40]
$ grep -v "^#" /etc/pimd.conf
phyint eth2 disable #重要:关掉不参与组播功能的端口,免得影响组播
phyint eth3 disable #重要:关掉不参与组播功能的端口,免得影响组播
phyint eth4 disable #重要:关掉不参与组播功能的端口,免得影响组播
bsr-candidate priority 5
rp-candidate time 30 priority 20 #动态选举汇聚点RP路由器(也可设置为静态RP,但所有路由必须设置同一个)
spt-threshold packets 0 interval 100

# root @ routerA in / [14:27:47]
$ systemctl restart pimd.service

配置RouterB

1
2
3
4
5
6
7
8
9
10
11
# root @ routerB in / [14:33:40]
$ grep -v "^#" /etc/pimd.conf
phyint eth2 disable #重要:关掉不参与组播功能的端口,免得影响组播
phyint eth3 disable #重要:关掉不参与组播功能的端口,免得影响组播
phyint eth4 disable #重要:关掉不参与组播功能的端口,免得影响组播
bsr-candidate priority 5
rp-candidate time 30 priority 20 #动态选举汇聚点RP路由器(也可设置为静态RP,但所有路由必须设置同一个)
spt-threshold packets 0 interval 100

# root @ routerB in / [14:33:47]
$ systemctl restart pimd.service

调试

1
2
$ journalctl -u pimd.service
...

查看结果

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
# root @ routerA in / [15:04:54]
$ ip mroute show to 239.255.255.249
(10.2.1.2, 239.255.255.249) Iif: eth0 Oifs: eth1

# root @ routerA in / [15:05:01]
$ pimd --show-routes
Virtual Interface Table ======================================================
Vif Local Address Subnet Thresh Flags Neighbors
--- --------------- ------------------ ------ --------- -----------------
0 10.2.1.1 10.2.1/24 1 DR NO-NBR
1 30.1.1.2 30.1.1/24 1 DR PIM 30.1.1.1
2 192.168.255.1 192.168.255 1 DISABLED
3 10.2.1.1 register_vif0 1

Vif SSM Group Sources

Multicast Routing Table ======================================================
----------------------------------- (*,G) ------------------------------------
Source Group RP Address Flags
--------------- --------------- --------------- ---------------------------
INADDR_ANY 239.255.255.249 30.1.1.2 WC RP
Joined oifs: ....
Pruned oifs: ....
Leaves oifs: .l..
Asserted oifs: ....
Outgoing oifs: .o..
Incoming : ...I

TIMERS: Entry JP RS Assert VIFS: 0 1 2 3
0 45 0 0 0 0 0 0
----------------------------------- (S,G) ------------------------------------
Source Group RP Address Flags
--------------- --------------- --------------- ---------------------------
10.2.1.2 239.255.255.249 30.1.1.2 SPT CACHE SG
Joined oifs: ....
Pruned oifs: ....
Leaves oifs: .l..
Asserted oifs: ....
Outgoing oifs: .o..
Incoming : I...

TIMERS: Entry JP RS Assert VIFS: 0 1 2 3
175 30 0 0 0 0 0 0
...

routerA的组播路由表出现了 (10.2.1.2, 239.255.255.249) Iif: eth0 Oifs: eth1

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
# root @ routerB in / [15:08:16]
$ ip mroute show to 239.255.255.249
(30.1.1.2, 239.255.255.249) Iif: eth1 Oifs: eth0

# root @ routerB in / [15:10:21]
$ pimd --show-routes
Virtual Interface Table ======================================================
Vif Local Address Subnet Thresh Flags Neighbors
--- --------------- ------------------ ------ --------- -----------------
0 10.1.1.1 10.1.1/24 1 DR NO-NBR
1 30.1.1.1 30.1.1/24 1 PIM 30.1.1.2
2 192.168.20.1 192.168.20 1 DISABLED
3 192.168.30.1 192.168.30 1 DISABLED
4 10.1.1.1 register_vif0 1

Vif SSM Group Sources

Multicast Routing Table ======================================================
----------------------------------- (*,G) ------------------------------------
Source Group RP Address Flags
--------------- --------------- --------------- ---------------------------
INADDR_ANY 239.255.255.249 30.1.1.2 WC RP
Joined oifs: .....
Pruned oifs: .....
Leaves oifs: l....
Asserted oifs: .....
Outgoing oifs: o....
Incoming : .I...

TIMERS: Entry JP RS Assert VIFS: 0 1 2 3 4
0 40 0 0 0 0 0 0 0
----------------------------------- (S,G) ------------------------------------
Source Group RP Address Flags
--------------- --------------- --------------- ---------------------------
30.1.1.2 239.255.255.249 30.1.1.2 SPT CACHE SG
Joined oifs: .....
Pruned oifs: .....
Leaves oifs: l....
Asserted oifs: .....
Outgoing oifs: o....
Incoming : .I...

TIMERS: Entry JP RS Assert VIFS: 0 1 2 3 4
190 45 0 0 0 0 0 0 0

routerB的组播路由表出现了 (30.1.1.2, 239.255.255.249) Iif: eth1 Oifs: eth0

备注:可以看到routerA和routerB的汇聚点选举结果为 RP Address:30.1.1.2

测试组播通路

接收端1和接收端2上面的vlc都能播放udp://@239.255.255.249:1249的视频。

linux网络1_ospf

安装quagga

1
apt install quagga

拓扑

TOPO

配置RouterA

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
# root @ routerA in / [11:37:11]
$ grep -v "^#" /etc/quagga/debian.conf
vtysh_enable=yes
zebra_options=" --daemon -A 127.0.0.1"
ospfd_options=" --daemon -A 127.0.0.1"
watchquagga_enable=yes
watchquagga_options=(--daemon)

# root @ routerA in / [11:37:21]
$ grep -v "^#" /etc/quagga/zebra.conf
hostname Router_A #各路由不一样
password testpassword
enable password testpassword

# root @ routerA in / [11:37:26]
$ grep -v "^#" /etc/quagga/ospfd.conf
hostname Router_A #跟zebra.conf一样
password testpassword
enable password testpassword
router ospf
ospf router-id 30.1.1.2 #自身的ip,连接邻居路由
network 10.2.1.0/24 area 1
network 30.1.1.0/24 area 0 #邻居相同网段 area也需要是0
debug ospf event
log file /usr/local/etc/ospfd.log

# root @ routerA in / [11:37:32]
$ grep -v "^#" /etc/quagga/daemons
zebra=yes
ospfd=yes

# root @ routerA in / [11:37:39]
$ systemctl restart quagga.service

配置RouterB

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
# root @ routerB in / [11:39:11]
$ grep -v "^#" /etc/quagga/debian.conf
vtysh_enable=yes
zebra_options=" --daemon -A 127.0.0.1"
ospfd_options=" --daemon -A 127.0.0.1"
watchquagga_enable=yes
watchquagga_options=(--daemon)

# root @ routerB in / [11:39:18]
$ grep -v "^#" /etc/quagga/zebra.conf
hostname Router_B #各路由不一样
password testpassword
enable password testpassword

# root @ routerB in / [11:39:24]
$ grep -v "^#" /etc/quagga/ospfd.conf
hostname Router_B #跟zebra.conf一样
password testpassword
enable password testpassword
router ospf
ospf router-id 30.1.1.1 #自身的ip,连接邻居路由
network 10.1.1.0/24 area 1
network 30.1.1.0/24 area 0 #邻居相同网段 area也需要是0
debug ospf event
log file /usr/local/etc/ospfd.log

# root @ routerB in / [11:39:31]
$ grep -v "^#" /etc/quagga/daemons
zebra=yes
ospfd=yes

# root @ routerB in / [11:39:37]
$ systemctl restart quagga.service

调试

1
2
3
4
$ journalctl -u quagga.service
...
Sep 27 11:25:29 ubuntu watchquagga[12328]: ospfd state -> up : connect succeeded
Sep 27 11:25:29 ubuntu watchquagga[12328]: zebra state -> up : connect succeeded

查看结果

1
2
3
4
5
6
# root @ routerA in / [11:41:51]
$ ip route
10.1.1.0/24 via 30.1.1.1 dev eth1 proto zebra metric 20
10.2.1.0/24 dev eth0 proto kernel scope link src 10.2.1.1
30.1.1.0/24 dev eth1 proto kernel scope link src 30.1.1.2
192.168.255.0/24 dev eth3 proto kernel scope link src 192.168.255.1

routerA的路由表出现了 10.1.1.0/24 via 30.1.1.1 dev eth1 proto zebra metric 20

1
2
3
4
5
6
7
# root @ routerB in / [11:39:49]
$ ip route
10.1.1.0/24 dev eth0 proto kernel scope link src 10.1.1.1 linkdown
10.2.1.0/24 via 30.1.1.2 dev eth1 proto zebra metric 20
30.1.1.0/24 dev eth1 proto kernel scope link src 30.1.1.1
192.168.20.0/24 dev eth2 proto kernel scope link src 192.168.20.1 linkdown
192.168.30.0/24 dev eth3 proto kernel scope link src 192.168.30.1 linkdown

routerB的路由表出现了 10.2.1.0/24 via 30.1.1.2 dev eth1 proto zebra metric 20

测试网络通路

PC1 ping PC2

1
2
3
4
5
6
7
# zh @ li in ~ [11:44:36]
$ ping 10.1.1.2
PING 10.1.1.2 (10.1.1.2) 56(84) 字节的数据。
64 字节,来自 10.1.1.1: icmp_seq=1 ttl=64 时间=0.607 毫秒
64 字节,来自 10.1.1.1: icmp_seq=2 ttl=64 时间=0.564 毫秒
64 字节,来自 10.1.1.1: icmp_seq=3 ttl=64 时间=0.602 毫秒
^C

PC2 ping PC1

1
2
3
4
5
6
7
# zh @ li in ~ [11:44:36]
$ ping 10.2.1.2
PING 10.2.1.2 (10.2.1.2) 56(84) 字节的数据。
64 字节,来自 10.2.1.1: icmp_seq=1 ttl=64 时间=0.647 毫秒
64 字节,来自 10.2.1.1: icmp_seq=2 ttl=64 时间=0.534 毫秒
64 字节,来自 10.2.1.1: icmp_seq=3 ttl=64 时间=0.402 毫秒
^C

打开命令行配置

telnet localhost 2601 密码在/etc/quagga/zebra.conf
telnet localhost 2604 密码在/etc/quagga/ospfd.conf

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
# root @ routerA in / [13:53:38] C:1
$ telnet localhost 2601
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

Hello, this is Quagga (version 0.99.24.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.


User Access Verification

Password:
Router_A> show ip ospf neighbor
...
Router_A> exit
Connection closed by foreign host.

# root @ routerA in / [13:54:03] C:1
$ telnet localhost 2604
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

Hello, this is Quagga (version 0.99.24.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.


User Access Verification

Password:
Router_A>
Router_A> exit
Connection closed by foreign host.

# root @ routerA in / [13:54:19] C:1
$

VLC组播测试Server及Client使用

VLC组播测试Server及Client使用

VLC是一种开源的媒体播放器,但同时又可以用于组播测试。可以用VLC搭建组播Server,也可以用VLC作客户端接受组播流。

一. 使用VLC搭建组播Server

1. 修改TTL

注意:默认VLC图形方式下打出的组播流其IP报头中的TTL=1

  1. 菜单 工具 偏好设置
  2. 简明偏好设置 显示设置:全部(左下角)
  3. 高级偏好设置 串流输出 访问输出 跳数限制(TTL):64 多播输出接口(若无法指定,请在win10网络管理内禁用其他的接口)

2. 设置组播server

  1. 菜单 媒体 流
  2. 网络 删除URL
  3. 文件 添加 选择文件 打开/加载/确定 串流 下一步
  4. 勾选在本地显示 新目标:UDP(legacy) 添加
  5. 地址(239.10.1.1) 端口(1234) 下一步
  6. 勾选激活转码 配置文件(Video - H.264 + MP3(MP4))
  7. 取消串流所有基本流,生成的串流输出字符串(默认) 流

注意:默认UDP的组播流输出接口是根据系统本地的路由表来选择的。比如:

如果输出组播的IP地址为239.10.1.1,那么根据上述路由表组播流将发往192.168.56.1那个接口。因为该接口的路由metric相对较小(276)

二. VLC客户端接受组播流。

  1. 菜单 媒体 打开网络串流
  2. 网络 URL(udp://@239.10.1.1:1234) 播放

三. 命令行操作方式(windows操作系统)——UDP(legacy)实现方式。

1. Server侧打流。

  • 先通过添加组播路由条目的方式指定组播流的输出接口
    1
    route add 239.0.0.0 mask 255.0.0.0 192.168.1.100
  • 使用VLC打组播流
    1
    vlc -vvv D:\bak\others\组播测试片源\hello.mp4 --sout udp:239.10.1.1:1234 --ttl 10

    2. Client侧接受流。

    1
    vlc udp://@239.10.1.1:1234

    四. 命令行操作方式(windows操作系统)——RTP实现方式。

1. Server侧打流

1
vlc -vvv hello.mp4 --sout "#transcode{vcodec=h264,vb=0,scale=0,acodec=mpga,ab=128,channels=2,samplerate=44100}:rtp{dst=239.10.1.1,port=4321,mux=ts,ttl=20}"

2. Client侧接受流

1
vlc rtp://@239.10.1.1:4321

五. 命令行操作方式(windows操作系统)——RTSP实现方式。

1. Server侧打流

1
vlc -vvv sample1.avi --sout "#transcode{vcodec=h264,vb=0,scale=0,acodec=mpga,ab=128,channels=2,samplerate=44100}:rtp{sdp=rtsp://:8554/test}"

2. Client侧接受流

1
vlc rtsp://172.16.1.1:8554/test

六. 命令行操作方式(windows操作系统)——HTTP实现方式。

1. Server侧打流

1
vlc -vvv sample1.avi --sout "#transcode{vcodec=h264,vb=0,scale=0,acodec=mpga,ab=128,channels=2,samplerate=44100}:http{mux=ffmpeg{mux=flv},dst=:8080/test}"

2. Client侧接受流

1
vlc http://172.16.1.1:8080/test

pacman笔记

更新系统

在 Archlinux 中,使用一条命令即可对整个系统进行更新

1
pacman -Syyu

如果你已经使用pacman -Sy将本地的包数据库与远程的仓库进行了同步,也可以只执行:pacman -Su

安装包

1
2
3
4
5
pacman -S 包名 #例如,执行 pacman -S firefox 将安装 Firefox。你也可以同时安装多个包,只需以空格分隔包名即可。
pacman -Sy 包名 #与上面命令不同的是,该命令将在同步包数据库后再执行安装。
pacman -Sv 包名 #在显示一些操作信息后执行安装。
pacman -U #安装本地包,其扩展名为 pkg.tar.gz。
pacman -U http://www.example.com/repo/example.pkg.tar.xz 安装一个远程包(不在 pacman 配置的源里面)

删除包

1
2
3
4
5
6
pacman -R 包名 #该命令将只删除包,保留其全部已经安装的依赖关系
pacman -Rs 包名 #在删除包的同时,删除其所有没有被其他已安装软件包使用的依赖关系
pacman -Rsc 包名 #在删除包的同时,删除所有依赖这个软件包的程序
pacman -Rd 包名 #在删除包时不检查依赖。
pacman -Rns 包名 #删除包和它所有的依赖,还删掉它的全局配置文件
pacman -R $(sudo pacman -Qdtq) #查询孤儿软件并删除掉他们

搜索包

1
2
3
4
5
6
7
8
9
pacman -Ss 关键字 #在仓库中搜索含关键字的包。
pacman -Qi 包名 #查看有关包的详尽信息。
pacman -Ql 包名 #列出该包的文件。
pacman -Q #显示出所有软件 sudo pacman -Q | wc -l 查询数量
pacman -Qe #查询所有自己安装的软件
pacman -Qeq #查询所有自己安装的软件,只显示包名,不显示版本号等
pacman -Qs <pkg_name> #查询本地安装的所有带<pkg_name>的软件
pacman -Qdt #查询所有孤儿软件,不再被需要的。
pacman -Qdtq #查询所有不再被依赖的包名

其他用法

1
2
3
pacman -Sw 包名 #只下载包,不安装。
pacman -Sc #清理未安装的包文件,包文件位于 /var/cache/pacman/pkg/ 目录。
pacman -Scc #删除/var目录下的缓存文件

kernel进程读写文件

在kernel驱动程序中读写文件,没有标准库可用,需要用kernel提供的一些函数

1
2
3
4
5
6
7
8
9
10
#include <linux/fs.h>
#include <asm/uaccess.h>

filp_open()
filp_close()
vfs_read()
vfs_write()
set_fs()
get_fs()
...

打开文件

1
2
3
4
5
//filename: 表明要打开或创建文件的名称(包括路径部分)。需要先挂载文件系统再挂载驱动才能打开文件。
//open_mode: 打开方式,其取值与标准库中的open相应参数类似,可以取O_CREAT,O_RDWR,O_RDONLY等。
//mode: 创建文件时的默认权限
//返回strcut file*指针,供后继函数操作使用,返回值用IS_ERR(file)检测是否打开出错
strcut file* filp_open(const char* filename, int open_mode, int mode);

读写文件

1
2
3
4
//参数loff_t * pos所指向的值要初始化,表明从文件的什么地方开始读写。
ssize_t vfs_read(struct file* filp, char __user* buffer, size_t len, loff_t* pos);

ssize_t vfs_write(struct file* filp, const char __user* buffer, size_t len, loff_t* pos);

这两个函数的第二个参数buffer,前面有__user修饰符,这就要求buffer指针应该指向用户空间的内存,如果传入kernel空间的指针,就会返回失败-EFAULT

但在Kernel中,我们一般不容易生成用户空间的指针,或者不方便独立使用用户空间内存。想要使这两个读写函数使用kernel空间的buffer指针也能正确工作,就需要使用set_fs()宏/函数

1
2
3
4
5
6
//获取kernel当前对内存地址检查的处理方式,默认情况下是USER_DS,即对用户空间地址检查并做变换。
mm_segment_t fs get_fs(void);

//改变kernel对内存地址检查的处理方式
//fs取值:USER_DS,KERNEL_DS 分别代表用户空间和内核空间
void set_fs(mm_segment_t fs);

要在对内存地址做检查变换的函数中使用内核空间地址,可以用set_fs(KERNEL_DS)进行设置

1
2
3
4
5
6
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);

vfs_read() or vfs_write() //与内存有关的操作

set_fs(old_fs);

还有一些其它的内核函数参数也用__user修饰的参数,在kernel中需要用kernel空间的内存代替时,都可以使用类似办法。

关闭文件

1
2
//第二个参数一般传递NULL值,也有用current->files作为实参的
int filp_close(struct file*filp, fl_owner_t id);

使用以上函数的其它注意点:

  1. 其实Linux Kernel组成员不赞成在kernel中独立的读写文件(这样做可能会影响到策略和安全问题),对内核需要的文件内容,最好由应用层配合完成。
  2. 在可加载的kernel module中使用这种方式读写文件可能使模块加载失败,原因是内核可能没有EXPORT你所需要的所有这些函数。
  3. 分析以上某些函数的参数可以看出,这些函数的正确运行需要依赖于进程环境,因此,有些函数不能在’中断的handle’或’Kernel中不属于任可进程的代码’中执行,否则可能出现崩溃,要避免这种情况发生,可以在kernel中创建内核线程,将这些函数放在线程环境下执行(创建内核线程的方式请参数kernel_thread()函数)。

vnc

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
84
85
86
87
88
89
# zh @ li in ~ [6:24:36]
$ vncpasswd
Password:
Verify:
Would you like to enter a view-only password (y/n)? n

# zh @ li in ~ [6:25:59]
$ sudo cp /lib/systemd/system/vncserver@.service /etc/systemd/system/vncserver@\:1.service
[sudo] zh 的密码:

# zh @ li in ~ [6:28:23]
$ sudo vim /etc/systemd/system/vncserver@:1.service

# zh @ li in ~ [6:30:15]
$ sudo vim /etc/systemd/system/vncserver@:1.service
没有 systemd.Install 的手册页条目
^[没有 systemd.Install 的手册页条目
没有 systemd.Install 的手册页条目
^[^[%
# zh @ li in ~ [6:39:48]
$ systemctl start vncserver@:1.service
Failed to start vncserver@:1.service: Access denied
See system logs and 'systemctl status vncserver@:1.service' for details.

# zh @ li in ~ [6:40:44] C:4
$ sudo systemctl start vncserver@:1.service
[sudo] zh 的密码:

# zh @ li in ~ [6:40:51]
$ sudo systemctl enable vncserver@:1.service
Created symlink /etc/systemd/system/multi-user.target.wants/vncserver@:1.service → /etc/systemd/system/vncserver@:1.service.

# zh @ li in ~ [6:41:17]
$ ls ~/.vnc
passwd

# zh @ li in ~ [6:42:42]
$ vncserver -list

usage: vncserver <display>


# zh @ li in ~ [6:43:31] C:2
$ vncserver -geometry 1920x1080

usage: vncserver <display>


# zh @ li in ~ [6:44:18] C:2
$ ls ~/.vnc
passwd

# zh @ li in ~ [6:44:27]
$ vncserver 1

usage: vncserver <display>


# zh @ li in ~ [6:44:41] C:2
$ vncserver display

usage: vncserver <display>


# zh @ li in ~ [6:44:49] C:2
$ vncserver --help

usage: vncserver <display>


# zh @ li in ~ [6:44:57] C:2
$ netstat -pantu|grep vnc
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)

# zh @ li in ~ [6:45:37] C:1
$ sudo netstat -pantu|grep vnc

# zh @ li in ~ [6:45:48] C:1
$ vncserver

usage: vncserver <display>


# zh @ li in ~ [6:48:16] C:2
$ vncserver :1
Using desktop session i3-with-shmlog

New 'li:1 (zh)' desktop is li:1

mysql笔记

mysql

Source

学习过程参考视频教程:java1234网站《一头扎进mysql》
源码及视频网盘:链接

0前言

数据库(Database)是按照数据结构来组织、存储和管理数据的仓库。

我们也可以将数据存储在文件中,但是在文件中读写数据速度相对较慢。

使用关系型数据库管理系统(RDBMS)来存储和管理的大数据量。所谓的关系型数据库,是建立在关系模型基础上的数据库,借助于集合代数等数学概念和方法来处理数据库中的数据。

RDBMS 即关系数据库管理系统(Relational Database Management System)的特点:

  • 1.数据以表格的形式出现
  • 2.每行为各种记录名称
  • 3.每列为记录名称所对应的数据域
  • 4.许多的行和列组成一张表单
  • 5.若干的表单组成database

1 mysql的数据类型简介

1.1 整数类型

整数类型 字节数 无符号(unsigned)范围 有符号(signed)范围
TINYINT 1 0~255 -128~127
SMALLINT 2 0~65535 -32768~32767
MEDIUMINT 3 0~16777215 -8388608~8388607
INT 4 0~4294967295 -2147483648~2147483647
INTEGER 4 0~4294967295 -2147483648~2147483647
BIGINT 8 0~18446744073709551615 -9223372036854775808~9223372036854775807

INTEGER和INT是相同的,一般定义主键使用INT即可。默认是有符号情况,无符号要特别说明。

1.2 浮点数和定点数类型

浮点型在数据库中存放的是近似值,而定点类型在数据库中存放的是精确值。

数据类型精度有无符号的范围

  • float(m,d)单精度浮点型,(4字节),m总位数,d小数位10^38 级别
  • double(m,d)双精度浮点型,(8字节),m总位数,d小数位10^308 级别
  • decimal(m,d)m<65 是总个数,d<30 且 d<m 是小数位$1

float32位中,有1位符号位,8位指数位,23位尾数位,实际的范围是-2128—2127,约为-3.4E38—3.4E38

double64位中,1位符号位,11位指数位,52位尾数位,范围约是-1.7E308—1.7E308

float和double精度问题:参考博客

1.3 日期与时间类型

在这里插入图片描述

常用 DATA,TIME,DATATIME

1.4 字符串类型

数据类型大小用途

  • CHAR 0-255 字节定长字符串(实际分配的空间是固定的)
  • VARCHAR 0-65535 字节可变长字符串(实际分配空间根据字符存决定)
  • TEXT 0-65535 字节文本数据(TINYTEXT\LONGTEXT\MEDIUMTEXT)
  • ENUM枚举类型(只能取一个)
  • SET集合类型(可以取多个)

1.5 二进制类型

在这里插入图片描述

存一些图片视频,一般存在web目录下读取快,存数据库慢。

2 数据库(database)的基本操作

2.1 数据库组件

  • Mysql Shell:自带的一个高级的mysql命令行工具。
  • MySQL Workbench:为MySQL设计的ER/数据库建模工具,实现可视化管理数据库。
  • Mysql Command Line Client:MySQL的DOS界面,客户端工具。
  • Mysql Command Line Client-Unicode同上。
  • Mysqladmin:运维和管理工具

退出Mysql:quit

2.2 显示数据库

显示所有数据库:show databases;

2.3 创建数据库

创建数据库:create database Name; Name最好有一定规范,如db_book1

2.4 删除数据库

删除数据库:drop database Name;

3 数据表(Tables)的基本操作

表是数据库存储数据的基本单位,一个表包含若干字段或记录。

3.1 创建表

1
2
3
4
5
6
CREATE TABLE table_name (
属性名 数据类型 [完整性约束条件],
属性名 数据类型 [完整性约束条件],
.....
属性名 数据类型 [完整性约束条件],
);

表的常见约束条件:

在这里插入图片描述

创建图书类别表:

1
2
3
4
5
6
use db_book;
create table t_bookType(
id int primary key auto_increment,
bookTypeName varchar(20),
bookTypeDesc varchar(200)
);

创建图书类型表:

并将图书的类别ID,做外键关联使用。

1
2
3
4
5
6
7
8
create table t_book(
id int primary key auto_increment,
bookname varchar(20),
author varchar(10),
price decimal(6,2),
bookTypeId int,
constraint `fk` foreign key(`bookTypeId`) references `t_booktype`(`id`)
);

若之前表已经存在了,则:

1
2
3
4
5
6
create table Connect(
con DOUBLE,
nectid INT
);

alter table Connect add constraint `fkid` foreign key(`nectid`) references `t_booktype`(`id`)

注意这里的符号是数字1左侧英文输入状态下的 ` 不是单引号 ’

3.2 查看表结构

查看基本表结构:DESCRIBE/DESC 表名;

详细结构:SHOW create table 表名;

显示本数据库内的所有表:SHOW tables

1
2
3
desc t_booktype;
show create table t_booktype;
show tables;

3.3 修改表结构

在这里插入图片描述

修改表名: alter table t_book rename t_book2;

修改t_book表的bookName字段名为bookName2,数据类型为 varchar(20): alter table t_book change bookName bookName2 varchar(20);

增加字段名: alter table t_book add authorage varchar(10);

带约束增加

1
2
3
4
#加到最前面一列
alter table t_book add testField varchar(10) first;
#加到指定属性后面
alter table t_book add testField varchar(10) after author;

删除字段名: alter table t_book drop testField;

3.4 删除表

drop table 表名;

有依赖关系的时候先删掉子表再删主表。

4 表内容的相关操作

先创建一个数据表:

1
2
3
4
5
6
7
create table t_student(
id int primary key auto_increment,
stuName varchar(60),
age int,
sex varchar(30),
gradeName varchar(60)
);

插入数据: 注意里面是使用的单引号

1
2
3
4
5
6
7
8
insert into db_book.t_student (id,stuName,age,gender,gradeName) values (1,'张一',18,'男','大一');
insert into db_book.t_student (id,stuName,age,gender,gradeName) values (2,'张二',19,'男','大二');
insert into db_book.t_student (id,stuName,age,gender,gradeName) values (3,'张三',20,'男','大三');
insert into db_book.t_student (id,stuName,age,gender,gradeName) values (4,'张四',21,'男','大四');
insert into db_book.t_student (id,stuName,age,gender,gradeName) values (5,'张五',22,'男','大一');
insert into db_book.t_student (id,stuName,age,gender,gradeName) values (6,'张六',23,'男','大二');
insert into db_book.t_student (id,stuName,age,gender,gradeName) values (7,'张七',18,'男','大三');
insert into db_book.t_student (id,stuName,age,gender,gradeName) values (8,'张八',20,'男','大四');

或者后面多组:

1
insert into db_book.t_student (id,stuName,age,gender,gradeName) values (9,'张九',20,'女','大二'),(10,'张十',20,'女','大四');

给表取别名:表名 表别名

select * from t_book where id=1; 等价 select * from t_book tb where tb.id=1;

字段取别名:属性 [AS] 属性别名

select tb.bookName [AS] tbNa from t_book tb where tb.id=1;

4.1 查询表内字段

4.1.1 查询所有字段:

1
2
3
4
#select 字段1,字段2,字段3... from 表名;
select id,stuName,age,gender,gradeName from t_student; #顺序可调
#select * from 表名;
select * from t_student;

4.1.2 查询指定字段:

1
2
select 指定字段1,指定字段3... from 表名;
select stuName,gender from t_student;

4.1.3 where条件查询

1
2
3
#select 字段1,字段2... from 表名 where 条件表达式;
select * from t_student where id=1;
select * from t_student where age>19;

指定条件删除字段:

1
delete from t_student where id=4;

4.1.4 带in关键字查询

1
2
3
#select 字段1,字段2,字段3... from 表名 where 字段 [NOT] IN (元素1.元素2…);
select * from t_student where age in (18,19); #只能取这两个,不是范围
select * from t_student where age not in (18,19);

4.1.5 带between and的范围查询

1
2
3
#select 字段1,字段2,字段3... from 表名 where 字段 [NOT] BETWEEN 取值a AND 取值b;
select * from t_student where age between 18 and 20; #18 19 20 都可以取,是范围
select * from t_student where age not between 18 and 20

4.1.6 带like模糊查询

1
2
3
4
5
6
7
#select 字段1,字段2,字段3... from 表名 where 字段 [NOT] LIKE ‘字符串’;
#"%"代表任意字符 '_'代表单个字符,前后都可

select * from t_student where stuName like '张三';
select * from t_student where stuName like '张三%'; #带'张三'开头的
select * from t_student where stuName like '张三_'; #_只能占一个位。多个就写多个_ _
select * from t_student where stuName like '%张三%';

4.1.7 空值查询

1
2
3
4
#select 字段1,字段2,字段3... from 表名 where 字段 IS [NOT] NULL;

select * from t_student where age is NULL;
select * from t_student where age is NOT NULL;

4.1.8 带and的多条件查询

1
2
3
4
#select 字段1,字段2,字段3... from 表名 where 条件表达式1 AND 条件表达式2 AND 条件表达式3 AND ...
#注意末尾是没有分号的

select * from t_student where age=20 and gradeName='大四'

4.1.9 带or的多条件查询

1
2
3
4
#select 字段1,字段2,字段3... from 表名 where 条件表达式1 OR 条件表达式2 OR 条件表达式3 OR ...
#或,语句末尾也是无分号;

select * from t_student where age=20 or gradeName='大四'

4.1.10 distinct去重复查询

1
2
3
#select distinct 字段名 from 表;

select distinct age from t_student;

4.1.11 对查询结果排序

1
2
3
4
#select 字段1,字段2,... from 表名 order by 属性名 [ASC|DESC]
#默认升序

select age from t_student order by age asc;

4.1.12 group by 分组查询

在这里插入图片描述

一般不单独使用,常和聚合函数一起使用,一定注意使用的时候各个符号。

1
2
3
4
5
6
7
8
9
10
11
#按age分组,看每个(岁数)组有哪些stuName,GROUP_CONCAT把所有stuName拼接
select age,GROUP_CONCAT(stuName) from t_student group by age;

#按age分组,看每个(岁数)组有多少个stuName
select age,COUNT(age) from t_student group by age;

#按age分组,看每个(岁数)组有多少个stuName,只输出统计总数大于1
select age,COUNT(age) from t_student group by age having count(age)>1;

#按age分组,看每个(岁数)组有多少个stuName,多加一行计算总和,若是文本为所有元素拼接
select age,COUNT(age) from t_student group by age with rollup;

4.1.13 limit 分页查询

1
2
3
4
#select 字段1,字段2... from 表名 limit 初始位置,记录数,

select * from t_student LIMIT 0,5; #从第0条开始查5
select * from t_student LIMIT 5,5; #从第5条开始查5

4.2 使用聚合函数查询(统计)

先创建一个相关数据表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
create table t_garde(
int primary key auto_increment,
stuName varchar(60),
course varchar(50),
score int
);

insert into t_garde (id,stuName,course,score) values
(1,'张一一','语文',88),
(2,'张一一','数学',89),
(3,'张一一','英语',90),
(4,'张三三','语文',92),
(5,'张三三','数学',93),
(6,'张三三','英语',94),
(7,'张五五','语文',76),
(8,'张五五','数学',78),
(9,'张五五','英语',80);

4.2.1 COUNT() 函数

在这里插入图片描述

1
2
3
select COUNT(*) from t_grade; #统计t_grade表内总共有多少条目
select COUNT(*) as total from t_grade; #统计t_grade表内总共有多少条目,并把列名 count(*) 改为 total
select stuName,COUNT(*) from t_grade group by stuName; #按stuName分组,看每个(stuName)组有多少条

4.2.2 SUM() 函数

在这里插入图片描述

1
2
select stuName,SUM(score) from t_grade where stuName='张一一'; #求某个学生的总分
select stuName,SUM(score) from t_grade group by stuName; #按stuName分组,看每个(stuName)组的各score总和

4.2.3 AVG() 函数

在这里插入图片描述

1
2
select stuName,AVG(score) from t_grade where stuName='张一一'; #求某个学生的总分
select stuName,AVG(score) from t_grade group by stuName; #按stuName分组,看每个(stuName)组的平均score

4.2.4 MAX() 函数

在这里插入图片描述

1
2
select stuName,course,MAX(score) from t_grade where stuName='张一一'; #求某个学生的最高score,并列出course
select stuName,MAX(score) from t_grade group by stuName; #按stuName分组,看每个(stuName)组的最高score #分组前面属性只能写一个属性

4.2.5 MIN() 函数

在这里插入图片描述

1
2
select stuName,course,MIN(score) from t_grade where stuName='张一一'; #求某个学生的最低score,并列出course
select stuName,MIN(score) from t_grade group by stuName; #按stuName分组,看每个(stuName)组的最低score #分组前面属性只能写一个属性

4.3 多表连接查询

将两个或两个以上的表按照某个条件连接起来,从中选取需要的数据。

4.3.1 内连接查询

可以查询两个或两个以上的表,最常用。

1
2
3
4
5
6
7
select * from t_book,t_bookType; #数量取两表笛卡尔乘积(两两组合)

select * from t_book,t_bookType where t_book.bookTypeId=t_booktype.id; #数量取两表笛卡尔乘积后,看bookTypeId与id相等的条目

select bookName,author,bookTypeName from t_book,t_bookType where t_book.bookTypeId=t_booktype.id;

select tb.bookName,tb.author,tby.bookTypeName from t_book tb,t_bookType tby where tb.bookTypeId=tby.id; #设置别名,防止多表字段名有重合

4.3.2 外连接查询

外链接可以查询某一张表的所有信息,就是把一张表不全的部分补进来,left right决定谁合并到谁之中。

1
2
select 属性列表 from 表名1 left|right join 表名2 on1.属性1=2.属性2
#left连接把表1所有属性列出来加上表2匹配的条目,right连接吧表2所有属性列出来加上表1匹配的条目。空值NULLon限制条件。

1、左连接查询

1
select * from t_book left join t_booktype on t_book.bookTypeId=t_booktype.id; #显示表t_book所有属性

2、右连接查询

1
2
select tb.bookName,tb.author,tby.bookTypeName from t_book tb right join t_bookType tby on tb.bookTypeId=tby.id;
#显示表t_bookType全部属性,推荐使用别名。

4.3.3 多条件查询

多个条件之间使用AND连接。

1
select tb.bookName,tb.author,tby.bookTypeName from t_book tb,t_bookType tby where tb.bookTypeId=tby.id AND price>70;

4.4 子查询

在表2中进行查询,查询的某属性是限定在表一的条件中的。

4.4.1 带IN关键字的子查询

一个查询语句的条件可能落在另一个SELECT语句的查询结果中。

1
2
select * from t_book where bookTypeID [NOT] in (select id in t_booktype);
#选择表t_book的所有属性显示,筛选bookTypeID 在表t_booktype里的

4.4.2 带比较运算条件的子查询

1
2
select * from t_book where price >= (select price from t_pricelevel where pricelevel=1);
#后面括号筛选完成的条件要是数字,不是集合

4.4.3 带Exits关键字的子查询

判断性质:假如子查询语句查询到记录则进行外层查询,子查询为空不执行外层查询。

1
2
select * from t_book where [NOT] EXITS (select * from t_booktype);
#后面括号筛选得到空,则不执行前面的查询

4.4.4 带ANY关键字的子查询

满足任一条件即可。

1
2
select * from t_book where price >= any (select price from t_pricelevel);
#后面括号筛选完成的条件是集合

4.4.5 带ALL关键字的子查询

满足所有条件。

1
2
select * from t_book where price >= all (select price from t_pricelevel);
#后面括号筛选完成的条件是集合

4.5 合并查询

使用UNION关键字将所有查询结果合并到一起,去除相同记录。UNION ALL不去除重复记录。

1
2
3
4
select id from t_book; #(1,2,3,4)
select id from t_booktype; #(1,2,3,6)
select id from t_book UNION select id from t_booktype; #(1,2,3,4,6)
select id from t_book UNION ALL select id from t_booktype; #(1,2,3,4,1,2,3,6)

4.6 插入、更新、删除数据

4.6.1 插入

所有字段插入数据:

1
2
3
#insert into 表名 values(值1,值2,值3...);
insert into t_book values(NULL,'围城',,'钱钟书',1);
#自增的属性可以设为NULL

指定字段插入数据:

1
2
3
#insert into 表名(属性1,属性2,属性n) values(值1,值2,值3...);
insert into t_book(id,bookName,price,author,bookTypeId) values(NULL,'围城',28,'钱钟书',1);
insert into t_book(bookName,author) values('围城','钱钟书'); #也可以取指定的属性插入,其他默认为NULL

同时插入多条记录:

1
2
#insert into 表名[(属性列表)] values (取值列表1),(取值列表2),...(取值列表n);
insert into t_book(id,bookName,price,author,bookTypeId) values (NULL,'围城2',28,'钱钟书',1),(NULL,'围城3',28,'钱钟书',1);

4.6.2 更新

表里的一些属性值发生了变化,及时更新

1
2
3
4
5
#update 表名 set 属性1 =取值1,属性2=取值2...属性n=取值n where 条件表达式;
update t_book set bookName='围城',price=40 where id=5; #逗号分隔

update t_book set bookName='钱钟书的书' where like '%围%'
#使用模糊查询,将表中含有 围 字书名的书全部改为 钱钟书的书

4.6.3 删除

1
2
3
4
5
6
7
#delete from 表名 where [条件表达式]
#条件不是主键可能会报错,需要百度修改数据库模式。

delete from t_book where id=6; #删除一条

delete from t_student where age>=20; #符合条件多条
delete from t_student where age='2%'; #错误,非字符串不能这么匹配

5.索引

索引类似图书的目录,方便快速定位,查找指定内容,提高查询速度。

索引是数据库的一个对象,数据库中的一列或者多列组成,它不能独立存在,必须对某个表对象进行依赖。

索引保存在information_schema数据库里的STATISTICS表中。

  • 优点:提高查询数据的速度
  • 缺点:创建和维护索引的时间增加了

5.1 索引分类

查看数据库 db_book 内所有索引:

1
select * from mysql.`innodb_index_stats` a where a.`database_name` = 'db_book';

查看当前table的索引:

1
show index from tableName;

在这里插入图片描述

5.2 创建索引

创建的关键字 INDEX

一般主键在创建时默认就是唯一性索引。

方法1:最开始创建表的时候添加索引:

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
#普通 单列 索引
create table t_u1(id int, uName varchar(20), password varchar(20), INDEX (uName));

#唯一性单列索引 注意对应位置含义
create table t_u2(id int, uName varchar(20), password varchar(20), UNIQUE INDEX (uName));
create table t_u2(id int, uName varchar(20), password varchar(20), UNIQUE INDEX index_uName(uName)); #带别名

#全文单列索引
create table t_u2(id int, uName varchar(20), password varchar(20), FULLTEXT INDEX (uName));

#多列索引,uName,password两列属性指向一个索引
create table t_u3(id int, uName varchar(20), password varchar(20), INDEX index_uName_password(uName,password));

方法2:为已经存在的表添加索引:

在这里插入图片描述

1
2
3
create INDEX index_uName ON t_u4(uName); #新增普通单列索引
create UNIQUE INDEX index_uName ON t_u4(uName); #新增唯一性单列索引
create INDEX index_uName_password ON t_u4(uName,password); #新增多列索引

方法3:使用alter方法创建索引:

在这里插入图片描述

1
2
3
alter TABLE t_u5 ADD INDEX index_uName(uName); #新增普通单列索引
alter TABLE t_u5 ADD UNIQUE INDEX index_uName(uName); #新增唯一性单列索引
alter TABLE t_u5 ADD INDEX index_uName_password(uName,password); #新增多列索引

5.3 删除索引

1
2
3
4
#DROP INDEX 索引名 ON 表名;

DROP INDEX index_userName ON t_u5;
DROP INDEX index_userName_password ON t_u5; #删除多列索引

6 数据库视图(View)

  • 视图是从一个或几个基本表(或视图)中导出的虚拟的表。
  • 视图是从一个或多个实际表中获得的,那些用于产生视图的表叫做该视图的基表。在系统的数据字典中仅存放了视图的定义,不存放对应的数据,通过视图看到的数据存放在基表中。
  • 通过视图修改数据时,实际上是在改变基表中的数据;相反地,基表数据的改变也会自动反映在由基表产生的视图中。
  • 由于逻辑上的原因,有些视图可以修改对应的基表,而有些则不能(仅仅能查询)。

6.1 视图的好处

  • 提高重用性,针对重复使用的属性字段;
  • 增强安全性,不同权限用户使用部分数据创建的对应视图;
  • 表的逻辑独立性。

可参考:博客

6.2 创建视图

在这里插入图片描述

ALGORITHM表示视图选择的算法(可选参数)

  • UNDEFINED:MySQL将自动选择所要使用的算法
  • MERGE:将视图的语句与视图定义合并起来,使得视图定义的某一部分取代语句的对应部分
  • TEMPTABLE:将视图的结果存入临时表,然后使用临时表执行语句

WITH CHECK OPTION表示更新视图时要保证在该试图的权限范围之内(可选参数)

6.2.1 单表视图

1
2
3
4
5
6
7
8
CREATE VIEW v1 AS SELECT * FROM t_book; #用t_book所有字段生成视图v1
SELECT * FROM v1; #查看视图v1的所有的字段属性

CREATE VIEW v2 AS SELECT bookName,price FROM t_book; #用t_book的bookName,price字段生成视图v2
SELECT * FROM v2;

CREATE VIEW v3(b,p) AS SELECT bookName,price FROM t_book; #创建的视图内将bookName和price字段重命名为 b 和 p
SELECT * FROM v3;

6.2.2 多表视图

同一个数据库下不同表:

1
create view v4 as (select * from table1) union all (select * from table2); #合并查询

或:

1
2
3
create view v5 as select bookName,bookTypeName from t_book,t_booktype where t_book.bookTypeID=t_booktype.id;
CREATE VIEW v5 AS SELECT tb.bookName,tby.bookTypeName FROM t_book tb,t_booktype tby WHERE tb.bookTypeId=tby.id; #从两张取了别名的表里按指定条件创建视图
SELECT * FROM v5;

不同数据库下的不同表:

1
2
3
4
5
#在数据库1 目录下创建
create view 数据库1.v as (select * from 数据库1.table1) union all (select * from 数据库2.table2);

#在数据库2 目录下创建
create view 数据库2.v as (select * from 数据库1.table1) union all (select * from 数据库2.table2);

6.3 查看视图

通过指令或数据库图形管理工具查看。

1
2
3
4
5
6
7
DESC view1; #基本的视图内容信息:字段名、类型等

# 查看状态信息:包含创建时间,一些虚拟状态值
SHOW TABLE STATUS LIKE 'view1'; #视图是虚表,所以没有collation之类的信息
SHOW TABLE STATUS LIKE 't_book'; #t_book是表,有collation之类的信息

SHOW CREATE VIEW view1; #详细信息,包括编码,建表语句等

6.4 修改视图

方法1:

在这里插入图片描述

1
2
#视图v1有4个字段,改为只有bookName和price
CREATE OR REPLACE VIEW v1(bookName,price) AS SELECT bookName,price FROM t_book;

方法2:

在这里插入图片描述

1
2
#视图v1只有bookName和price,改为t_book所有的字段
ALTER VIEW v1 AS SELECT * FROM t_book;

6.5 更新视图

  • 通过视图来插入、更新、删除表中的数据
  • 视图只是一个虚表,没有数据,操作的是识图来源的基表
  • 更新视图要在权限范围内。

6.5.1 插入视图(数据)

1
2
#视图v1有id,bookName,price,author,bookTypeID
INSERT INTO v1 VALUES(NULL,'java good',120,'feng',1);

6.5.2 更新视图(数据)

1
UPDATE v1 SET bookName='java very good',price=200 WHERE id=5;

6.5.3 删除视图(数据)

1
DELETE FROM v1 WHERE id=5;

6.6 删除视图

删除数据库中已经存在的视图,删除视图并不会删除数据,只是删除视图定义。

1
DROP VIEW IF EXISTS view1;

7 触发器(TRIGGER)

执行某项操作(INSERT、UPDATE、DELETE)时候自动触发执行预设好的相对应操作。

如在表a添加数据时表b里的对应属性也进行一个变化。

相同的表相同的操作只能创建一个对应的触发器。

7.1 创建使用触发器

单个执行语句的触发器:

在这里插入图片描述

注意执行语句where 后面的部分old和new

  • old表示插入之前的值,old用在删除和修改
  • new表示新插入的值,new用在添加、更新和修改
1
2
3
4
5
6
#创建trigger:在insert插入新数据操作之后 更新书的数量 new表示插入的条目
CREATE TRIGGER trig_book AFTER INSERT
ON t_book FOR EACH ROW
UPDATE t_bookType SET bookNum=bookNum+1 WHERE new.bookTypeId=t_booktype.id;

INSERT INTO t_book VALUES(NULL,'java好',100,'ke',1); #插入操作触发trigger

多条执行语句:

在这里插入图片描述

默认mysql是遇到一个分号 ; 执行一次的。

DELIMITER | 是告诉mysql解释器不要将多条程序体里面单条语句的分号 ;直接执行,是作为一个程序体的。也可以使用 $$ // 等表示

可参考:厚积_薄发博客

1
2
3
4
5
6
7
8
9
10
11
12
13
#创建触发器
DELIMITER |
CREATE TRIGGER trig_book2 AFTER DELETE
ON t_book FOR EACH ROW
BEGIN
UPDATE t_bookType SET bookNum=bookNum-1 WHERE old.bookTypeId=t_booktype.id;
INSERT INTO t_log VALUES(NULL,NOW(),'在book表里删除了一条数据'); #mysql里NOW()表示当前时间
DELETE FROM t_test WHERE old.bookTypeId=t_test.id; #顺便把t_test的一条也删掉
END
|
DELIMITER;

DELETE FROM t_book WHERE id=5; #删除操作触发trigger

上面函数的作用是:当删除 t_book 里面 id=5的数据时,触发在

  • t_bookType 表中 bookNum数量-1
  • 在日志表 t_log 中加入一条记录
  • 在测试表 t_test 里面删除了和之前记录id相等的数据 old.bookTypeId=t_test.id

7.2 查看触发器状态

查看触发器的状态:

1
SHOW TRIGGERS; #列出来所有的

7.3 删除触发器

注意结束符号 ;之前是否有空格或者全半角区别,可能引起错误。

1
DROP TRIGGER trig_book ;

触发器补充介绍:硕果累累的博客

8 函数(functions)

8.1 常用函数

8.1.1 日期和时间函数

获取当前时间的语句

1
2
3
4
5
6
7
8
9
10
11
12
13
select now();
select sysdate();

获取日期时间的各个部分:日期、时间、年、季度、月、日、小时、分钟、秒

set @test='2019-03-26 23:08:12.123456';

select date(@test); #示例
+-------------+
| date(@test) |
+-------------+
| 2019-03-26 |
+-------------+

其他类似:time、year 、quarter、month、week、day、hour、minute、second

与时间相关函数

名称作用CURDATE()返回当前日期CURTIME()返回当前时间MONTH(d)返回当前日期d中的月份值

1
2
SELECT CURDATE(),CURTIME(),MONTH(birthday) AS m FROM table; #birthday是列名,MONTH(birthday)重命名为m
select curdate(),curtime() from gongkuang limit 5;

8.1.2 字符串函数

  • CHAR_LENGTH(s)计算字符串字符数
  • UPPER(s)转为大写
  • LOWER(s)转为小写
  • CONCAT(s1,s2...)拼接多个字符串
  • LEFT(str,len) RIGHT(str,len)返回字符串str 从左、右起len长的子串。
  • REVERSE(str)把str倒序
1
SELECT uName,CHAR_LENGTH(uName),UPPER(uName) up,LOWER(uName) low,concat(uName,upper(uName)) FROM t_u1;

输出:

在这里插入图片描述

1
select uName,left(uName,2),right(uName,2) from t_u1;

其他还有很多字符串函数可以需要的时候再查阅使用:

1
2
3
4
5
6
7
8
9
10
11
12
ASCII(str),
BIN(N),
CONV(str,from_base,to_base),
ELT(N,str1,str2,str3,…),
FIELD(str,str1,str2,str3,…),
FIND_IN_SET(str,strlist),
FORMAT(X,D),
INSERT(str,pos,len,newstr),
LOCATE(substr,str,pos),
REPLACE(str,from_str,to_str),
POSITION(substr IN str),
INSTR(str,substr),

8.1.3 数学函数

  • ABS(x)绝对值
  • SQRT(x)平方根
  • MOD(x,y)求余
  • ROUND(X,Y)X的Y位四舍五入小数
  • CEIL(x)CEILING(x)向上取整
  • FLOOR(x)向下取整
  • POW(x,y)POWER(x,y)幂运算,求x的y次方幂
1
select num,ABS(num),SQRT(num),MOD(num,3) from t_t; #计算num列的绝对值和对3求余

8.1.4 加密函数

  • PASSWORD(str)对密码加密,不可逆
  • MD5(str)SHA5()MD5校验、SHA5校验
  • ENCODE(str,pswd_str)加密字符串,结果必须用BLOB类型保存
  • DECODE(crypt_str,pswd_str)解密字符串

在插入数据的时候设置加密的格式、加密数据和加密解密的钥匙:

1
2
3
4
5
#t_t有id,birthday,uName,num,password,pp    其中pp是blob类型
INSERT INTO t_t VALUES(NULL,'2018-11-11','a',1,PASSWORD('123456')); #password字段不是明文123456
INSERT INTO t_t VALUES(NULL,'2018-11-11','a',1,MD5('123456')); #password字段不是明文123456
INSERT INTO t_t VALUES(NULL,'2018-11-11','a',1,MD5('123456'),ENCODE('abcd','aa')); #pp是明文abcd用aa加密得到的二进制
SELECT DECODE(pp,'aa') FROM t_t WHERE id=1; #用aa解密pp 得到abcd

8.2 其他函数可以查阅mysql官方手册

9 存储过程

9.1 存储过程和函数定义

存储过程和函数是在数据库中定义一些 SQL 语句的集合,然后直接调用这些存储过程和函数来执行已经定义好的 SQL 语句。存储过程和函数可以避免开发人员重复的编写相同的 SQL 语句。而且,存储过程和函数是在 MySQL服务器中存储和执行的,可以减少客户端和服务器端的数据传输;

区别:

  • 存储函数必须有返回值,而存储过程没有,
  • 存储过程的参数可以使用 in,out,inout 类型,而存储函数的参数只能是 in 类型的。
  • 如果有函数从其他类型的数据库迁移到 MySQL,那么就可能因此需要将函数改造成存储过程

9.1.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
CREATE PROCEDURE sp_name ([proc_parameter[,...]])
[characteristic ...] routine_body

CREATE FUNCTION sp_name ([func_parameter[,...]])
RETURNS type
[characteristic ...] routine_body

sp_name 参数是存储过程/或存储函数的名称

proc_parameter 表示存储过程的参数列表,每个参数格式 [IN|OUT|INOUT] param_name type
IN 表示输入参数;OUT 表示输出参数;INOUT 表示既可以是输入,也可以是输出;param_name 参数是
存储过程的参数名称;type 参数指定存储过程的参数类型,该类型可以是 MySQL 数据库的任意数据类型

func_parameter:param_name type

type:Any valid MySQL data type

characteristic 参数指定存储过程的特性
可取:LANGUAGE SQL| [NOT] DETERMINISTIC| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }| SQL SECURITY { DEFINER | INVOKER }| COMMENT 'string'

LANGUAGE SQL:说明 routine_body 部分是由 SQL 语言的语句组成

[NOT] DETERMINISTIC 存储过程输出结果是否是确定的,即每次输入一样输出也一样的程序,NOT 每次输入一样但输出可能不一样

{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }| SQL SECURITY { DEFINER | INVOKER } 指明子程序使用 SQL 语句的限制
- CONTAINS SQL子程序包含 SQL 语句,但不包含读或写数据的语句。默认格式
- NO SQL 表示子程序中不包含 SQL语句
- READS SQL DATA 子程序中包含读数据的语句
- MODIFIES SQL DATA 表示子程序中包含写数据的语句

SQL SECURITY { DEFINER | INVOKER };指明谁有权限来执行。
- DEFINER 表示只有定义者自己才能够执行;
- INVOKER 表示调用者可以执行。默认情况下,系统指定的权限是 DEFINER。

COMMENT ‘string’ :注释信息;

routine_body 参数是 SQL 代码的内容,可以用 BEGIN...END 来标志 SQL 代码的开始和结束

创建存储过程/函数示例sql语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DELIMITER &&
CREATE PROCEDURE pro_book ( IN bT INT,OUT count_num INT)
READS SQL DATA
BEGIN
SELECT COUNT(*) FROM t_book WHERE bookTypeId=bT;
END
&&
DELIMITER ;

CALL pro_book(1,@total); #调用存储过程

DELIMITER &&
CREATE FUNCTION func_book (bookId INT)
RETURNS VARCHAR(20)
BEGIN
RETURN ( SELECT bookName FROM t_book WHERE id=bookId );
END
&&
DELIMITER ;

SELECT func_book(2); #调用存储函数

9.1.2 修改存储过程或函数:

1
2
ALTER {PROCEDURE | FUNCTION} sp_name [characteristic ...]
#characteristic:参数含义类似

9.1.3 调用存储函数/过程

调用存储过程 CALL sp_name([param_name[,...]);

调用存储函数 sp_name([param_name[,...]);

9.1.4 查看现有存储过程/函数

1
2
3
4
5
#SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern'] #查看状态

SHOW PROCEDURE STATUS LIKE 'pro_book';

SHOW CREATE {PROCEDURE | FUNCTION} sp_name #查看定义

9.1.5 删除存储函数/过程

1
2
3
#DROP {PROCEDURE | FUNCTION} [IF EXISTS] sp_name

DROP PROCEDURE pro_user3;

9.2 存储函数变量

9.2.1 存储函数/过程中定义变量

1
DECLARE var_name[,...] type [DEFAULT value]

定义的变量不赋值时默认插入为NULL

1
2
3
4
5
6
7
8
9
10
11
#t_user有id,uName,password
DELIMITER &&
CREATE PROCEDURE pro_user()
BEGIN
DECLARE a,b VARCHAR(20) ;
INSERT INTO t_user VALUES(NULL,a,b);
END
&&
DELIMITER ;

CALL pro_user(); #调用存储过程

9.2.2 存储函数过程变量赋值

1
2
3
4
5
#方式1 直接赋值
SET var_name = expr [, var_name = expr] ...

#方式2 从其他表查询结果赋给当前表
SELECT col_name[,...] INTO var_name[,...] table_expr
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
#分别表示直接赋值和查询结果赋值

DELIMITER &&
CREATE PROCEDURE pro_user2()
BEGIN
DECLARE a,b VARCHAR(20) ;
SET a='java1234',b='123456';
INSERT INTO t_user VALUES(NULL,a,b);
END
&&
DELIMITER ;
CALL pro_user2(); #调用存储过程


#t_user2有id,uName,password 从t_user2查询数据存入变量再插入到t_user
DELIMITER &&
CREATE PROCEDURE pro_user3()
BEGIN
DECLARE a,b VARCHAR(20) ;
SELECT userName2,password2 INTO a,b FROM t_user2 WHERE id2=1;
INSERT INTO t_user VALUES(NULL,a,b);
END
&&
DELIMITER ;
CALL pro_user3(); #调用存储过程

9.3 游标

查询语句可能查询出多条记录,在存储过程和函数中使用游标来逐条读取查询结果集中的记录。游标必须声明在处理程序之前,并且声明在变量和条件之后。

游标的使用包括

  • 声明游标 DECLARE cursor_name CURSOR FOR select_statement ;
  • 打开游标 OPEN cursor_name;
  • 使用游标 FETCH cursor_name INTO var_name [,var_name ... ];
  • 关闭游标 CLOSE cursor_name;
1
2
3
4
5
6
7
8
9
10
11
12
13
DELIMITER &&
CREATE PROCEDURE pro_user4()
BEGIN
DECLARE a,b VARCHAR(20) ;
DECLARE cur_t_user2 CURSOR FOR SELECT userName2,password2 FROM t_user2;
OPEN cur_t_user2;
FETCH cur_t_user2 INTO a,b; #未使用循环,所以只取出、插入了集合的一条数据
INSERT INTO t_user VALUES(NULL,a,b);
CLOSE cur_t_user2;
END
&&
DELIMITER ;
CALL pro_user4(); #调用存储过程

9.4 流程控制

存储过程和函数中可以使用流程控制来控制语句的执行。MySQL 中可以使用 IF 语句、CASE 语句、LOOP语句、LEAVE 语句、ITERATE 语句、REPEAT 语句和 WHILE 语句来进行流程控制。

9.4. 1 IF语句

1
2
3
4
IF search_condition THEN statement_list
[ELSEIF search_condition THEN statement_list ]...
[ELSE statement_list ]
END IF

举例:根据传入的id查询,结果数量大于0时更新这个id对应的name,否则新插入一条数据

1
2
3
4
5
6
7
8
9
10
11
12
13
DELIMITER &&
CREATE PROCEDURE pro_user5(IN bookId INT)
BEGIN
SELECT COUNT(*) INTO @num FROM t_user WHERE id=bookId; #@num 表示全局变量
IF @num>0 THEN UPDATE t_user SET userName='java12345' WHERE id=bookId;
ELSE
INSERT INTO t_user VALUES(NULL,'2312312','2321312');
END IF ;
END
&&
DELIMITER ;
CALL pro_user5(4); #调用存储过程
CALL pro_user5(5); #调用存储过程

9.4. 2 CASE 语句

1
2
3
4
5
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list]...
[ELSE statement_list ]
END CASE

举例:传入id,查询的结果数量为1时更新,2时插入,其他情况插入另一条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DELIMITER &&
CREATE PROCEDURE pro_user6(IN bookId INT)
BEGIN
SELECT COUNT(*) INTO @num FROM t_user WHERE id=bookId;
CASE @num
WHEN 1 THEN UPDATE t_user SET userName='java12345' WHERE id=bookId;
WHEN 2 THEN INSERT INTO t_user VALUES(NULL,'2312312','2321312');
ELSE INSERT INTO t_user VALUES(NULL,'231231221321312','2321312321312');
END CASE ;
END
&&
DELIMITER ;
CALL pro_user6(5); #调用存储过程
CALL pro_user6(6); #调用存储过程

9.4. 3 LOOP,LEAVE 语句

LOOP 语句可以使某些特定的语句重复执行,实现一个简单的循环。但是 LOOP 语句本身没有停止循环的语句,必须是遇到 LEAVE 语句等才能停止循环。LOOP 语句的语法的基本形式如下:

1
2
3
[begin_label:]LOOP
Statement_list
END LOOP [ end_label ]

LEAVE 语句主要用于跳出循环控制。语法形式如下:

1
LEAVE label

举例:循环插入指定参数条数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DELIMITER &&
CREATE PROCEDURE pro_user7(IN totalNum INT)
BEGIN
aaa:LOOP
SET totalNum=totalNum-1;
IF totalNum=0 THEN LEAVE aaa ;
ELSE INSERT INTO t_user VALUES(totalNum,'2312312','2321312');
END IF ;
END LOOP aaa ;
END
&&
DELIMITER ;
DELETE FROM t_user;
CALL pro_user7(11); #调用存储过程

9.4. 4 ITERATE 语句

ITERATE 语句也是用来跳出循环的语句。但是,ITERATE 语句是跳出本次循环,然后直接进入下一次循环。基本语法:

1
ITERATE label ;

示例:指定插入次数参数为3的时候不插入,其余在0之前的都插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DELIMITER &&
CREATE PROCEDURE pro_user8(IN totalNum INT)
BEGIN
aaa:LOOP
SET totalNum=totalNum-1;
IF totalNum=0 THEN LEAVE aaa ;
ELSEIF totalNum=3 THEN ITERATE aaa ; #当是3时,直接continue
END IF ;
INSERT INTO t_user VALUES(totalNum,'2312312','2321312');
END LOOP aaa ;
END
&&
DELIMITER ;
DELETE FROM t_user;
CALL pro_user8(11); #调用存储过程

9.4. 5 REPEAT 语句

REPEAT 语句是有条件控制的循环语句。当满足特定条件时,就会跳出循环语句。REPEAT 语句的基本语法形式如下:

1
2
3
4
[ begin_label : ] REPEAT
Statement_list
UNTIL search_condition
END REPEAT [ end_label ]

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
DELIMITER &&
CREATE PROCEDURE pro_user9(IN totalNum INT)
BEGIN
REPEAT
SET totalNum=totalNum-1;
INSERT INTO t_user VALUES(totalNum,'2312312','2321312');
UNTIL totalNum=1
END REPEAT;
END
&&
DELIMITER ;
DELETE FROM t_user;
CALL pro_user9(11); #调用存储过程

9.4. 6 WHILE 语句

1
2
3
[ begin_label : ] WHILE search_condition DO
Statement_list
END WHILE [ end_label ]

举例:

1
2
3
4
5
6
7
8
9
10
11
12
DELIMITER &&
CREATE PROCEDURE pro_user10(IN totalNum INT)
BEGIN
WHILE totalNum>0 DO
INSERT INTO t_user VALUES(totalNum,'2312312','2321312');
SET totalNum=totalNum-1;
END WHILE ;
END
&&
DELIMITER ;
DELETE FROM t_user;
CALL pro_user10(11); #调用存储过程

10 数据备份与还原

备份数据可以保证数据库中数据的安全,需要定期的进行数据库备份,可以备份数据表和整个数据库。

10.1 使用 mysqldump 备份(导出)

可以使用命令备份,也可以使用图形管理工具直接导出备份。

1
mysqldump -username -p dbname table1 table2 ... > BackupName.sql
  • dbname :表示数据库的名称;
  • table1 和 table2 :表示数据表的名称,没有指明时将备份整个数据库;
  • BackupName.sql :表示备份文件的名称,文件名前面可以加绝对路径,一般以sql做后缀

10.2 还原(导入sql数据)

使用命令或者图形界面导入还原数据。

1
mysql -u root -p [dbname] < backup.sql
  • dbname :表示导入后的数据库名称,参数可指定或不指定
  • 指定数据库名时,表示还原文件到这个表下
  • 不指定数据库名时,表示还原备份sql文件中默认的数据库和表属性。