MySQL数据库

目录

数据库管理 

内置客户端操作

Python代码操作

数据表管理

内置客户端操作

Python代码操作

数据行管理

内置客户端操作

Python代码操作

案例

关于SQL注入

其他常用

条件

通配符

映射

排序

分组

左右连表

联合

表关系

用户管理

授权管理

索引

索引原理

常见索引

表操作

事务

数据库连接池


数据库管理系统(DBMS,Database Management System)的软件,可以帮助我们实现对文件夹中的文件进行操作,而我们只要学习DBMS能识别的指令, 就能控制它去帮助我们实现的文件和文件夹的处理。例如:

 

数据库管理系统(DBMS)专注于帮助开发者解决数据存储的问题,这样开发者就可以把主要精力放在实现业务功能上了。

业内有很多的的数据库管理系统产品,例如:

  • MySQL,原来是sun公司,后来被甲骨文收购。现在互联网企业几乎都在使用。【免费 + 收费】

  • Oracle,甲骨文。收费,一般国企、事业单位居多。【收费】

  • Microsoft SQL Server,微软。【收费】

  • DB2,IBM。【免费 + 收费】

  • SQLite,D. Richard Hipp个人开发。【免费】

  • Access, 微软。【收费】

  • PostgreSQL,加州大学伯克利分校。【免费】

  • 等众多..

由于各大公司都是使用MySQL,所以我们课程主要给大家讲解MySQL数据库。

在项目开发中想要基于MySQL来进行数据存储,大致应该怎么做呢?

数据库管理 

内置客户端操作

安装配置完成后后,进入mysql。

 常用基本代码:

  • show databases;        查看当前数据库
  • create database xx ;        创建一个名为xx的数据库(默认utf-8编码)
  • drop database xx;        删除名为xx的数据库
  • use db1; -> show tables;        查看该数据库下的所有数据表
  • desc 表名;          查看数据表信息(如有什么列,默认值多少,是否可以为空)
  • select * from 表;      查看表里的内容
  • exit;        退出

Python代码操作

想要使用Python操作MySQL需要安装第三方模块:

pip3 install pymysql

import pymysql

# 1. 连接MySQL(底层就是用socket链接的,utf8不是utf-8)
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='', charset='utf8')
cursor = conn.cursor() #创建一个游标 初学要注意哪里用conn.commit() 哪里用cursor.fetchone()

# 2. 创建数据库(新增、删除、修改)
# 发送指令
cursor.execute('show databases')
# 获取返回的结果(接收要加cursor.fetchall()等才能收到)
result = cursor.fetchall()
print(result)
#(('bookmanager',), ('information_schema',), ('mysql',), ('new_store',), ('performance_schema',)...)
#result = cursor.fetchone()     返回:('bookmanager',)


# 2. 创建数据库(新增、删除、修改要加conn.commit()才能执行)
# 发送指令(后面默认的编码排序等可以不写)
cursor.execute("create database adb3 default charset utf8 collate utf8_general_ci")
conn.commit()

# 4. 删除数据库
cursor.execute('drop database adb3')
conn.commit()

# 5. 进入数据库,查看表
cursor.execute('use bookmanager')
cursor.execute('show tables')
result1 = cursor.fetchall()
print(result1)
#(('app01_author',), ('app01_author_books',), ('app01_book',), ('app01_publisher',)...)

cursor.close()
conn.close()

对于一些基础的 命令错了、数据库创建重名、数据库不存在都会报错。

对于python操作,要先链接,之后设置游标,之后用游标发送命令。

  • 链接:conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='', charset='utf8')
  • 设置游标:cursor = conn.cursor()
  • 发送命令如:cursor.execute('show databases')
  • 接收返回用cursor.fetchall()
  • 执行增删改用conn.commit()

cursor.fecthall() 返回全部信息,元组里套元组,没有为一个空括号

cursor.fecthone() 返回第一条信息,一个元组里面是想要的信息,没有返回None

数据表管理

内置客户端操作

数据表常见操作的指令:

  • 进入数据库 use 数据库;,查看当前所有表:show tables;

  • 创建表结构

create table 表名(
    列名  类型,
    列名  类型,
    列名  类型
)default charset=utf8;

最后一个并不要加逗号

create table tb1(
    id int,
    name varchar(16)
)default charset=utf8; 

写一行一样,这样习惯。

 create table tb4(
    id int primary key,             -- 主键(不允许为空、不能重复)
    name varchar(16) not null,   -- 不允许为空
    email varchar(32) null,      -- 允许为空(默认)
    age int default 3            -- 插入数据时,如果不给age列设置值,默认值:3
)default charset=utf8;

主键一般用于表示当前这条数据的ID编号(类似于人的身份证),需要我们自己来维护一个不重复的值,比较繁琐。所以,在数据库中一般会将主键和自增结合。

create table tb5(
    id int not null auto_increment primary key,    -- 不允许为空 & 主键 & 自增
    name varchar(16) not null,           -- 不允许为空
    email varchar(32) null,              -- 允许为空(默认)
    age int default 3                    -- 插入数据时,如果不给age列设置值,默认值:3
)default charset=utf8;

    注意:一个表中只能有一个自增列【自增列,一般都是主键】。

    注意:在mysql里 -- 代表注释

  • 查看表

        desc 表名;

        

  • 删除表 drop table 表名;
  • 清空表 delete from 表名; 或者 truncate from 表名;(速度快、无法回滚撤销等)
  • 修改表
    • 添加列

                alter table 表名 add 列名 类型;
                alter table 表名 add 列名 类型 DEFAULT 默认值;
                alter table 表名 add 列名 类型 not null default 默认值;
                alter table 表名 add 列名 类型 not null primary key auto_increment;

  • 删除列

                alter table 表名 drop column 列名;

  • 修改列类型

                alter table 表名 modify column 列名 类型;

  • 修改列类型+名称

                alter table 表名 change 原列名 新列名 新类型;

                alter table  tb change id nid int not null;
                alter table  tb change id id int not null default 5;
                alter table  tb change id id int not null primary key auto_increment;

                alter table  tb change id id int; -- 允许为空,删除默认值,删除自增。

                可用上面这一种方式修改已有列

  • 修改列默认值

                ALTER TABLE 表名 ALTER 列名 SET DEFAULT 1000;

  • 删除列默认值

                ALTER TABLE 表名 ALTER 列名 DROP DEFAULT;

  • 添加主键

                alter table 表名 add primary key(列名);

  • 删除主键

                alter table 表名 drop primary key;

      

Python代码操作

与上面一样 不进行过多列举

import pymysql


conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='', charset='utf8')
cursor = conn.cursor() #创建一个游标 初学要注意哪里用conn.commit() 哪里用cursor.fetchone()

cursor.execute("create database db3 default charset utf8 collate utf8_general_ci")
conn.commit()

cursor.execute('use db3')
message = '''
create table t4(
    id int not null primary key auto_increment,
    title varchar(128),
    content text,
    ctime datetime
)default charset=utf8;
'''
cursor.execute(message)
cursor.execute('show tables')
cursor.execute('desc t4')
result = cursor.fetchall()
print(result)

#(('id', 'int', 'NO', 'PRI', None, 'auto_increment'), ('title', 'varchar(128)', 'YES', '', None, ''), ('content', 'text', 'YES', '', None, ''), ('ctime', 'datetime', 'YES', '', None, ''))

cursor.close()
conn.close()

但对于一些数据类型 显示的时候会告诉你这是什么数据

但遍历出来就没事了   很多东西都是这样,如字典.items()会生成一个伪列表。

 

数据行管理

内置客户端操作

数据行操作的相关SQL语句(指令)如下:

  • 数据

insert into 表名 (列名,列名,列名) values(对应列的值,对应列的值,对应列的值);

insert into tb1(name,password) values('卢本伟','123123');
insert into tb1(name,password) values('卢本伟','123123'),('alex','123');

insert into tb1 values('卢本伟','123123'),('alex','123'); -- 如果表中只有2列

放心写,不符合设定什么的都会报错。 记得value和字符串要加引号。

左边的可以不写全(如有自增的,默认值的)但右边一定要和左边数量相等且相匹配。

主键自增也是默认自增,12345,你可以直接插入一条id是7的数据,像这种主键默认是不允许重复的。

  • 除数据

delete from 表名;
delete from 表名 where 条件;

delete from tb1;
delete from tb1 where name="lbw";
delete from tb1 where name="lbw" and password="123";
delete from tb1 where id>9;
  • 数据

update 表名 set 列名=值;
update 表名 set 列名=值 where 条件; (注:一个等号)

update tb1 set name="lbw";
update tb1 set name="lbw" where id=1;

update tb1 set age=age+1;  -- 整型
update tb1 set age=age+1 where id=2;

update L3 set name=concat(name,"db");
update L3 set name=concat(name,"123")  where id=2;  -- concat一个函数,可以拼接字符串
  • 询数据

select * from 表名;
select 列名,列名,列名 from 表名;
select 列名,列名 as 别名,列名 from 表名;
select * from 表名 where 条件;

select * from tb1;
select id,name,age from tb1;
select id,name as N,age, from tb1;
select id,name as N,age, 111 from tb1;

select * from tb1 where id = 1;
select * from tb1 where id > 1;
select * from tb1 where id != 1;
select * from tb1 where name="wupeiqi" and password="123";

Python代码操作

注意 这里链接的时候写了参数 db='userdb',即连接到userdb这个数据库。

相当于执行了 cursor.execute('use userdb')

import pymysql

# 连接MySQL,自动执行 use userdb; -- 进入数据库
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8", db='userdb')
cursor = conn.cursor()


# 1.新增(需commit)
"""
cursor.execute("insert into tb1(name,password) values('卢本伟','123123')")
conn.commit()
"""

# 2.删除(需commit)
"""
cursor.execute("delete from tb1 where id=1")
conn.commit()
"""

# 3.修改(需commit)
"""
cursor.execute("update tb1 set name='xx' where id=1")
conn.commit()
"""

# 4.查询
"""
cursor.execute("select * from tb where id>10")
data = cursor.fetchone() # cursor.fetchall()
print(data)
"""

# 关闭连接
cursor.close()
conn.close()

案例

例如:实现一个 用户管理系统。

先使用MySQL自带的客户端创建相关 数据库和表结构(相当于先创建好Excel结构)。

create database usersdb default charset utf8 collate utf8_general_ci;
create table users(
    id int not null primary key auto_increment,
    name varchar(32),
    password varchar(64)
)default charset=utf8;

再在程序中执行编写相应的功能实现 注册、登录 等功能。

import pymysql
​
​
def register():
    print("用户注册")
​
    user = input("请输入用户名:") # alex
    password = input("请输入密码:") # sb
​
    # 连接指定数据
    conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8", db="usersdb")
    cursor = conn.cursor()
​
    # 执行SQL语句(有SQL注入风险,稍后讲解)
 
    sql = 'insert into users(name,password) values("{}","{}")'.format(user, password)
    
    cursor.execute(sql)
    conn.commit()
​
    # 关闭数据库连接
    cursor.close()
    conn.close()
​
    print("注册成功,用户名:{},密码:{}".format(user, password))
​
​
def login():
    print("用户登录")
​
    user = input("请输入用户名:")
    password = input("请输入密码:")
​
    # 连接指定数据
    conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8", db="usersdb")
    cursor = conn.cursor()
​
    # 执行SQL语句(有SQL注入风险,稍后讲解)
    # sql = select * from users where name='wupeiqi' and password='123'
    sql = "select * from users where name='{}' and password='{}'".format(user, password)
    cursor.execute(sql)
    
    result = cursor.fetchone() # 去向mysql获取结果
    # 没有返回 None
    
    # 关闭数据库连接
    cursor.close()
    conn.close()
​
    if result:
        print("登录成功", result)
    else:
        print("登录失败")
    #这里可以细分 只去找账号,返回账号是否不存在,然后根据元组坎密码是否对应正确。
​
​
def run():
    choice = input("1.注册;2.登录")
    if choice == '1':
        register()
    elif choice == '2':
        login()
    else:
        print("输入错误")
​
​
if __name__ == '__main__':
    run()
​

注意,看到引号没 ->  这个要加 不然有时会出错

不过不应该这样写,不用字符串格式化,下面MySQL注入会讲

应该

So,你会发现, 在项目开发时,数据库 & 数据表 的操作其实就做那么一次,最最常写的还是 对数据行 的操作。

关于SQL注入

假如,你开发了一个用户认证的系统,应该用户登录成功后才能正确的返回相应的用户结果。

import pymysql
​
# 输入用户名和密码
user = input("请输入用户名:") # ' or 1=1 -- 
pwd = input("请输入密码:") # 123
​
​
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8",db='usersdb')
cursor = conn.cursor()
​
# 基于字符串格式化来 拼接SQL语句
# sql = "select * from users where name='alex' and password='123'"
# sql = "select * from users where name='' or 1=1 -- ' and password='123'"
sql = "select * from users where name='{}' and password='{}'".format(user, pwd)
cursor.execute(sql)
​
result = cursor.fetchone()
print(result) # None,不是None
​
cursor.close()
conn.close()

如果用户在输入user时,输入了: ' or 1=1 -- ,这样即使用户输入的密码不存在,也会可以通过验证。

为什么呢?

因为在SQL拼接时,拼接后的结果是:

select * from users where name='' or 1=1 -- ' and password='123'

注意:在MySQL中 -- 表示注释。

那么,在Python开发中 如何来避免SQL注入呢?

切记,SQL语句不要在使用python的字符串格式化,而是使用pymysql的execute方法。

import pymysql
​
# 输入用户名和密码
user = input("请输入用户名:")
pwd = input("请输入密码:")
​
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8", db='userdb')
​
cursor = conn.cursor()
​
cursor.execute("select * from users where name=%s and password=%s", [user, pwd])
# 或
# cursor.execute("select * from users where name=%(n1)s and password=%(n2)s", {"n1": user, 'n2': pwd})
​
result = cursor.fetchone()
print(result)
​
cursor.close()
conn.close()

其他常用

条件

根据条件搜索结果。

select * from info where age > 30;
select * from info where id > 1;
select * from info where id = 1;
select * from info where id >= 1;
select * from info where id != 1;

select * from info where info.id > 10;

select * from info where id between 2 and 4;   -- id大于等于2、且小于等于4

select * from info where name = 'xx' and age = 19;
select * from info where name = 'xx' or age = 49;
select * from info where (name = 'xx' or email="pyyu@live.com")  and age=49;

select * from info where id in (1,4,6);
select * from info where id not in (1,4,6);
select * from info where id in (select id from depart);
# select * from info where id in (1,2,3);

# exists select * from depart where id=5,去查数据是否存在,如果存在,如果不存在。
select * from info where exists (select * from depart where id=5);
select * from info where not exists (select * from depart where id=5);

select * from (select * from info where id>2) as T where age > 10;

如:查找年龄大于20小于60的数据:

select * from info where age between 20 and 60;

select * from info where age>=20 and age<=60;

select * from (select * from info where age>=20) as T where age<=60;
-- 最后一种的as要加的

通配符

一般用于模糊搜索。

select * from info where name like "%本%";
select * from info where name like "%伟";
select * from info where email like "%@live.com";
select * from info where name like "卢%伟";
select * from info where name like "k%y";
select * from info where email like "lubenwei%";


select * from info where email like "_@live.com";
select * from info where email like "_ubenwei@live.com";
select * from info where email like "__benwei@live.com";
select * from info where email like "__benwei_live.co_";

注:数量少的时候用。

映射

想要获取的列。

select * from info;

select id, name				from info;
select id, name as NM 		from info; 给name起了个别名
select id, name, 123  from info; 输出的时候又额外增加一列全是123
select id, name, 123 as age  from info; 全是123的表头起名为age
注意:少些select * ,自己需求。

select 
	id,
	name,
	666 as num,
	( select max(id) from depart ) as mid, -- max/min/sum
	( select min(id) from depart) as nid, -- max/min/sum
	age
from info;
select 
	id,
	name,
	( select title from depart where depart.id=info.depart_id) as x1
from info;

# 注意:效率很低

case when then else

select 
	id,
	name,
	case depart_id when 1 then "第1部门" end v1
from info;

select 
	id,
	name,
	case depart_id when 1 then "第1部门" else "其他" end v2
from info;

select 
	id,
	name,
    -- 如果depart_id等于1
	case depart_id when 1 then "第1部门" end v1,  --注:没有就是Null
	case depart_id when 1 then "第1部门" else "其他" end v2,
	case depart_id when 1 then "第1部门" when 2 then "第2部门" else "其他" end v3,
    
    -- 如果age<18  范围外为Null
	case when age<18 then "少年" end v4,
	case when age<18 then "少年" else "油腻男" end v5,
	case when age<18 then "少年" when age<30 then "青年" else "油腻男" end v6
from info;
-- 直接case when不能用于等于如case when depart_id=1 报错
-- 别忘记逗号 且最后一条数据都不加逗号


select 
	id,
	name,
	case depart_id when 1 then "开发" when 2 then "运营" else "销售" end v3,
	case when age<18 then "少年" when age<30 then "青年" else "油腻男" end v6
from info;

如:打印出表中信息,年龄小于18的为少年,年龄。。。。

排序

select * from info order by age asc;  -- 顺序
select * from info order by age desc; -- 倒序

select * from info order by id desc;
select * from info order by id asc;
select * from info order by age asc,id desc; -- 优先按照age从小到大;如果age相同则按照id从大到小。


select * from info where id>10 order by age asc,id desc;
select * from info where id>6 or name like "%y" order by age asc,id desc;

取部分 limit offset

一般要用于获取部分数据。

select * from info limit 5;   										-- 获取前5条数据
select * from info order by id desc limit 3;						-- 先排序,再获取前3条数据
select * from info where id > 4 order by id desc limit 3;			-- 先排序,再获取前3条数据


select * from info limit 3 offset 2;	-- 从位置2开始,向后获取前3数据

数据库表中:1000条数据。

  • 第一页:select * from info limit 10 offset 0;

  • 第二页:select * from info limit 10 offset 10;

  • 第三页:select * from info limit 10 offset 20;

  • 第四页:select * from info limit 10 offset 30;

  • ...

分组

这个要注意不用where用having了。

select age,max(id),min(id),count(id),sum(id),avg(id) from info group by age;
select age,count(1) from info group by age;


select depart_id,count(id) from info group by depart_id;
select depart_id,count(id) from info group by depart_id having count(id) > 2;


select count(id) from info;
select max(id) from info;


select age,max(id),min(id),sum(id),count(id) from info group by age;
select age,name from info group by age;  -- 不建议
select * from info where id in (select max(id) from info group by age);

select age,count(id) from info group by age having count(id) > 2;
select age,count(id) from info where id > 4 group by age having count(id) > 2;  -- 聚合条件放在having后面

到目前为止SQL执行顺序:
    where 
    group by
    having 
    order by
    limit 

id大于2的数据中统计不同年龄的各有多少个,把个数大于1的按照年龄从大到小显示出来,且只显示一个。

select age,count(id) from info where id > 2 group by age having count(id) > 1 order by age desc limit 1;

- 要查询的表info
- 条件 id>2
- 根据age分组
- 对分组后的数据再根据聚合条件过滤 count(id)>1
- 根据age从大到小排序
- 获取第1条

左右连表

多个表可以连接起来进行查询。

展示用户信息&部门名称:

主表 left outer join 从表 on 主表.x = 从表.id 
select * from info left outer join depart on info.depart_id = depart.id;
select info.id,info.name,info.email,depart.title from info left outer join depart on info.depart_id = depart.id;
从表 right outer join 主表 on 主表.x = 从表.id
select info.id,info.name,info.email,depart.title from info right outer join depart on info.depart_id = depart.id;

 为了更加直接的查看效果,我们分别在 depart 表 和 info 中额外插入一条数据。

insert into depart(title) values("运维");

这样一来主从表就有区别:

  • info主表,就以info数据为主,depart为辅。

    select info.id,info.name,info.email,depart.title from info left outer join depart on info.depart_id = depart.id;
  • depart主表,,就以depart数据为主,info为辅。

    select info.id,info.name,info.email,depart.title from info right outer join depart on info.depart_id = depart.id;
select * from info left outer join depart on ....
select * from depart left outer join info on ....

简写:select * from depart left join info on ....

-- 内连接:    表  inner join 表  on 条件
select * from info inner join depart on info.depart_id=depart.id;

​到目前为止SQL执行顺序:
    join 
    on 
    where 
    group by
    having 
    order by
    limit 

写在最后:多张表也可以连接。

联合

select id,title from depart 
union
select id,name from info;


select id,title from depart 
union
select email,name from info;
-- 列数需相同
select id from depart 
union
select id from info;

-- 自动去重
select id from depart 
union all
select id from info;

-- 保留所有

表关系

  • 单表:

        略

  • 一对多:
create table depart(
	id int not null auto_increment primary key,
    title varchar(16) not null
)default charset=utf8;


create table info(
	id int not null auto_increment primary key,
    name varchar(16) not null,
    email varchar(32) not null,
    age int,
    depart_id int not null,
    constraint fk_info_depart foreign key (depart_id) references depart(id)
)default charset=utf8;

如果表结构已创建好了,额外想要增加外键:

alter table info add constraint fk_info_depart foreign key info(depart_id) references depart(id);

删除外键:

alter table info drop foreign key fk_info_depart;
  • 多对多
create table boy(
	id int not null auto_increment primary key,
    name varchar(16) not null
)default charset=utf8;

create table girl(
	id int not null auto_increment primary key,
    name varchar(16) not null
)default charset=utf8;


create table boy_girl(
	id int not null auto_increment primary key,
    boy_id int not null,
    girl_id int not null,
    constraint fk_boy_girl_boy foreign key boy_girl(boy_id) references boy(id),
    constraint fk_boy_girl_girl foreign key boy_girl(girl_id) references girl(id)
)default charset=utf8;

如果表结构已创建好了,额外想要增加外键:

alter table boy_girl add constraint fk_boy_girl_boy foreign key boy_girl(boy_id) references boy(id);
alter table boy_girl add constraint fk_boy_girl_girl foreign key boy_girl(girl_id) references girl(id);

删除外键:

xxxxxxxxxx alter table info drop foreign key fk_boy_girl_boy;alter table info drop foreign key fk_boy_girl_girl;

用户管理

在MySQL的默认数据库 mysql 中的 user 表中存储着所有的账户信息(含账户、权限等)。

  • 创建和删除用户

    create user '用户名'@'连接者的IP地址' identified by '密码';
    create user wuyou@127.0.0.1 identified by 'root123';
    drop user wuyou@127.0.0.1;
    ​
    create user wuyou@'127.0.0.%' identified by 'root123';
    drop user wuyou@'127.0.0.%';
    ​
    create user wuyou@'%' identified by 'root123';
    drop user wuyou@'%';
    ​
    create user 'wuyou'@'%' identified by 'root123';
    drop user 'wuyou'@'%';
  • 修改用户

    rename user '用户名'@'IP地址' to '新用户名'@'IP地址';
    rename user wuyou@127.0.0.1 to wuyou@localhost;
    ​
    rename user 'wuyou'@'127.0.0.1' to 'wuyou'@'localhost';
  • 修改密码

    set password for '用户名'@'IP地址' = Password('新密码')
    set password for 'wuyou'@'%' = Password('123123');

授权管理

创建好用户之后,就可以为用户进行授权了。

  • 授权
grant all privileges on *.* TO 'wuyou'@'localhost';         -- 用户wuyou拥有所有数据库的所有权限
grant all privileges on day26.* TO 'wuyou'@'localhost';     -- 用户wuyou拥有数据库day26的所有权限
grant all privileges on day26.info TO 'wuyou'@'localhost';  -- 用户wuyou拥有数据库day26中info表的所有权限

grant select on day26.info TO 'wuyou'@'localhost';          -- 用户wuyou拥有数据库day26中info表的查询权限
grant select,insert on day26.* TO 'wuyou'@'localhost';      -- 用户wuyou拥有数据库day26所有表的查询和插入权限

grant all privileges on day26db.* to 'wuyou'@'%';

注意:flush privileges;   -- 将数据读取到内存中,从而立即生效。

  • 对于权限

all privileges  除grant外的所有权限
select          仅查权限
select,insert   查和插入权限
...
usage                   无访问权限
alter                   使用alter table
alter routine           使用alter procedure和drop procedure
create                  使用create table
create routine          使用create procedure
create temporary tables 使用create temporary tables
create user             使用create user、drop user、rename user和revoke  all privileges
create view             使用create view
delete                  使用delete
drop                    使用drop table
execute                 使用call和存储过程
file                    使用select into outfile 和 load data infile
grant option            使用grant 和 revoke
index                   使用index
insert                  使用insert
lock tables             使用lock table
process                 使用show full processlist
select                  使用select
show databases          使用show databases
show view               使用show view
update                  使用update
reload                  使用flush
shutdown                使用mysqladmin shutdown(关闭MySQL)
super                   􏱂􏰈使用change master、kill、logs、purge、master和set global。还允许mysqladmin􏵗􏵘􏲊􏲋调试登陆
replication client      服务器位置的访问
replication slave       由复制从属使用

  • 对于数据库和表

数据库名.*            数据库中的所有
数据库名.表名          指定数据库中的某张表
数据库名.存储过程名     指定数据库中的存储过程
*.*                  所有数据库

  • 查看授权
show grants for 'wuyou'@'localhost';
show grants for 'wuyou'@'%';
  • 取消授权
revoke ALL PRIVILEGES on day26.* from 'wuyou'@'localhost';

revoke ALL PRIVILEGES on day26db.* from 'wuyou'@'%';

注意:flush privileges;   -- 将数据读取到内存中,从而立即生效。

哪怕没有A这个数据库,被授权了A数据库的操作,一样不会报错。

这里的localhost与127.0.0.1不一样。

一般情况下,在很多的 正规 公司,数据库都是由 DBA 来统一进行管理,DBA为每个项目的数据库创建用户,并赋予相关的权限。

索引

在数据库中索引最核心的作用是:加速查找。 例如:在含有300w条数据的表中查询,无索引需要700秒,而利用索引可能仅需1秒。

索引原理

        为什么加上索引之后速度能有这么大的提升呢? 因为索引的底层是基于B+Tree的数据结构存储的。

数据库的索引是基于上述B+Tree的数据结构实现,但在创建数据库表时,如果指定不同的引擎,底层使用的B+Tree结构的原理有些不同。

  • myisam引擎,非聚簇索引(数据 和 索引结构 分开存储)

  • innodb引擎,聚簇索引(数据 和 主键索引结构存储在一起)

非聚簇索引(mysiam引擎)

聚簇索引(innodb引擎)

上述 聚簇索引 和 非聚簇索引 底层均利用了B+Tree结构结构,只不过内部数据存储有些不同罢了。

在企业开发中一般都会使用 innodb 引擎(内部支持事务、行级锁、外键等特点),在MySQL5.5版本之后默认引擎也是innodb。

常见索引

在innodb引擎下,索引底层都是基于B+Tree数据结构存储(聚簇索引)。

在开发过程中常见的索引类型有:

  • 主键索引:加速查找、不能为空、不能重复。 + 联合主键索引

  • 唯一索引:加速查找、不能重复。 + 联合唯一索引

  • 普通索引:加速查找。 + 联合索引

主键和联合主键索引

create table 表名(
    id int not null auto_increment primary key,   -- 主键
    name varchar(32) not null
);

create table 表名(
    id int not null auto_increment,
    name varchar(32) not null,
    primary key(id)
);

create table 表名(
    id int not null auto_increment,
    name varchar(32) not null,
    primary key(列1,列2)          -- 如果有多列,称为联合主键(不常用且myisam引擎支持)
);
alter table 表名 add primary key(列名);

alter table 表名 drop primary key;

注意:删除索引时可能会报错,自增列必须定义为键。

ERROR 1075 (42000): Incorrect table definition; there can be only one auto column and it must be defined as a key

alter table 表 change id id int not null;

create table t7(
    id int not null,
    name varchar(32) not null,
    primary key(id)
);

alter table t6 drop primary key;

唯一和联合唯一索引

create table 表名(
    id int not null auto_increment primary key,
    name varchar(32) not null,
    email varchar(64) not null,
    unique ix_name (name),
    unique ix_email (email),
);

create table 表名(
    id int not null auto_increment,
    name varchar(32) not null,
    unique (列1,列2)               -- 如果有多列,称为联合唯一索引。
);
create unique index 索引名 on 表名(列名);

drop unique index 索引名 on 表名;

索引和联合索引

create table 表名(
    id int not null auto_increment primary key,
    name varchar(32) not null,
    email varchar(64) not null,
    index ix_email (email),
    index ix_name (name),
);

create table 表名(
    id int not null auto_increment primary key,
    name varchar(32) not null,
    email varchar(64) not null,
    index ix_email (name,email)     -- 如果有多列,称为联合索引。
);
create index 索引名 on 表名(列名);

drop index 索引名 on 表名;

表操作

一般情况下,我们针对只要通过索引列去搜搜都可以 命中 索引(通过索引结构加速查找)。

但是,还是会有一些特殊的情况,让我们无法命中索引(即使创建了索引),这也是需要大家在开发中要注意的。

  • 类型不一致

    select * from big where name = 123;     -- 未命中
    select * from big where email = 123;    -- 未命中
    ​
    特殊的主键:
        select * from big where id = "123"; -- 命中
  • 使用不等于

    select * from big where name != "xx";              -- 未命中
    select * from big where email != "wupeiqi@live.com";  -- 未命中
    ​
    特殊的主键:
        select * from big where id != 123;  -- 命中
  • or,当or条件中有未建立索引的列才失效。

    select * from big where id = 123 or password="xx";          -- 未命中
    select * from big where name = "wupeiqi" or password="xx";  -- 未命中
    特别的:
        select * from big where id = 10 or password="xx" and name="xx"; -- 命中
  • 排序,当根据索引排序时候,选择的映射如果不是索引,则不走索引。

    select * from big order by name asc;     -- 未命中
    select * from big order by name desc;    -- 未命中
    ​
    特别的主键:
        select * from big order by id desc;  -- 命中
  • like,模糊匹配时。

    select * from big where name like "%u-12-19999";    -- 未命中
    select * from big where name like "_u-12-19999";    -- 未命中
    select * from big where name like "wu-%-10";        -- 未命中
    ​
    特别的:
        select * from big where name like "wu-1111-%";  -- 命中
        select * from big where name like "wuw-%";      -- 命中
  • 使用函数

    select * from big where reverse(name) = "wupeiqi";  -- 未命中
    ​
    特别的:
        select * from big where name = reverse("wupeiqi");  -- 命中
  • 最左前缀,如果是联合索引,要遵循最左前缀原则。

    如果联合索引为:(name,password)
        name and password       -- 命中
        name                    -- 命中
        password                -- 未命中
        name or password        -- 未命中

事务

innodb引擎中支持事务,myisam不支持。   

例如:xx给oo 转账 100,那就会涉及2个步骤。

  • xx账户 减100

  • oo账户 加 100

这两个步骤必须同时完成才算完成,并且如果第一个完成、第二步失败,还是回滚到初始状态。

事务,就是来解决这种情况的。 大白话:要成功都成功;要失败都失败。

事务的具有四大特性(ACID):

  • 原子性(Atomicity)

    原子性是指事务包含的所有操作不可分割,要么全部成功,要么全部失败回滚。
  • 一致性(Consistency)

    执行的前后数据的完整性保持一致。
  • 隔离性(Isolation)

    一个事务执行的过程中,不应该受到其他事务的干扰。
  • 持久性(Durability)

    事务一旦结束,数据就持久到数据库

begin;
update users set amount=amount-2 where id=1;
update users set amount=amount+2 where id=2;
commit;
import pymysql

conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8", db='userdb')
cursor = conn.cursor()

# 开启事务
conn.begin()

try:
    cursor.execute("update users set amount=1 where id=1")
    int('asdf')
    cursor.execute("update tran set amount=2 where id=2")
except Exception as e:
    # 回滚
    print("回滚")
    conn.rollback()
else:
    # 提交
    print("提交")
    conn.commit()

cursor.close()
conn.close()

在用MySQL时,不知你是否会疑问:同时有很多做更新、插入、删除动作,MySQL如何保证数据不出错呢?

MySQL中自带了锁的功能,可以帮助我们实现开发过程中遇到的同时处理数据的情况。对于数据库中的锁,从锁的范围来讲有:

  • 表级锁,即A操作表时,其他人对整个表都不能操作,等待A操作完之后,才能继续。

  • 行级锁,即A操作表时,其他人对指定的行数据不能操作,其他行可以操作,等待A操作完之后,才能继续。

MYISAM支持表锁,不支持行锁;
InnoDB引擎支持行锁和表锁。

即:在MYISAM下如果要加锁,无论怎么加都会是表锁。
    在InnoDB引擎支持下如果是基于索引查询的数据则是行级锁,否则就是表锁。

在innodb引擎中,update、insert、delete的行为内部都会先申请锁(排它锁),申请到之后才执行相关操作,最后再释放锁。

所以,当多个人同时像数据库执行:insert、update、delete等操作时,内部加锁后会排队逐一执行。

而select则默认不会申请锁。

select * from xxx;

如果,你想要让select去申请锁,则需要配合 事务 + 特殊语法来实现。

  • for update,排它锁,加锁之后,其他不可以读写。

    begin; 
        select * from L1 where name="武沛齐" for update;    -- name列不是索引(表锁)
    commit;
    begin; -- 或者 start transaction;
        select * from L1 where id=1 for update;           -- id列是索引(行锁)
    commit;
  • lock in share mode ,共享锁,加锁之后,其他可读但不可写。

    begin; 
        select * from L1 where name="武沛齐" lock in share mode;    -- 假设name列不是索引(表锁)
    commit;
    begin; -- 或者 start transaction;
        select * from L1 where id=1 lock in share mode;           -- id列是索引(行锁)
    commit;

排它锁

排它锁( for update),加锁之后,其他事务不可以读写。

应用场景:总共100件商品,每次购买一件需要让商品个数减1 。

A: 访问页面查看商品剩余 100
B: 访问页面查看商品剩余 100

此时 A、B 同时下单,那么他们同时执行SQL:
    update goods set count=count-1 where id=3
由于Innodb引擎内部会加锁,所以他们两个即使同一时刻执行,内部也会排序逐步执行。


但是,当商品剩余 1个时,就需要注意了。
A: 访问页面查看商品剩余 1
B: 访问页面查看商品剩余 1

此时 A、B 同时下单,那么他们同时执行SQL:
    update goods set count=count-1 where id=3
这样剩余数量就会出现 -1,很显然这是不正确的,所以应该怎么办呢?

-- 这种情况下,可以利用 排它锁,在更新之前先查询剩余数量,只有数量 >0 才可以购买,所以,下单时应该执行:
    begin; -- start transaction;
    select count from goods where id=3 for update;
    -- 获取个数进行判断
    if 个数>0:
        update goods set count=count-1 where id=3;
    else:
        -- 已售罄
    commit;
import pymysql
import threading


def task():
    conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8", db='userdb')
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    # cursor = conn.cursor()
	
    # 开启事务
    conn.begin()

    cursor.execute("select id,age from tran where id=2 for update")
    # fetchall      ( {"id":1,"age":10},{"id":2,"age":10}, )   ((1,10),(2,10))
    # {"id":1,"age":10}   (1,10)
    result = cursor.fetchone()
    current_age = result['age']
    
    if current_age > 0:
        cursor.execute("update tran set age=age-1 where id=2")
    else:
        print("已售罄")

    conn.commit()

    cursor.close()
    conn.close()


def run():
    for i in range(5):
        t = threading.Thread(target=task)
        t.start()


if __name__ == '__main__':
    run()

数据库连接池

import threading
import pymysql
from dbutils.pooled_db import PooledDB

MYSQL_DB_POOL = PooledDB(
    creator=pymysql,  # 使用链接数据库的模块
    maxconnections=5,  # 连接池允许的最大连接数,0和None表示不限制连接数
    mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
    maxcached=3,  # 链接池中最多闲置的链接,0和None不限制
    blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
    setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
    ping=0,
    # ping MySQL服务端,检查是否服务可用。
    # 如:0 = None = never, 1 = default = whenever it is requested, 
    # 2 = when a cursor is created, 4 = when a query is executed, 7 = always
    host='127.0.0.1',
    port=3306,
    user='root',
    password='root123',
    database='userdb',
    charset='utf8'
)


def task():
    # 去连接池获取一个连接
    conn = MYSQL_DB_POOL.connection()
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    
    cursor.execute('select sleep(2)')
    result = cursor.fetchall()
    print(result)

    cursor.close()
    # 将连接交换给连接池
    conn.close()

def run():
    for i in range(10):
        t = threading.Thread(target=task)
        t.start()


if __name__ == '__main__':
    run()

推荐使用上下文管理方式: 

# db_context.py
import threading
import pymysql
from dbutils.pooled_db import PooledDB

POOL = PooledDB(
    creator=pymysql,  # 使用链接数据库的模块
    maxconnections=5,  # 连接池允许的最大连接数,0和None表示不限制连接数
    mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
    maxcached=3,  # 链接池中最多闲置的链接,0和None不限制
    blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
    setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
    ping=0,
    host='127.0.0.1',
    port=3306,
    user='root',
    password='root123',
    database='userdb',
    charset='utf8'
)


class Connect(object):
    def __init__(self):
        self.conn = conn = POOL.connection()
        self.cursor = conn.cursor(pymysql.cursors.DictCursor)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.cursor.close()
        self.conn.close()

    def exec(self, sql, **kwargs):
        self.cursor.execute(sql, kwargs)
        self.conn.commit()

    def fetch_one(self, sql, **kwargs):
        self.cursor.execute(sql, kwargs)
        result = self.cursor.fetchone()
        return result

    def fetch_all(self, sql, **kwargs):
        self.cursor.execute(sql, kwargs)
        result = self.cursor.fetchall()
        return result
from db_context import Connect

with Connect() as obj:
    # print(obj.conn)
    # print(obj.cursor)
    ret = obj.fetch_one("select * from d1")
    print(ret)

    ret = obj.fetch_one("select * from d1 where id=%(id)s", id=3)
    print(ret)
  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老师我作业忘带了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值