entner

entner 查看完整档案

成都编辑西南民族大学  |  信息管理与信息系统 编辑SYL/BBT/SE  |  小白 编辑 segmentfault.com/u/entner 编辑
编辑

青年 ---------------

-关注前沿、实时更新、接近生活、积淀经验、保持健康

flag:开源 MVC
to: 让小白们了解一般的mvc框架(如:laravel、thinkphp)的实现原理 我们自己也能写~

个人动态

entner 收藏了文章 · 10月14日

MySQL基本操作之-DDL,DML,DQL,DCL

MySQL基本操作之DDL(数据定义语言),DML(数据操纵语言),DQL(数据查询语言),DCL(数据控制语言)微信搜索公众号:”菜鸟封神记“,定期分享一线大厂常用技术干货。

一、DDL--数据定义语言
作用:数据定义语言主要用来定义数据库中的各类对象,包括用户、库、表、视图、索引、触发器、事件、存储过程和函数等。
常见的DDL操作的基本用法如下:

   CREATE USER           #创建用户
   CREATE DATABASE       #创建数据库
   CREATE TABLE          #创建表
   CREATE VIEW           #创建视图
   CREATE INDEX          #创建索引
   CREATE TRIGGER        #创建触发器
   CREATE EVENT          #创建事件
   CREATE PROCEDURE      #创建存储过程
   CREATE FUNCTION       #创建自定义函数
   ...其他不常用的DDL(如:TABLESPACE)操作可自行查阅资料...

1、创建用户:
详细用法:CREATE USER 'username'@'[ip/domain/netmask]'

参数解释:
username:表示登陆MySQL实例的用户名
[ip/domain/ip range]:表示数据库实例允许的登陆ip,域名或者ip段

示例:创建一个名称为bingwang,登陆ip为192.168.0.10的用户:

mysql> CREATE USER 'bingwang'@'192.168.0.10';

2、创建数据库,示例如下:

详细用法:

CREATE DATABASE db_name;

示例如下:

#创建一个名称为test_db,字符集为utf8的数据库
mysql> CREATE DATABASE test_db DEFAULT CHARSET UTF8;

3、创建表:

详细用法:CREATE TABLE table_name;
示例如下:

#创建一个名称为t_test,字符集为utf8,存储引擎为InnoDB,字符校验集为utf8_general_ci的表:
mysql> CREATE TABLE t_test (
           id INT NOT NULL AUTO_INCREMENT,
           name VARCHAR(50),
           PRIMARY KEY(id)
       ) ENGINE = InnoDB DEFAUL CHARSET = UTF8 COLLATE = utf8_general_ci;

4、创建视图:

详细用法:

CREATE VIEW view_name as <SELECT phrase>;
参数解释: <SELECT phrase>:查询语句

示例如下:

#创建一个视图t_view,用来查询t_test中的ID为1或者2的数据:
mysql> CREATE VIEW test_view AS SELECT * FROM t_test WHERE id IN (1,2);
#查看创建视图的过程:
mysql> SHOW CREATE VIEW test_view;

5、创建索引,有两种方法,CREATE和ALTER,下面先介绍一下CREATE:

详细用法:

CREATE [UNIQUE] INDEX index_name ON table_name(field[num]) <OPTIONS>;

参数解释: 
UNIQUE:表示创建的索引类型为唯一索引,如果创建的为一般索引可以忽略该选项
table_name:表名称
field:表中的某个字段。num为可选参数,如果field为字符创类型,表示给该字段的前num个字符创建索引
OPTIONS:表示可选选项,可以指定索引使用的算法,比如:USING BTREE。不指定默认为BTREE;

示例如下:
(1)给t_test表中的name字段添加一个唯一索引,使用BTREE作为其索引算法:

mysql> CREATE UNIQUE INDEX name_ind ON t_test(name) USING BTREE;
mysql> SHOW [INDEX/INDEXES] FROM t_test;  #查看t_test表中的索引,[INDEX/INDEXES]两个关键字都可以

(2)给t_test表中的name字段的前5个字符创建一般索引,使用BTREE作为其索引算法:

mysql> CREATE INDEX name_index ON t_test(name(5));

关于索引的更多用法及优化在后面的文章中会详细讲解。

6、创建触发器:
详细用法:

CREATE TRIGGER trigger_name trigger_time trigger_event FOR EACH ROW 
BEGIN 
  trigger_stmt 
END;

示例:创建触发器内容稍多,此处先稍微提一下,后面专门章节介绍;

7、创建存储过程:
详细用法:

CREATE PROCEDURE procedure_name([proc_parameter[,...]])
BEGIN
  ...存储过程体
END

示例:创建存储过程内容稍多,此处先稍微提一下,后面专门章节介绍;
8、创建自定义函数:
详细用法:

CREATE FUNCTION function_name([func_parameter[,...]])
RETURNS type
BEGIN
...函数体
END

示例:创建自定义函数内容稍多,此处先稍微提一下,后面专门章节介绍;
至此,简单的DDL操作介绍完成。


二、DML--数据操纵语言:
作用:用来操作数据库中的表对象,主要包括的操作有:INSERT,UPDATE,DELETE
常见的DML的基本操作方法如下:

#给表中添加数据
INSERT INTO ...
#修改表中的数据
UPDATE table_name SET ...
#删除表中的数据
DELETE FROM table_name WHERE <condition>;
注:<condition>:表示DML操作时的条件

1、向表中插入数据:
详细用法:

mysql> INSERT INTO table_name(field1,field2,[,...]) values(value1,value2),(value3,value4),...;

示例:向学生表中插入一条数据,name:'xiaohong', age:24, gender:'M' ,如下:
(1)创建表:

 mysql> CREATE TABLE student(
        id INT PRIMARY KEY AUTO_INCREMENT,
        name VARCHAR(50) NOT NULL DEFAULT '',
        age TINYINT,
        gender ENUM('F','M')
     ) ENGINE = InnoDB DEFAULT CHARSET = UTF8;

(2)插入数据:

mysql> INSERT INTO student(name,age,gender) VALUES('xiaohong',24,'M');
Query OK, 1 row affected (0.09 sec)
mysql> SELECT * FROM student;
+----+----------+------+--------+
| id | name     | age  | gender |
+----+----------+------+--------+
|  1 | xiaohong |   24 | M      |
+----+----------+------+--------+
1 row in set (0.37 sec)
注:主键如果自动递增,插入时可不用指定;

2、修改表中的数据:
详细用法:

UPDATE table_name SET field1 = value1, field2 = value2, ... ,  WHERE <condition>;

示例:将student表中id为1的记录中的name值修改为:"xiaohua",如下:

mysql> UPDATE STUDENT SET name = 'xiaohua' WHERE id = 1;
Query OK, 1 row affected (0.67 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> SELECT * FROM student;
+----+---------+------+--------+
| id | name    | age  | gender |
+----+---------+------+--------+
|  1 | xiaohua |   24 | M      |
+----+---------+------+--------+
1 row in set (0.00 sec)

注意:此处修改的时候,一定得注意加条件,否则整个表都会被改。讲个技巧:在MySQL的命令中执行操作的时候,可以在登录MySQL时添加-U选项,如果忘加条件,会被阻止执行sql语句。登录命令如下:

    [root@WB-BLOG ~]# mysql -uroot -proot -U -h127.0.0.1 -P3306
    mysql> UPDATE STUDENT SET name = 'hong';
    ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column

可见:登录之后如果不加条件执行UPDATE语句,会被阻止;

3、删除表中的数据:
详细用法:

mysql> DELETE FROM table_name WHERE <condition>;

示例:删除student表中id为1的记录,如下:

mysql> DELETE FROM student WHERE id = 1;
Query OK, 1 row affected (0.37 sec)
mysql> SELECT * FROM student;
Empty set (0.00 sec)

注意:注意!注意!!再注意!!!,该操作非常危险,命令行中操作时,需要万分注意。可以使用登录时加-U参数的方式,防止忘加条件而删除所有数据,加了-U参数之后,如果不加条件,会被阻止,执行结果如下:

mysql> DELETE FROM student;
ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column

至此,DML操作介绍完毕。


三、DQL--数据查询语言
作用:主要用来查看表中的数据,也是平时使用最多的操作,主要命令为:SELECT
基本用法如下:

mysql> SELECT fields FROM table_name WHERE <condition>;

注意事项:
fields:表示要查询的字段列表,可以使用代替,但是在程序中最好别写,因为使用*一方面会降低SQL的查询效率,查询到一些用不到的字段;另一方面,使用一些ORM框架时,如果数据库中字段有变动,可能会立刻导致程序报错。

1、简单不加条件的单表查询:
用法:

mysql> SELECT * FROM table;

示例略。

2、单表中的条件查询:
常见的条件:>,>=,<,<= ,=,<>,!=,IN,NOT IN,LIKE,NOT LIKE,REGEXP
示例:

#查询年龄大于23的记录
mysql> SELECT * FROM student WHERE age > 23;
#查询年龄大于等于24的记录,和上面age>23结果相同
mysql> SELECT * FROM student WHERE age >= 24;
#查询年龄小于24的记录
mysql> SELECT * FROM student WHERE age < 24;
#查询年龄小于等于24的记录
mysql> SELECT * FROM student WHERE age <= 24;
#查询姓名等于xiaohong的记录
mysql> SELECT * FROM student WHERE name = 'xiaohong';
#查询姓名不等于xiaohong的记录
mysql> SELECT * FROM student WHERE name <> 'xiaohong'; 
#查询姓名不等于xiaohong的记录
mysql> SELECT * FROM student WHERE name != 'xiaohong';
#查询姓名为xiaohong或者xiaohui的记录
mysql> SELECT * FROM student WHERE name in ('xiaohong','xiaohui');
#查询姓名不是xiaohong和xiaohui的记录等价于:where name != xiaohong and name != xiaohui
mysql> SELECT * FROM student WHERE name not in ('xiaohong','xiaohui');
#查询姓名以xiao开头的记录
mysql> SELECT * FROM student WHERE name like 'xiao%';
#查询姓名以xiaohon开头的记录,后面模糊匹配一位,如:xiaohong,xiaohoni
mysql> SELECT * FROM student WHERE name like 'xiaohon_';
#查询姓名中包含ao字符创的记录
mysql> SELECT * FROM student WHERE name like '%ao%';
#查询以hong结尾的记录
mysql> SELECT * FROM student WHERE name not like '%hong';
#使用正则表达式查询姓名以xiao开头的记录
mysql> SELECT * FROM student WHERE name REGEXP('^xiao');
#使用正则表达式查询姓名以hong结尾的记录
mysql> SELECT * FROM student WHERE name REGEXP('hong$');
#支持的其他复杂的正则表达式,请参阅资料:

正则表达式教程:http://www.runoob.com/regexp/...
注意:
(1)当某个字段上有索引时,使用上述的反向查询或者前模糊查询,如:<>,!=,NOT LIKE,NOT IN,LIKE "%test",将会不走索引;
(2)查询中的潜在问题:如果某个字段在创建表结构的时候未设置非空,则使用WHERE name!="BING"的时候,将不会包含name为NULL的记录;

示例:查询student表中年龄大于"xiaohong"年龄的记录的数量:

mysql> SELECT COUNT(*) FROM student WHERE age > (SELECT age FROM student WHERE name = 'xiaohong');
+----------+
| COUNT(*) |
+----------+
|        2 |
+----------+
1 row in set (0.46 sec)

3、分页查询:
用法:

mysql> SELECT * FROM table_name LIMIT start,num;

参数解释:
start:开始位置,默认从0开始;
num:偏移量,即:从开始位置向后查询的数据条数;
示例:查询test表中,第二页的数据,每页显示10条,如下:

mysql> SELECT * FROM student LIMIT 1,10;

4、使用ORDER BY对查询结果进行排序:
用法:

SELECT * FROM table_name <where condition> ORDER BY <field> ASC/DESC;

示例:从student表中查询出所有年龄大于20的学生记录,并且按照年龄age倒序排列,如下:

SELECT * FROM student WHERE age > 20 ORDER BY age DESC;

注意:如果在排序时ORDER BY <field>之后没有添加DESC和ASC关键字,默认按照ASC升序排列;

5、使用GROUP BY对查询结果集进行分组
基本用法:

mysql> SELECT res FROM table_name <where condition> GROUP BY <field>;

示例:查询student表中男生和女生的数量:

mysql> SELECT gender,COUNT(*) FROM student GROUP BY gender;

6、使用GROUP BY之后,在使用HAVING完成分组之后的条件查询
基本用法:

SELECT res FROM table_name <where condition> GROUP BY <field> <having condition>;

示例:查询student_course表中有3门成绩大于等于80的学生学号
(1)创建测试表结构:

mysql> CREATE TABLE student_course(
    sno INT(11) NOT NULL,
    cno INT(11) NOT NULL,
    grade SMALLINT NOT NULL DEFAULT 0
)ENGINE = InnoDB DEFAULT CHARSET = UTF8;

(2)插入测试数据:

INSERT INTO student_course(sno,cno,grade) VALUES(1,100,79);
INSERT INTO student_course(sno,cno,grade) VALUES(1,101,89);
INSERT INTO student_course(sno,cno,grade) VALUES(1,102,87);
INSERT INTO student_course(sno,cno,grade) VALUES(1,103,99);
INSERT INTO student_course(sno,cno,grade) VALUES(2,100,90);
INSERT INTO student_course(sno,cno,grade) VALUES(2,101,80);
INSERT INTO student_course(sno,cno,grade) VALUES(2,102,77);
INSERT INTO student_course(sno,cno,grade) VALUES(2,103,79);
INSERT INTO student_course(sno,cno,grade) VALUES(3,100,89);
INSERT INTO student_course(sno,cno,grade) VALUES(3,101,90);
INSERT INTO student_course(sno,cno,grade) VALUES(3,102,83);
INSERT INTO student_course(sno,cno,grade) VALUES(3,103,91);

(3)查询:

mysql> SELECT sno,SUM(CASE WHEN grade > 80 THEN 1 ELSE 0 END) num FROM student_course GROUP BY sno HAVING num >= 3;     
+-----+------+
| sno | num  |
+-----+------+
|   1 |    3 |
|   3 |    4 |
+-----+------+
2 rows in set (0.45 sec)


四、DCL--数据控制语言
作用:用来授予或回收访问数据库的某种特权,并控制数据库操纵事务发生的时间及效果。
1、GRANT授予用户权限:
基本用法:

mysql> GRANT priv_type ON <object_type> TO user <WITH {GRANT OPTION | resource_option} ...>;

示例:给用户jerry授予对test_db数据库的增删改查权限,允许该用户从IP为'192.168.0.10'的网络登录
(1)方法一:

mysql> GRANT INSERT,SELECT,UPDATE,DELETE ON test_db.* TO 'jerry'@'192.168.0.10' IDENTIFIED BY 'password' WITH GRANT OPTION;

(2)方法二:

mysql> CREATE USER 'jerry'@'192.168.0.10' IDENTIFIED BY 'password';
mysql> GRANT INSERT,SELECT,UPDATE,DELETE ON test_db.* TO 'jerry'@'192.168.0.10';

2、REVOKE收回用户权限:
基本用法:

mysql> REVOKE priv_type ON <object_type> FROM 'jerry'@'192.168.0.10';

示例:收回用户对test_db库的删除权限:

mysql> REVOKE DELETE ON test_db.* FROM 'jerry'@'192.168.0.10';

3、查看给某个用户所授予的权限:
基本用法:

mysql> SHOW GRANTS FOR user;

示例:查询给'jerry'@'192.168.0.10'所授予的所有权限:

mysql> SHOW GRANTS FOR 'jerry'@'192.168.0.10';

4、查询可授予的所有权限,使用技巧:

(1)首先将某个库(如:test_db)的所有权限授予给用户'jerry'@'localhost'

mysql> GRANT ALL ON test_db.* TO 'jerry'@'localhost' IDENTIFIED BY 'jerry';

(2)收回某个权限,如:查询权限

mysql> REVOKE SELECT ON test_db.* FROM 'jerry'@'localhost';

(3)查看剩余权限,就可以查到除了查询权限之外的权限,再加上查询权限即可授予的所有权限

 mysql> SHOW GRANTS FOR 'jerry'@'localhost';

至此,MySQL的基本操作DDL,DML,DQL,DCL介绍完毕。
下面几次次章节专门介绍触发器、存储过程和函数,大家有什么想法或者希望重点介绍MySQL的哪个模块可以在下方留言,一块学习数据库,欢迎转发~
后续更多文章将更新在个人小站上,欢迎查看。

另外提供一些优秀的IT视频资料,可免费下载!如需要请查看https://www.592xuexi.com

查看原文

entner 赞了文章 · 10月14日

MySQL基本操作之-DDL,DML,DQL,DCL

MySQL基本操作之DDL(数据定义语言),DML(数据操纵语言),DQL(数据查询语言),DCL(数据控制语言)微信搜索公众号:”菜鸟封神记“,定期分享一线大厂常用技术干货。

一、DDL--数据定义语言
作用:数据定义语言主要用来定义数据库中的各类对象,包括用户、库、表、视图、索引、触发器、事件、存储过程和函数等。
常见的DDL操作的基本用法如下:

   CREATE USER           #创建用户
   CREATE DATABASE       #创建数据库
   CREATE TABLE          #创建表
   CREATE VIEW           #创建视图
   CREATE INDEX          #创建索引
   CREATE TRIGGER        #创建触发器
   CREATE EVENT          #创建事件
   CREATE PROCEDURE      #创建存储过程
   CREATE FUNCTION       #创建自定义函数
   ...其他不常用的DDL(如:TABLESPACE)操作可自行查阅资料...

1、创建用户:
详细用法:CREATE USER 'username'@'[ip/domain/netmask]'

参数解释:
username:表示登陆MySQL实例的用户名
[ip/domain/ip range]:表示数据库实例允许的登陆ip,域名或者ip段

示例:创建一个名称为bingwang,登陆ip为192.168.0.10的用户:

mysql> CREATE USER 'bingwang'@'192.168.0.10';

2、创建数据库,示例如下:

详细用法:

CREATE DATABASE db_name;

示例如下:

#创建一个名称为test_db,字符集为utf8的数据库
mysql> CREATE DATABASE test_db DEFAULT CHARSET UTF8;

3、创建表:

详细用法:CREATE TABLE table_name;
示例如下:

#创建一个名称为t_test,字符集为utf8,存储引擎为InnoDB,字符校验集为utf8_general_ci的表:
mysql> CREATE TABLE t_test (
           id INT NOT NULL AUTO_INCREMENT,
           name VARCHAR(50),
           PRIMARY KEY(id)
       ) ENGINE = InnoDB DEFAUL CHARSET = UTF8 COLLATE = utf8_general_ci;

4、创建视图:

详细用法:

CREATE VIEW view_name as <SELECT phrase>;
参数解释: <SELECT phrase>:查询语句

示例如下:

#创建一个视图t_view,用来查询t_test中的ID为1或者2的数据:
mysql> CREATE VIEW test_view AS SELECT * FROM t_test WHERE id IN (1,2);
#查看创建视图的过程:
mysql> SHOW CREATE VIEW test_view;

5、创建索引,有两种方法,CREATE和ALTER,下面先介绍一下CREATE:

详细用法:

CREATE [UNIQUE] INDEX index_name ON table_name(field[num]) <OPTIONS>;

参数解释: 
UNIQUE:表示创建的索引类型为唯一索引,如果创建的为一般索引可以忽略该选项
table_name:表名称
field:表中的某个字段。num为可选参数,如果field为字符创类型,表示给该字段的前num个字符创建索引
OPTIONS:表示可选选项,可以指定索引使用的算法,比如:USING BTREE。不指定默认为BTREE;

示例如下:
(1)给t_test表中的name字段添加一个唯一索引,使用BTREE作为其索引算法:

mysql> CREATE UNIQUE INDEX name_ind ON t_test(name) USING BTREE;
mysql> SHOW [INDEX/INDEXES] FROM t_test;  #查看t_test表中的索引,[INDEX/INDEXES]两个关键字都可以

(2)给t_test表中的name字段的前5个字符创建一般索引,使用BTREE作为其索引算法:

mysql> CREATE INDEX name_index ON t_test(name(5));

关于索引的更多用法及优化在后面的文章中会详细讲解。

6、创建触发器:
详细用法:

CREATE TRIGGER trigger_name trigger_time trigger_event FOR EACH ROW 
BEGIN 
  trigger_stmt 
END;

示例:创建触发器内容稍多,此处先稍微提一下,后面专门章节介绍;

7、创建存储过程:
详细用法:

CREATE PROCEDURE procedure_name([proc_parameter[,...]])
BEGIN
  ...存储过程体
END

示例:创建存储过程内容稍多,此处先稍微提一下,后面专门章节介绍;
8、创建自定义函数:
详细用法:

CREATE FUNCTION function_name([func_parameter[,...]])
RETURNS type
BEGIN
...函数体
END

示例:创建自定义函数内容稍多,此处先稍微提一下,后面专门章节介绍;
至此,简单的DDL操作介绍完成。


二、DML--数据操纵语言:
作用:用来操作数据库中的表对象,主要包括的操作有:INSERT,UPDATE,DELETE
常见的DML的基本操作方法如下:

#给表中添加数据
INSERT INTO ...
#修改表中的数据
UPDATE table_name SET ...
#删除表中的数据
DELETE FROM table_name WHERE <condition>;
注:<condition>:表示DML操作时的条件

1、向表中插入数据:
详细用法:

mysql> INSERT INTO table_name(field1,field2,[,...]) values(value1,value2),(value3,value4),...;

示例:向学生表中插入一条数据,name:'xiaohong', age:24, gender:'M' ,如下:
(1)创建表:

 mysql> CREATE TABLE student(
        id INT PRIMARY KEY AUTO_INCREMENT,
        name VARCHAR(50) NOT NULL DEFAULT '',
        age TINYINT,
        gender ENUM('F','M')
     ) ENGINE = InnoDB DEFAULT CHARSET = UTF8;

(2)插入数据:

mysql> INSERT INTO student(name,age,gender) VALUES('xiaohong',24,'M');
Query OK, 1 row affected (0.09 sec)
mysql> SELECT * FROM student;
+----+----------+------+--------+
| id | name     | age  | gender |
+----+----------+------+--------+
|  1 | xiaohong |   24 | M      |
+----+----------+------+--------+
1 row in set (0.37 sec)
注:主键如果自动递增,插入时可不用指定;

2、修改表中的数据:
详细用法:

UPDATE table_name SET field1 = value1, field2 = value2, ... ,  WHERE <condition>;

示例:将student表中id为1的记录中的name值修改为:"xiaohua",如下:

mysql> UPDATE STUDENT SET name = 'xiaohua' WHERE id = 1;
Query OK, 1 row affected (0.67 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> SELECT * FROM student;
+----+---------+------+--------+
| id | name    | age  | gender |
+----+---------+------+--------+
|  1 | xiaohua |   24 | M      |
+----+---------+------+--------+
1 row in set (0.00 sec)

注意:此处修改的时候,一定得注意加条件,否则整个表都会被改。讲个技巧:在MySQL的命令中执行操作的时候,可以在登录MySQL时添加-U选项,如果忘加条件,会被阻止执行sql语句。登录命令如下:

    [root@WB-BLOG ~]# mysql -uroot -proot -U -h127.0.0.1 -P3306
    mysql> UPDATE STUDENT SET name = 'hong';
    ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column

可见:登录之后如果不加条件执行UPDATE语句,会被阻止;

3、删除表中的数据:
详细用法:

mysql> DELETE FROM table_name WHERE <condition>;

示例:删除student表中id为1的记录,如下:

mysql> DELETE FROM student WHERE id = 1;
Query OK, 1 row affected (0.37 sec)
mysql> SELECT * FROM student;
Empty set (0.00 sec)

注意:注意!注意!!再注意!!!,该操作非常危险,命令行中操作时,需要万分注意。可以使用登录时加-U参数的方式,防止忘加条件而删除所有数据,加了-U参数之后,如果不加条件,会被阻止,执行结果如下:

mysql> DELETE FROM student;
ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column

至此,DML操作介绍完毕。


三、DQL--数据查询语言
作用:主要用来查看表中的数据,也是平时使用最多的操作,主要命令为:SELECT
基本用法如下:

mysql> SELECT fields FROM table_name WHERE <condition>;

注意事项:
fields:表示要查询的字段列表,可以使用代替,但是在程序中最好别写,因为使用*一方面会降低SQL的查询效率,查询到一些用不到的字段;另一方面,使用一些ORM框架时,如果数据库中字段有变动,可能会立刻导致程序报错。

1、简单不加条件的单表查询:
用法:

mysql> SELECT * FROM table;

示例略。

2、单表中的条件查询:
常见的条件:>,>=,<,<= ,=,<>,!=,IN,NOT IN,LIKE,NOT LIKE,REGEXP
示例:

#查询年龄大于23的记录
mysql> SELECT * FROM student WHERE age > 23;
#查询年龄大于等于24的记录,和上面age>23结果相同
mysql> SELECT * FROM student WHERE age >= 24;
#查询年龄小于24的记录
mysql> SELECT * FROM student WHERE age < 24;
#查询年龄小于等于24的记录
mysql> SELECT * FROM student WHERE age <= 24;
#查询姓名等于xiaohong的记录
mysql> SELECT * FROM student WHERE name = 'xiaohong';
#查询姓名不等于xiaohong的记录
mysql> SELECT * FROM student WHERE name <> 'xiaohong'; 
#查询姓名不等于xiaohong的记录
mysql> SELECT * FROM student WHERE name != 'xiaohong';
#查询姓名为xiaohong或者xiaohui的记录
mysql> SELECT * FROM student WHERE name in ('xiaohong','xiaohui');
#查询姓名不是xiaohong和xiaohui的记录等价于:where name != xiaohong and name != xiaohui
mysql> SELECT * FROM student WHERE name not in ('xiaohong','xiaohui');
#查询姓名以xiao开头的记录
mysql> SELECT * FROM student WHERE name like 'xiao%';
#查询姓名以xiaohon开头的记录,后面模糊匹配一位,如:xiaohong,xiaohoni
mysql> SELECT * FROM student WHERE name like 'xiaohon_';
#查询姓名中包含ao字符创的记录
mysql> SELECT * FROM student WHERE name like '%ao%';
#查询以hong结尾的记录
mysql> SELECT * FROM student WHERE name not like '%hong';
#使用正则表达式查询姓名以xiao开头的记录
mysql> SELECT * FROM student WHERE name REGEXP('^xiao');
#使用正则表达式查询姓名以hong结尾的记录
mysql> SELECT * FROM student WHERE name REGEXP('hong$');
#支持的其他复杂的正则表达式,请参阅资料:

正则表达式教程:http://www.runoob.com/regexp/...
注意:
(1)当某个字段上有索引时,使用上述的反向查询或者前模糊查询,如:<>,!=,NOT LIKE,NOT IN,LIKE "%test",将会不走索引;
(2)查询中的潜在问题:如果某个字段在创建表结构的时候未设置非空,则使用WHERE name!="BING"的时候,将不会包含name为NULL的记录;

示例:查询student表中年龄大于"xiaohong"年龄的记录的数量:

mysql> SELECT COUNT(*) FROM student WHERE age > (SELECT age FROM student WHERE name = 'xiaohong');
+----------+
| COUNT(*) |
+----------+
|        2 |
+----------+
1 row in set (0.46 sec)

3、分页查询:
用法:

mysql> SELECT * FROM table_name LIMIT start,num;

参数解释:
start:开始位置,默认从0开始;
num:偏移量,即:从开始位置向后查询的数据条数;
示例:查询test表中,第二页的数据,每页显示10条,如下:

mysql> SELECT * FROM student LIMIT 1,10;

4、使用ORDER BY对查询结果进行排序:
用法:

SELECT * FROM table_name <where condition> ORDER BY <field> ASC/DESC;

示例:从student表中查询出所有年龄大于20的学生记录,并且按照年龄age倒序排列,如下:

SELECT * FROM student WHERE age > 20 ORDER BY age DESC;

注意:如果在排序时ORDER BY <field>之后没有添加DESC和ASC关键字,默认按照ASC升序排列;

5、使用GROUP BY对查询结果集进行分组
基本用法:

mysql> SELECT res FROM table_name <where condition> GROUP BY <field>;

示例:查询student表中男生和女生的数量:

mysql> SELECT gender,COUNT(*) FROM student GROUP BY gender;

6、使用GROUP BY之后,在使用HAVING完成分组之后的条件查询
基本用法:

SELECT res FROM table_name <where condition> GROUP BY <field> <having condition>;

示例:查询student_course表中有3门成绩大于等于80的学生学号
(1)创建测试表结构:

mysql> CREATE TABLE student_course(
    sno INT(11) NOT NULL,
    cno INT(11) NOT NULL,
    grade SMALLINT NOT NULL DEFAULT 0
)ENGINE = InnoDB DEFAULT CHARSET = UTF8;

(2)插入测试数据:

INSERT INTO student_course(sno,cno,grade) VALUES(1,100,79);
INSERT INTO student_course(sno,cno,grade) VALUES(1,101,89);
INSERT INTO student_course(sno,cno,grade) VALUES(1,102,87);
INSERT INTO student_course(sno,cno,grade) VALUES(1,103,99);
INSERT INTO student_course(sno,cno,grade) VALUES(2,100,90);
INSERT INTO student_course(sno,cno,grade) VALUES(2,101,80);
INSERT INTO student_course(sno,cno,grade) VALUES(2,102,77);
INSERT INTO student_course(sno,cno,grade) VALUES(2,103,79);
INSERT INTO student_course(sno,cno,grade) VALUES(3,100,89);
INSERT INTO student_course(sno,cno,grade) VALUES(3,101,90);
INSERT INTO student_course(sno,cno,grade) VALUES(3,102,83);
INSERT INTO student_course(sno,cno,grade) VALUES(3,103,91);

(3)查询:

mysql> SELECT sno,SUM(CASE WHEN grade > 80 THEN 1 ELSE 0 END) num FROM student_course GROUP BY sno HAVING num >= 3;     
+-----+------+
| sno | num  |
+-----+------+
|   1 |    3 |
|   3 |    4 |
+-----+------+
2 rows in set (0.45 sec)


四、DCL--数据控制语言
作用:用来授予或回收访问数据库的某种特权,并控制数据库操纵事务发生的时间及效果。
1、GRANT授予用户权限:
基本用法:

mysql> GRANT priv_type ON <object_type> TO user <WITH {GRANT OPTION | resource_option} ...>;

示例:给用户jerry授予对test_db数据库的增删改查权限,允许该用户从IP为'192.168.0.10'的网络登录
(1)方法一:

mysql> GRANT INSERT,SELECT,UPDATE,DELETE ON test_db.* TO 'jerry'@'192.168.0.10' IDENTIFIED BY 'password' WITH GRANT OPTION;

(2)方法二:

mysql> CREATE USER 'jerry'@'192.168.0.10' IDENTIFIED BY 'password';
mysql> GRANT INSERT,SELECT,UPDATE,DELETE ON test_db.* TO 'jerry'@'192.168.0.10';

2、REVOKE收回用户权限:
基本用法:

mysql> REVOKE priv_type ON <object_type> FROM 'jerry'@'192.168.0.10';

示例:收回用户对test_db库的删除权限:

mysql> REVOKE DELETE ON test_db.* FROM 'jerry'@'192.168.0.10';

3、查看给某个用户所授予的权限:
基本用法:

mysql> SHOW GRANTS FOR user;

示例:查询给'jerry'@'192.168.0.10'所授予的所有权限:

mysql> SHOW GRANTS FOR 'jerry'@'192.168.0.10';

4、查询可授予的所有权限,使用技巧:

(1)首先将某个库(如:test_db)的所有权限授予给用户'jerry'@'localhost'

mysql> GRANT ALL ON test_db.* TO 'jerry'@'localhost' IDENTIFIED BY 'jerry';

(2)收回某个权限,如:查询权限

mysql> REVOKE SELECT ON test_db.* FROM 'jerry'@'localhost';

(3)查看剩余权限,就可以查到除了查询权限之外的权限,再加上查询权限即可授予的所有权限

 mysql> SHOW GRANTS FOR 'jerry'@'localhost';

至此,MySQL的基本操作DDL,DML,DQL,DCL介绍完毕。
下面几次次章节专门介绍触发器、存储过程和函数,大家有什么想法或者希望重点介绍MySQL的哪个模块可以在下方留言,一块学习数据库,欢迎转发~
后续更多文章将更新在个人小站上,欢迎查看。

另外提供一些优秀的IT视频资料,可免费下载!如需要请查看https://www.592xuexi.com

查看原文

赞 5 收藏 4 评论 0

entner 收藏了文章 · 7月11日

老旧话题:PHP读取超大文件

作为一名常年深耕curd的PHPer,关注内存那是不可能的,反正apache或者fpm都帮我们做了,况且运行一次就销毁,根本就不存在什么内存问题。

然而偏偏就有些个不开眼的人把这些个东西当面试题,比如总有刁民用“php读取一个10G的超大文件”当面试题来问你。当然了,作为一个和我一样的普普通通的蠢货,你听到这个问题的第一瞬间是懵逼,第二瞬间是卧槽,第三瞬间是保持结巴状态。

“面试造火箭,入职拧螺丝”。然而,刚进来就拧螺丝的人如果能够对“PHP读取一个10G的超大文件”有所见解的话,“造火箭”也是迟早的事儿。当前为了能够来这里“拧螺丝”,还是得先搞定“读取10G文件”这个问题。

要想读取10G的文件,首先,你得有个10G的文件

... ...

其实,相对来说也是比较简单的事情,我们随便找一个nginx的日志文件,哪怕只有10KB,假设文件名是test.log,然后呢执行" cat test.log >> test.log ",听我说少年,30秒左右你就该按下ctrl + C了,比如我这里,你们感受一下:

202MB,作为实验演示,够意思了。难不成真要造10G的文件?

首先,我们尝试用php的file函数来作一把死,你们感受一下:

<?php
$begin = microtime( true );
file( './test.log' );
$end = microtime( true );
echo "cost : ".( $end - $begin ).PHP_EOL;

保存为test.php,然后命令行下执行一把,结果如下图所示:

这句英文的大概意思就是“PHP最大只给每个进程分配了128MB内存,然而你特么张口要202MB?”所以,我们修改一下php配置文件... ...

千万不要手软,把这个参数改成1024MB,然后再次执行上面的php脚本:

然后,我们再试试最爱的file_get_contents()函数,结果如下图:

文件已经一次性全部被载入到内存中并将文件的每一行保存到了一个php数组中,我的机器是10G内存+256G固态硬盘,一次性载入这个202MB的文件file函数用了0.67秒钟、file_get_contents函数用了0.25秒钟(看起来file_get_content要比file靠谱的多),不过,敲重点的我们调整了配置文件才可以读取202MB的文件,如果摆在我们面前的是一个100G的文件呢?或者说,系统提供的php配置最多之给20MB内存而你又无法修改呢?

我们重点是如何在内存有限的机器上读取体积几百倍于内存的文件。下面,我们把memory_limit调整成16M,开启困难模式。

202MB的文件,允许被分配的内存为16MB,所以,总体思路其实也很简单,就是一点儿一点儿地读,只要每次读取的内容小于16MB,那就一定不会有问题,首先我们感受一下一个字符一个字符读,出场嘉宾是fgetc函数:

<?php
$begin = microtime( true );
$fp = fopen( './test.log' );
while( false !== ( $ch = fgetc( $fp ) ) ){
  // ⚠️⚠️⚠️ 作为测试代码是否正确,你可以打开注释 ⚠️⚠️⚠️
  // 但是,打开注释后屏显字符会严重拖慢程序速度!也就是说程序运行速度可能远远超出屏幕显示速度
  //echo $char.PHP_EOL;
}
fclose( $fp );
$end = microtime( true );
echo "cost : ".( $end - $begin ).PHP_EOL;

运行结果如下图:

虽然只有给了16M内存,但我们还是成功将202M文件全部读出来了,只不过这个运行速度是差了那么点儿意思,不大行。不能一个字母一个字母地读,这次我们一行一行地读:

<?php
$begin = microtime( true );
$fp = fopen( './test.log', 'r' );
while( false !== ( $buffer = fgets( $fp, 4096 ) ) ){
  //echo $buffer.PHP_EOL;
}
if( !feof( $fp ) ){
  throw new Exception('... ...');
}
fclose( $fp );
$end = microtime( true );
echo "cost : ".( $end - $begin ).' sec'.PHP_EOL;

运行结果如下图:

一行一行果然比一个一个字符要快很多,转念一想吧,系统分配给我们的内存上限是16MB,那我们索性一次读取一定量容量数据看看,会不会更快:

<?php
$begin = microtime( true );
$fp = fopen( './test.log', 'r' );
while( !feof( $fp ) ){
  // 如果你要使用echo,那么,你会很惨烈...
  fread( $fp, 10240 );
}
fclose( $fp );
$end = microtime( true );
echo "cost : ".( $end - $begin ).' sec'.PHP_EOL;
exit;

保存代码,运行一把,屌了屌了!!!在内存有限的情况下,我们还把时间缩短到了0.1秒!

然后我们考虑将问题升级一下,依然是上述这个202M的文件,这次我们要求读取倒数后5行的内容,这个问题看起来屌了些许,用原来的fread啥的虽然奏效但总感觉比较愚蠢。所以,现在又得引入全新的函数来解决这个问题:ftell和fseek。其中,ftell用于告知当前文件读取指针所在位置,fseek可以手动设定文件读取指针的位置。我建议大家去手册上重点观摩一下fseek函数:点击这里

<?php
$fp = fopen( './test1.log', 'r' );
$line = 5;
$pos = -2;
$ch = '';
$content = '';
while( $line > 0 ){
  while( $ch != "\n" ){
    fseek( $fp, $pos, SEEK_END );
    $ch = fgetc( $fp );
    $pos--;
  }
  $ch = '';
  $content .= fgets( $fp );
  $line--;
}
echo $content;
exit;

其中test1.log文件的内容如下:

aa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffff
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffff
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffff
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffff
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffff
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffff
1111111111
2222222222

保存文件并运行,结果如下图所示:

查看原文

entner 赞了文章 · 7月11日

老旧话题:PHP读取超大文件

作为一名常年深耕curd的PHPer,关注内存那是不可能的,反正apache或者fpm都帮我们做了,况且运行一次就销毁,根本就不存在什么内存问题。

然而偏偏就有些个不开眼的人把这些个东西当面试题,比如总有刁民用“php读取一个10G的超大文件”当面试题来问你。当然了,作为一个和我一样的普普通通的蠢货,你听到这个问题的第一瞬间是懵逼,第二瞬间是卧槽,第三瞬间是保持结巴状态。

“面试造火箭,入职拧螺丝”。然而,刚进来就拧螺丝的人如果能够对“PHP读取一个10G的超大文件”有所见解的话,“造火箭”也是迟早的事儿。当前为了能够来这里“拧螺丝”,还是得先搞定“读取10G文件”这个问题。

要想读取10G的文件,首先,你得有个10G的文件

... ...

其实,相对来说也是比较简单的事情,我们随便找一个nginx的日志文件,哪怕只有10KB,假设文件名是test.log,然后呢执行" cat test.log >> test.log ",听我说少年,30秒左右你就该按下ctrl + C了,比如我这里,你们感受一下:

202MB,作为实验演示,够意思了。难不成真要造10G的文件?

首先,我们尝试用php的file函数来作一把死,你们感受一下:

<?php
$begin = microtime( true );
file( './test.log' );
$end = microtime( true );
echo "cost : ".( $end - $begin ).PHP_EOL;

保存为test.php,然后命令行下执行一把,结果如下图所示:

这句英文的大概意思就是“PHP最大只给每个进程分配了128MB内存,然而你特么张口要202MB?”所以,我们修改一下php配置文件... ...

千万不要手软,把这个参数改成1024MB,然后再次执行上面的php脚本:

然后,我们再试试最爱的file_get_contents()函数,结果如下图:

文件已经一次性全部被载入到内存中并将文件的每一行保存到了一个php数组中,我的机器是10G内存+256G固态硬盘,一次性载入这个202MB的文件file函数用了0.67秒钟、file_get_contents函数用了0.25秒钟(看起来file_get_content要比file靠谱的多),不过,敲重点的我们调整了配置文件才可以读取202MB的文件,如果摆在我们面前的是一个100G的文件呢?或者说,系统提供的php配置最多之给20MB内存而你又无法修改呢?

我们重点是如何在内存有限的机器上读取体积几百倍于内存的文件。下面,我们把memory_limit调整成16M,开启困难模式。

202MB的文件,允许被分配的内存为16MB,所以,总体思路其实也很简单,就是一点儿一点儿地读,只要每次读取的内容小于16MB,那就一定不会有问题,首先我们感受一下一个字符一个字符读,出场嘉宾是fgetc函数:

<?php
$begin = microtime( true );
$fp = fopen( './test.log' );
while( false !== ( $ch = fgetc( $fp ) ) ){
  // ⚠️⚠️⚠️ 作为测试代码是否正确,你可以打开注释 ⚠️⚠️⚠️
  // 但是,打开注释后屏显字符会严重拖慢程序速度!也就是说程序运行速度可能远远超出屏幕显示速度
  //echo $char.PHP_EOL;
}
fclose( $fp );
$end = microtime( true );
echo "cost : ".( $end - $begin ).PHP_EOL;

运行结果如下图:

虽然只有给了16M内存,但我们还是成功将202M文件全部读出来了,只不过这个运行速度是差了那么点儿意思,不大行。不能一个字母一个字母地读,这次我们一行一行地读:

<?php
$begin = microtime( true );
$fp = fopen( './test.log', 'r' );
while( false !== ( $buffer = fgets( $fp, 4096 ) ) ){
  //echo $buffer.PHP_EOL;
}
if( !feof( $fp ) ){
  throw new Exception('... ...');
}
fclose( $fp );
$end = microtime( true );
echo "cost : ".( $end - $begin ).' sec'.PHP_EOL;

运行结果如下图:

一行一行果然比一个一个字符要快很多,转念一想吧,系统分配给我们的内存上限是16MB,那我们索性一次读取一定量容量数据看看,会不会更快:

<?php
$begin = microtime( true );
$fp = fopen( './test.log', 'r' );
while( !feof( $fp ) ){
  // 如果你要使用echo,那么,你会很惨烈...
  fread( $fp, 10240 );
}
fclose( $fp );
$end = microtime( true );
echo "cost : ".( $end - $begin ).' sec'.PHP_EOL;
exit;

保存代码,运行一把,屌了屌了!!!在内存有限的情况下,我们还把时间缩短到了0.1秒!

然后我们考虑将问题升级一下,依然是上述这个202M的文件,这次我们要求读取倒数后5行的内容,这个问题看起来屌了些许,用原来的fread啥的虽然奏效但总感觉比较愚蠢。所以,现在又得引入全新的函数来解决这个问题:ftell和fseek。其中,ftell用于告知当前文件读取指针所在位置,fseek可以手动设定文件读取指针的位置。我建议大家去手册上重点观摩一下fseek函数:点击这里

<?php
$fp = fopen( './test1.log', 'r' );
$line = 5;
$pos = -2;
$ch = '';
$content = '';
while( $line > 0 ){
  while( $ch != "\n" ){
    fseek( $fp, $pos, SEEK_END );
    $ch = fgetc( $fp );
    $pos--;
  }
  $ch = '';
  $content .= fgets( $fp );
  $line--;
}
echo $content;
exit;

其中test1.log文件的内容如下:

aa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffff
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffff
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffff
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffff
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffff
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffff
1111111111
2222222222

保存文件并运行,结果如下图所示:

查看原文

赞 88 收藏 55 评论 5

entner 收藏了问题 · 2月18日

一个让PHP小白百思不得其解的匿名函数及array_reduce的问题

最近学习PHP,看到了一段代码,其中涉及到了匿名函数以及array_reduce,把代码敲出来用各种方法分析也没想出是怎么调用的,代码如下:

<?php
interface Middleware
{
    public static function handle(Closure $next);
}

class VerifyCsrfToken implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(5)验证Csrf-Token".'<br>';
        $next();
    }
}

class ShareErrorsFromSession implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(4)如果session中有'errors'变量,则共享它".'<br>';
        $next();
    }
}

class StartSession implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(3)开启session,获取数据".'<br>';
        $next();
        echo "(7)保存数据,关闭session".'<br>';
    }
}

class AddQueuedCookiesToResponse implements Middleware
{
    public static function handle(Closure $next)
    {
        $next();
        echo "(8)添加下一次请求需要的cookie".'<br>';
    }
}

class EncryptCookies implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(2)对输入请求的cookie进行解密".'<br>';
        $next();
        echo "(9)对输出相应的cookie进行加密".'<br>';
    }
}

class CheckForMaintenanceMode implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(1)确定当前程序是否处于维护状态".'<br>';
        $next();
    }
}

function getSlice()
{
    return function($stack, $pipe)
    {
        return function() use ($stack, $pipe)
        {
            return $pipe::handle($stack);
        };
    };
}


function then()
{
    $pipes = [
        "CheckForMaintenanceMode",
        "EncryptCookies",
        "AddQueuedCookiesToResponse",
        "StartSession",
        "ShareErrorsFromSession",
        "VerifyCsrfToken"
    ];
    
    $firstSlice = function() {
        echo "(6)请求向路由器传递,返回响应.".'<br>';
    };

    $pipes = array_reverse($pipes);
    $go = array_reduce($pipes, getSlice(),$firstSlice);
    $go();
}
then();
?>

还望有大神能帮忙详解下$go = array_reduce($pipes, getSlice(),$firstSlice);和$go();这两段代码背后的每一步的调用执行流程,以及调用时的参数传递是哪些,如果能用流程图表示就更好啦,谢谢。

entner 发布了文章 · 1月22日

PHP手写MVC (五)—— 路由

路由是一个框架中必不可少的组件,其作用是把 URL 按照预定规则解析到特定控制器中。

我们在这里定义了两种路由规则:

  • 查询字符串。在路径后面使用问号加参数,多个参数用 & 分隔。在配置文件使用 querystring 表示
#控制器/方法?参数1=值1&参数2=值2
http://domain/user/info?name=php&chapter=10
  • 路径,以路径的形式将参数和值添加到后面,中间用 / 分隔。配置中使用 restful
#控制器/方法/参数1/值1/参数2/值2
https://domain/user/info/name/php/chapter/100

主控制器

在目录 core 创建 Controller.php,该类继承 Container

<?php

namespace core;

class Controller extends Container
{
    
}

主控制器可以添加控制器公共方法,如页面渲染 render(),错误代码等,所有控制器必须继承主控制器。由于主控制器继承 Container,因此,控制器也是分发器的子类,可以通过 register() 获取实例。

控制器类

  • 类命名规则

控制器命名遵循大写开头的驼峰命名规则,并且默认添加后缀 Controller,控制器文件命名和类命名一样,如控制器类 UserController,其文件命名为 UserController.php

  • 方法命名规则

方法命名遵循小写开头的驼峰命名规则,并且默认添加请求方式(如,get,post,put等)前缀,如 getIndex()postUpdate()

以上例 UserController 为例

<?php

namespace controller;

use core\Controller;

class UserController extends Controller
{
    /**
     * HTTP 请求方式为 GET 时有效
     * url 为 /user/info
     *
     */
    public function getInfo()
    {
        
    }

    /**
     * HTTP 请求方式为 POST 时有效
     * url 为 /user/update
     *
     */
    public function postUpdate()
    {
        
    }
}

路由解析

core 目录下创建 Router.php

$ cd tinyphp/core
$ touch Router.php

在构造函数中定义变量


<?php

namespace core;

use dispatcher\Container;

class Router extends Container
{
    public $method;
    public $uri;
    public $path;

    public function __construct()
    {
        $this->method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $this->uri = $_SERVER['REQUEST_URI'];
        $this->path = $_SERVER['PATH_INFO'];
    }
}

常见 $_SERVER 字段

  1. $_SERVER['PATH_INFO'] URL的路径信息,如 /user/info
  2. $_SERVER['REQUEST_METHOD'] 请求方法,如 POST,GET
  3. $_SERVER['REQUEST_URI'] 完整 URL,如 /user/info?id=1&name=Lucy

start() 方法中解析 URL

protected function start()
{
    /**
     * 也可以写成 Config::get('default.route','querystring');
     *
     */
    $route = Config::get('default.route') ?? 'querystring';

    //解析 controller 和 action
    $path = explode('/',trim($this->path,'/'));

    if (empty($path[0])) {
        $path[0] = Config::get('default.controller','index');
    }
    $controller = ucfirst($path[0]).'Controller';

    //获取请求方法
    $method = strtolower($this->method);
    $action = $method.ucfirst($path[1] ?? Config::get('default.action','index'));
    //获取参数
    $args = [];
    if (method_exists($this,$route)) {
        $args = call_user_func_array([$this,$route],[$this->uri]);
    }
    return ['controller'=>$controller,'action'=>$action,'args'=>$args];
}

querystring() 参数解析


private function querystring($url)
{
    $urls = explode('?', $url);
    if (empty($urls[1])) {
        return [];
    }
    $param_arr = [];
    $param_tmp = explode('&', $urls[1]);
    if (empty($param_tmp)) {
        return [];
    }
    foreach ($param_tmp as $param) {
        if (strpos($param, '=')) {
            list($key,$value) = explode('=', $param);
            //变量名是否复合规则
            if (preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $key)) {
                $param_arr[$key] = $value;
            }
        }
    }
    return $param_arr;
}

querystring 的参数为 ? 后面的部分,多个参数用 & 分隔。

restful() 参数解析

private function restful($url)
{
    $path = explode('/', trim(explode('?', $url)[0], '/'));
    $params = [];
    $i = 2;
    while (1) {
        if (!isset($path[$i])) {
            break;
        }
        $params[$path[$i]] = $path[$i+1] ?? '';
        $i = $i+2;
    }
    return $params;
}

restful 的参数为方法后面的路径。

完整代码如下:

<?php

namespace core;

use dispatcher\Container;

class Router extends Container
{
    public $method;
    public $uri;
    public $path;

    public function __construct()
    {
        $this->method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $this->uri = $_SERVER['REQUEST_URI'];
        $this->path = $_SERVER['PATH_INFO'];
    }

    protected function start()
    {
        $route = Config::get('default.route') ?? 'querystring';
    
        //解析 controller 和 action
        $path = explode('/',trim($this->path,'/'));
    
        if (empty($path[0])) {
            $path[0] = Config::get('default.controller','index');
        }
        $controller = ucfirst($path[0]).'Controller';
    
        //获取请求方法
        $method = strtolower($this->method);
        $action = $method.ucfirst($path[1] ?? Config::get('default.action','index'));
        
        //获取参数
        $args = [];
        if (method_exists($this,$route)) {
            $args = call_user_func_array([$this,$route],[$this->uri]);
        }
        return ['controller'=>$controller,'action'=>$action,'args'=>$args];
    }
    
    /**
     * 查询字符串参数
     * ?后,参数通过&&分隔
     *
     */
    private function querystring($url)
    {
        $urls = explode('?', $url);
        if (empty($urls[1])) {
            return [];
        }
        $param_arr = [];
        $param_tmp = explode('&', $urls[1]);
        if (empty($param_tmp)) {
            return [];
        }
        foreach ($param_tmp as $param) {
            if (strpos($param, '=')) {
                list($key,$value) = explode('=', $param);
                //变量名是否复合规则
                if (preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $key)) {
                    $param_arr[$key] = $value;
                }
            }
        }
        return $param_arr;
    }
    /**
     * 路径参数
     * 控制器/方法/参数1/值1/参数2/值2
     *
     */
    http://domain/user/info/name/entner?name=php&chapter=10
    private function restful($url)
    {
        $path = explode('/', trim(explode('?', $url)[0], '/'));
        $params = [];
        $i = 2;
        while (1) {
            if (!isset($path[$i])) {
                break;
            }
            $params[$path[$i]] = $path[$i+1] ?? '';
            $i = $i+2;
        }
        return $params;
    }
}

路由调用方式为

<?php

$router = Rouer::start();

测试路由

在配置文件 app/conf/config.php 中设置默认路由为 querystring

<?php

return [
    'default' => [
        'controller' => 'index',
        'action' => 'index',
        'route' => 'querystring',//还可以设置为 restful
    ],
    'view' => [
        'dir' => 'layout',
        'file' => 'base',
    ]
];

core/Application.php 文件中 run() 方法实现路由调用

<?php
...
public function run()
{
    $router = Router::start();
    echo '<pre>';
    print_r($router);
}
...

启动 PHP 内置服务器

$ cd tinyphp/public
$ php -S localhost:8080

在浏览器中输入 http://localhost:8080/course/document?name=php&&chapter=10
输出结果为

Array
(
    [controller] => CourseController
    [action] => getDocument
    [args] => Array
        (
            [name] => php
            [chapter] => 10
        )
)

同理可以测试 restful 路由规则。

调用控制器方法

路由解析后,获得需要调用的控制器名,方法和参数。由于控制器继承分发器后,可以通过 register() 获取实例,编辑 core/Applicaiton.php

<?php

...
public function run()
{
    $router = Router::start();
    //注意使用命名空间
    $controller = "controller\\".$router['controller'];
    $action = $router['action'];
    $args = $router['args'];
    
    echo call_user_func_array([$controller::register(),$action],$args);
}
...

通过这种方式可以实现方法调用,但是无法控制方法参数,比如,有时候我们需要在方法参数中使用某个对象实例,术语称为依赖注入,即把需要使用的实例注入到方法中,那么可以通过PHP的高级特性反射来实现。

查看原文

赞 1 收藏 0 评论 0

entner 发布了文章 · 1月22日

PHP手写MVC (四)—— 配置

通常一个框架会提供一种获取配置文件的操作类,该类的作用是,加载用户自定义配置文件,读取和设置配置文件等操作。

我们规定用户配置文件目录为 origin/app/conf,在该目录下,config.php 是默认的配置文件,此外,还可以在该目录下创建其他配置文件,例如 db.phpserver.php 等,所有其他文件也会被自动加载。

定义规则

配置文件内容直接返回数组。示例

app/conf/config.php

<?php
return [
    'default' => [
        'controller' => 'index',
        'action' => 'index',
        'router' => 'querystring',
    ],
    'debug' => true,
    'cache' => 'redis',
];

app/conf/db.php

<?php
return [
    'mysql' => [
        'host'      => '127.0.0.1',
        'username'  => '',
        'password'  => '',
        'db'        => '',
        'port'      => 3306,
        'charset'   => 'utf8',
        'prefix'    => ''
    ],
    'redis' => [
        'scheme' => '',
        'host'   => '',
        'port'   => '',
    ]
];

使用规则

通过 Config::get($key) 来获取配置。

  • config.php 作为默认配置文件可以直接获取该配置文件内容,多维数组通过.分隔,例如
<?php

Config::get('debug');
Config::get('default.route');
  • db.php 或其他自定义配置文件,需要添加文件名前缀,例如
<?php

Config::get('db.mysql.host');
Config::get('db.redis.scheme');

Config 类

Config 类主要实现以下功能:

  • 加载用户配置文件
<?php

namespace core;

use dispatcher\Container;

class Config extends Container
{
    private $conf;
    
    public function __construct()
    {
        $conf = [];
        $path = APP_PATH.'/app/conf/';
        foreach (scandir($path) as $file) {
            if (substr($file,-4) != '.php') {
                continue;
            }
            $filename = $path.$file;
            
            if ($file == 'config.php') {
                //1
                $conf += require $filename;
            } else {
                //2
                $k = explode('.', $file)[0];
                $conf[$k] = require $filename;
            }
        }
        $this->conf = $conf;
    }

由于继承 Container,构造函数只执行一次,因此在 __construct() 中加载配置文件可以避免重复加载。

除了 config.php 外的其他配置文件,需要用文件名作为 key

  • 解析多维 key 和使用默认值
//1
protected function get($key = null, $default=null)
{
    //2
    if (empty($key)) {
        return $this->conf;
    }
    //3
    if (strpos($key, '.')) {
        $conf = $this->conf;
        foreach (explode('.', $key) as $k) {
            $conf = $conf[$k] ?? $default;
        }
        return $conf;
    }
    //4
    return $this->conf[$key] ?? $default;
}
  1. 使用 :: 执行方法时,该方法需要声明为 protected,如 Config::get($key);
  2. 如果 $key 为空,则返回所有配置。
  3. 通过 . 来解析多维数组配置。
  4. 如果为找到对应值,根据是否设置默认值返回结果。
查看原文

赞 0 收藏 0 评论 0

entner 关注了专栏 · 2019-12-23

Grace development

记录分享开发、学习中的点点滴滴

关注 4669

entner 发布了文章 · 2019-12-22

PHP手写MVC(三) —— 分发器

丑话说在前面,这一章难点,但也算是框架的核心了,大家静下心学吧。

分发器

看一个概念分发器,英文叫dispather,它的主要作用是管理所有类的实例化操作。在程序中,有时需要大量创建对象实例,有时需要使用相同的实例,因此如果重复创建实例会额外给程序带来开销和管理的负担。所以我们使用了分发器的概念,用于专门管理所有类的实例化操作。之前,我们都是通过 new 关键字创建对象,如入口文件的 new Application(),以及 Application 类的构造器中 new Bootstrap()。如果有分发器就简单多了。

core 目录下创建目录 dispatcher,并创建文件 Dispatcher.php,Container.php,Box.php

$ cd origin/core
$ mkdir dispatcher
$ cd dispatcher
$ touch Dispatcher.php Container.php Box.php

Dispatcher 是一个抽象类,定义了一个抽象方法 getInstance(),用于获取子类实例。子类 Container 和 Box 分别继承 Dispatcher 并实现 getInstance() 方法,不同点在于,容器中该方法需要存放子类实例,盒子中直接进行实例化操作。

  • 容器 Container。容器中的实例可以重复使用
  • 盒子 Box。盒子中的实例不能重复使用,每次会创建新的实例

容器就是存东西的变量 在这里我们存放所有调用类的实例,当然也可以存服务,接口取决于你 everything

Dispatcher 类

Dispatcher 类主要实现:

  • 定义抽象方法 getInstance()
abstract function getInstance();

注意抽象方法必须使用关键字 abstract 声明,并且不能包含函数体(中括号包括的部分)

  • 接收所有静态方式请求__callStatic()
public static function __callstatic(string $method, array $args)
{
    //1
    $instance = static::getInstance();
    //2
    return call_user_func_array(array($instance, $method), $args);
}

__callstatic 该方法的作用是,你在其他文件调用的不可见的静态方法都会执行该方法,所以它也是分发器入口。所有通过静态方式调用的不可见的类方法都会执行该函数,如 Config::get('db')

1,第一行 static::getInstance(); 执行子类的 getInstance() 方法,因此需要在 ContainerBox 中分别实现该方法。

2,通过 call_user_func_array() 调用自身存在的方法,该方法需要声明为 protected

  • 使用延迟静态绑定实例化子类
public static function newObject()
{
    return new Static;
}

该函数的作用是实例化操作,在子类 ContainerBox 中调用,new Static 返回创建具体执行操作的该子类实例,如 Config::get() 那么返回 Config 类的实例,Router::start() 返回 Router 类的实例。

关于延迟静态绑定 很重要 我下面会继续讲 请耐心看

  • 定义获取类实例函数 register()
public static function register()
{
    return static::getInstance();
}

该函数获取子类实例。

Container 类

Container 类继承 Dispatcher,同时实现 getInstance() 方法。
该类使用一个静态变量 $container 来保存所有类实例,当再次调用该实例时直接返回,无需在实例化操作。

编辑 Container.php

<?php

namespace dispatcher;

/**
 * 继承 Dispatcher 必须实现 getInstance() 方法
 *
 */
class Container extends Dispatcher
{
    //存储子类实例
    public static $container = [];

    /**
     * 实现父类抽象方法
     *
     * 如果容器中已存在子类实例,直接返回
     */
    public function getInstance()
    {
        //获取子类名称
        $class = get_called_class();

        if (!isset(self::$container[$class]) ||
                !self::$container[$class] instanceof $class) {

            self::$container[$class] = self::newObject();
        }
        return self::$container[$class];
    }
}

get_called_class() 获取调用类的名称。

Box 类

Container 类一样,Box 继承 Dispatcher,同时实现 getInstance() 方法

编辑 Box.php

<?php

namespace dispatcher;

class Box extends Dispatcher
{
    /**
     * 该类的子类都会重新实例化
     *
     */
    public function getInstance()
    {
        return self::newObject();
    }
}

使用分发器

使用规则

  • 一个类要使用分发器,必须继承 ContainerBox 类。
  • 类方法通过静态方式调用,同时::符号后面直接接的方法必须声明为 protected
  • 通过 register() 方法获取类实例。

一个简单的例子

<?php

use core\Application;

// error_reporting(0);

define('APP_PATH',dirname(__DIR__));

require APP_PATH.'/core/Autoload.php';

(Application::register())::dispatcher();
<?php

namespace core;

use dispatcher\Container;

class Application extends Container
{
    public function run()
    {
        echo 'hello world';
    }

    protected function dispatcher()
    {
        echo 'dispatcher';
    }

  
}

可以通过 Application::dispatcher(); 调用类方法(非静态方法)。通过 Application::register(); 可以获取 Application 类实例。

执行 index.php 会输出 dispatcher

理理思路

1.先不管分发器怎么实现的,当你使用时必须得 use 吧
2.当我们从 index 出发,去调用了一个 静态的 dispatcher 方法时,Application 本身没有这样一个静态的方法啊,怎么办? 只能通过自动加载到 container.php 这个文件了,还是没找到这个方法,怎么办? container 继承了 Dispatcher,好了 有 __callStatic ,这时候会自动进入这个函数,接下来,请注意,这里利用了 static 这个关键字来延迟绑定(呼应上文),简单来说,就是哪个类触发的 callStatic,static 就代表哪个类,如果你换成self 就会报错,好了接着说,调用类,这里是 Application紧接着调用了 dispatcher 子类的getInstance 方法去获得了该类的实例,最后回调,输出 dispatcher 完。

关于延迟静态绑定

没搞懂的,看看这篇文章吧 https://www.cnblogs.com/codeAB/p/5560631.html

总结

本结主要介绍了分发器的概念,实现了一个简单的类管理机制,可以通过静态方式调用对象方法。分发器类 ContainerBox 的区别在于是否保存子类实例

下期见

查看原文

赞 1 收藏 0 评论 0

entner 发布了文章 · 2019-12-19

PHP 手写MVC(二) —— 自动加载和Bootstrap

类自动加载:

在使用类的时候会用 requireinclude 将类加载进来,但是每次都手动加载,会导致一系列问题,比如类重复加载,管理困难等。所以解决上述问题,我们使用单独的一个文件来完成所有类的自动加载。

spl_autoload_register()

该函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)时自动去加载。通过注册自动加载器,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。

尽管 __autoload() 函数也能自动加载类和接口,但更建议使用 spl_autoload_register() 函数。 spl_autoload_register() 提供了一种更加灵活的方式来实现类的自动加载(同一个应用中,可以支持任意数量的加载器,比如第三方库中的)。因此,不再建议使用 __autoload() 函数,在以后的版本中它可能被弃用。

core 目录下创建一个 Autoload.php 文件

$ cd tinyphp/core
$ touch Autoload.php

编辑 Autoload.php


// 命名空间和文件目录映射
<?php
$path = [
    'core' => 'core',
    'app' => 'app',
    'controller' => 'app/controllers',
    'model' => 'app/models',
    'view' => 'app/views',
    'dispatcher' => 'core/dispatcher',
];

/**
 * 参数 $class 表示自动加载的类名
 * 
 * 匿名函数中使用 use 可以使用外部变量
 */
spl_autoload_register(function($class) use ($path) {

    //解析类名,如果使用了命名空间,则会查找 $path 中对于的路径
    $position = strripos($class,'\\');

    $key = substr($class,0,$position);
    $value = $path[$key] ?? '';

    $file = substr($class,$position+1).'.php';

    require APP_PATH.'/'.$value.'/'.$file;
});

spl_autoload_register() 主要实现类自动加载,参数使用的是匿名函数,通过关键字 use 可以使用外部变量 $path,其作用是提供命名空间和路径的对应关系,例如使用 namespace controller,对应的类在路径 app/controllers

使用方式为,在入口文件public/index.php 中,直接 require 该文件即可。

<?php

define('APP_PATH',dirname(__DIR__));

require APP_PATH.'/core/Autoload.php';

然后我们在core 文件夹下创建一个名为 Application.php 并编辑内容

<?php

namespace core;

class Application
{
    public function run()
    {
        echo 'hello world';
    }
}

自动加载完成,待会启动(注册)框架之后就可以看到效果了。

注册启动项

有时候需要在框架启动时,额外加载一些应用配置或用户定义的方法,比如定义常量,加载自定义辅助函数等。

我们在应用目录 app 中创建一个启动脚本 Bootstrap.php,规定该脚本内所有以 init 的方法都会被调用,并在 Application 类的构造函数中调用该脚本

创建 app/Bootstrap.php,编辑内容

<?php 

namespace app;

class Bootstrap
{
    /**
     * 所有以 init 开始的函数都会被依次调用
     *
     */
    public function initConst()
    {
        echo 'Bootstrap : 1'.PHP_EOL;
    }
    
    public function initHelper()
    {
        echo 'Bootstrap : 2'.PHP_EOL;
    }
}

Application 类中添加启动项

  • 在构造函数 __construct() 中获取 Bootstrap 实例。
  • run() 方法中遍历 Bootstrap 类中以 init 开头的方法并执行。
<?php
//这里用...省略之前的代码,在实际操作中,需要完整的代码,不然会报错
...
    public $boot;
    
    public function __construct()
    {
        /**
         * \app\为命名空间,
         * 第一个\表示在根目录,否在表示在当前命名空间下的子目录
         * 
         * 例如,app\Bootstrap() 表示在类在 tinyphp/core/app 目录下
         * \app\Bootstrap() 则表示在 tinyphp/app 目录下
         */
        $this->boot = new \app\Bootstrap();
    }
    public function run()
    {
        /**
         * 执行启动项
         * 所有init开头的方法都会被调用
         *
         */
        foreach(get_class_methods($this->boot) as $func) {
            if (0 === strpos($func, 'init')) {
                call_user_func([$this->boot,$func]);
            }
        }
    }
...

启动框架

在入口文件 index.php 中启动框架,并执行 run() 方法

<?php

use \core\Application;

define('APP_PATH',dirname(__DIR__));

require APP_PATH.'/core/Autoload.php';

(new Application())->run();

进入 public 目录,两种方式执行 php

  • 命令行
$ php index.php
Bootstrap : 1
Bootstrap : 2
  • 使用 PHP 内置服务器
$ php -S localhost:8080

在浏览器输入 http://localhost:8080 , 结果如下

Bootstrap : 1Bootstrap : 2

从结果可以看出,Application 类在实例化的时候执行构造函数,构造函数中完成了启动项的方法调用。

三、总结

本节实现了一个简单的类自动加载和框架的核心类,在启动时加载用户启动项,在 app/Bootstrap.php 中所有以 init 开头的方法都会被依次调用。

下期见

查看原文

赞 8 收藏 6 评论 4

认证与成就

  • 获得 179 次点赞
  • 获得 10 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 9 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-02-21
个人主页被 2.3k 人浏览