数据库---SQL优化/视图/存储过程/触发器

目录

SQL优化

插入数据

1、批量插入

2、手动事务提交

3、主键顺序插入

4、涉及大批量的数据,可以使用mysql的load指令

 主键优化

 主键顺序插入

主键乱序插入:页分裂

主键删除:页合并 

 主键设计原则

order by优化

 order by优化方式

group by优化

group by优化方法

limit优化

limit优化方法

count优化

 update优化

视图

视图语法 

检查选项

视图的更新

存储过程

变量

系统变量

用户自定义变量

局部变量

 if判断

 存储过程的参数

case

循环

while循环---条件满足开始执行

repeat---条件不满足开始执行,条件满足退出

loop循环

游标

mysql的条件处理程序

 存储函数

触发器

触发器创建

Insert触发器

update触发器

delete触发器

before和after的区别

触发器特性

查看触发器

删除触发器

SQL优化

插入数据

1、批量插入

insert student_info values(1,'李四',1789090,'189@136.com','陕西咸阳'),(1,'李四',1789090,'189@136.com','陕西咸阳');

每一次insert都要和数据库建立连接,可以批量插入,减少连接次数

2、手动事务提交

mysql默认自动提交,每一条insert语句的执行,都会涉及到事务的开启和关闭,可以设置手动提交,减少事务开启关闭次数

3、主键顺序插入

按照主键顺序插入

4、涉及大批量的数据,可以使用mysql的load指令

(将文件内的各种记录加载进入数据库

打开命令行窗口,进入mysqlbin的目录

如何连接mysql:mysql -u root -p,后输入密码,即可连接本机

  1.  使用load连接指令时,使用指令 mysql --local-infile -u root -p

2、设置打开本地加载进入数据库的开关

set global local_infile =1;

3、加载数据进入表中

load data local infile ‘文本文件路径'  into table 表名 fields terminated by '属性之间的分隔符' lines terminated by'行分隔符';

 如果您已经在Windows系统中生成了文本文件,您可能必须使用LINES TERMINATED BY ‘\r\n'来正确地读取文件,因为Windows程序通常使用两个字符作为一个行终止符。部分程序,当编写文件时,可能会使用\r作为行终止符。要读取这样的文件,应使用LINES TERMINATED BY ‘\r\n'。

 主键优化

数据的组织形式:在innodb存储引擎中,数据按照主键的顺序存放(聚集索引)

页是磁盘IO默认的最小单位,一个页16k,一个区可以存放64个页

规定一个页最少包含两行数据

一个页满了,申请另外的页,两个页通过双向指针联系

 主键顺序插入

主键乱序插入:页分裂

 页分裂:当乱序输入时,发生页分裂来存储

主键删除:页合并 

删除一行数据,记录并没有被实际上物理删除(类似于数组删除,数组元素前移覆盖),只是被标记(标记位)为删除并且它的空间可以被别的记录声明使用

设置记录的标志位例如flag,删除此纪录修改标志位:flag=1,表示纪录被删除

 当删除了的记录达到了页的百分之50,就会看是否进行页合并(前后寻找)

 主键设计原则

1、主键值长度尽量小(减少存储空间,减少磁盘IO)

2、主键尽量顺序插入(自增主键)

3、尽量减少主键修改(会更改聚集索引)

order by优化

分类有两种:

1、using index :排序时可以直接根据索引排序,不必要浪费空间,效率最高

2、using filesort:通过条件选择符合要求的行数据,将这些行数据在缓冲区进行排序,后输出(借用缓冲区空间,效率低)

向使用索引方向优化

explain select * from student_info order by telephone  ,email ;--
explain select telephone,email from student_info order by telephone  ,email ;--

 第一个使用:using filesort---*回表扫描

第二个使用:using index--- 根据索引查询

两边排序方法不一样,创建指定排序不同的索引

 order by优化方式

1、尽量使用索引排序

2、使用覆盖查询,不要涉及回表查询

3、多字段排序也涉及最左前缀法则

4、排序方式不一样时,注意创建索引的方法

group by优化

不使用索引:

 使用索引:

group by优化方法

1、使用索引

2、索引使用也满足最左前缀法则

limit优化

大数据量情况下,例如得到200000后的10条记录,需要扫描200000~200010的数据,效率低

limit优化方法

1、官方说通过覆盖索引和子查询,但是mysql不支持in(limit)

2、通过覆盖索引和联合查询

select * from student_info as a,(select id from student_info force index(primary)limit 2,2 )as b where a.id=b.id;

对于主键使用limit,可能出现问题

查看执行计划

存在主键,主键被设计成为聚集索引,以为使用的会是聚集索引。实际上这个使用的索引并不是主键索引,而是辅助索引,这是mysql的优化器优化的结果

这个情况下,强制使用主键索引

select id from student_info  force index  (`PRIMARY`)limit 1,3;

 这个走的是全表扫描,没有使用索引,因此和主键的顺序一致

count优化

MyISAM存储引擎将纪录的总数存入,count(*)时可直接读取该数值(前提没有where条件)

InnoDB存储引擎执行count(*)时,将所有的纪录一一读取,效率低

count没有优化方法

 update优化

 InnoDB存储引擎给索引列会增加行锁,update时where后面+索引列,如果不是索引列或者索引列失效,会导致行锁升级为表锁,不可以对表进行更改

视图

视图是一个虚拟的表,视图中的所有数据都来自于创建视图时使用的表

视图语法 

1、创建视图

create  [or replace]view 视图名 as 查询表的记录

create view borr_info_view as  select * from borrow_info;

2、查询视图

select (列名) from 视图名

select * from borr_info_view;

 3、修改视图

第一种:

create  [or replace] view 视图名 as 查询表的记录

借助 replace来替换视图

create or replace view  borr_info_view as  select id,book_id from borrow_info;

第二种: 

alter view 视图名 as 查询表的记录

alter view  borr_info_view as  select id from borrow_info;

 4、删除视图

drop view 视图名

drop view borr_info_view;

检查选项

create or replace view  borr_info_view as  select * from borrow_info where book_id<5;
select * from ebook.borr_info_view;
insert ebook.borr_info_view values(null,4,1,'2019-03-12 14:25:00','2019-04-02 16:37:00');
select * from ebook.borr_info_view;

查看视图和表,都含有此条记录 

insert ebook.borr_info_view values(null,6,2,'2019-09-12 14:25:00','2019-12-02 16:37:00');
select * from ebook.borr_info_view;

查看表,含有此条记录 ,视图却没有(条件限制)

在不添加检查选项时,数据都会通过视图被插入表,只不过是在视图中不一定看得到(限制条件)

引入检查选项,来解决数据全部插入的问题

with cascaded/local check option :检查数据是否可以插入,不可以插入就会报错

 视图允许在一个视图的基础上创建视图,为了明确检查范围,引入cascaded和local ,默认为cascaded

cascaded:同时满足多个视图的要求

local:只需满足当前要求

create or replace view  borr_info_view as  select * from borrow_info where book_id<5 with cascaded check option ;
select * from ebook.borr_info_view;
insert ebook.borr_info_view values(null,3,2,'2019-09-12 14:25:00','2019-12-02 16:37:00');
select * from ebook.borr_info_view;

insert ebook.borr_info_view values(null,9,2,'2019-09-12 14:25:00','2019-12-02 16:37:00');
select * from ebook.borr_info_view;

检查选项:检查数据是否可以插入,不可以插入就会报错

1、视图1不设置检查选项

  • 视图2不设置检查选项
-- 视图1
create or replace view s_v1  as select * from student where id<5;

-- 视图2
create or replace view s_v2  as select * from s_v1 where id>2;
-- 没有检查选项,不会检查,数据全部插入表格
 insert s_v2 values(1,'张三丰');-- 不满足s2,满足s1

insert s_v2 values(6,'张三丰');-- 满足s2,不满足s1


insert s_v2 values(3,'赵敏');-- 都满足

s2不设置检查选项,s2不检查自身,s2去递归查看s1,s1也没有设置检查选项,s1不检查自身

记录全部插入student表

  • 视图2设置检查选项 with cascaded check option 
-- 视图1
create or replace view s_v1  as select * from student where id<5;

-- 视图2
create or replace view s_v2  as select * from s_v1 where id>2 with cascaded check option ;

 insert s_v2 values(1,'周芷若');-- 不满足s2,满足s1
-- 查看s_v2,s_v2检查不满足要求,报错

insert s_v2 values(6,'张三丰');-- 满足s2,不满足s1
-- 查看s_v2,s_v2检查满足自身要求,联级查询需要同时满足,s_v2查看s_v1,6不满足s1的条件,报错

insert s_v2 values(3,'赵敏');-- 都满足

 with cascaded check option ;当视图2条件不满足时,报错,当视图2条件满足,视图2去查看视图1的条件是否满足,只有当两个同时满足才会成功

  • 视图2设置检查选项 with local check option 
-- 视图1
create or replace view s_v1  as select * from student where id<5;

-- 视图2
create or replace view s_v2  as select * from s_v1 where id>2 with local check option ;
 insert s_v2 values(1,'周芷若');-- 不满足s2,满足s1
-- 查看s_v2,s_v2检查自身,并去检查s_v1,s_v2检查自身不符合要求,报错

insert s_v2 values(6,'张三丰');-- 满足s2,不满足s1
-- 查看s_v2,s_v2检查满足自身要求,不需要同时满足s_v1,s_v2查看s_v1,s_v1没设置检查选项,不进行检查,插入成功

insert s_v2 values(3,'赵敏');-- 都满足

with local check option :不要求本身视图和所依赖的视图同时满足

2、多重情况的嵌套

create or replace view s_v1  as select * from student where id>=5;
create or replace view s_v2  as select * from s_v1 where id<=30  with cascaded check option ;
create or replace view s_v3  as select * from s_v2 where id>=20 ;
insert s_v3 values(30,'周芷若');-- 三个都满足

insert s_v3 values(40,'周芷若');
-- 满足s_v3,s_v3不设置检查选项,查看s_v2,s_v2是级联检查,不满足s_v2,报错

insert s_v3 values(4,'周芷若');
-- 不满足s_v3,s_v3不设置检查选项,不检查s_v3,去查看s_v2,s_v2是级联检查,满足s_v2,但是不满足s_v1,报错

insert s_v3 values(19,'周芷若');
-- 不满足s_v3,s_v3不设置检查选项,不检查s_v3,去查看s_v2,s_v2是级联检查,满足s_v2,查看s_v1,也满足s_v1 插入成功
 

s_v3不包含检查选项,不检查s_v3,s_v3递归查看s_v2, s_v2是级联检查,检查s_v2后还要检查s_v1 ,s_v2和s_v1同时满足才会插入成功

3、多重情况的嵌套  

create or replace view s_v1  as select * from student where id>=5;
create or replace view s_v2  as select * from s_v1 where id<=30  with local check option  ;
create or replace view s_v3  as select * from s_v2 where id>=20 ;
insert s_v3 values(30,'周芷若');-- 三个都满足

insert s_v3 values(40,'周芷若');
-- 满足s_v3,s_v3不设置检查选项,查看s_v2,s_v2只检查自身,不满足s2,报错

insert s_v3 values(4,'周芷若');
-- 不满足s_v3,s_v3不设置检查选项,查看s_v2,s_v2只检查自身,满足s_v2,查看s_v1不包含检查选项,成功

insert s_v3 values(19,'周芷若');
-- 不满足s_v3,s_v3不设置检查选项,查看s_v2,满足s_v2,查看s_v1,不包含检查选项,成功

 s_v3不包含检查选项,不检查s_v3,查看s_v3的来源s_v2, s_v2只检查自身,s_v2检查后检查s_v1,(不是联合查询,不要求s_v2和s_v1同时满足)

视图的更新

要使视图可以更新,那么视图的行必须和基础表的行属于一对一的关系,即要求视图

  • 不含有聚合函数(sum,count,div……)
  • 不含有group by(having)
  • 不含有distinct
  • union 、union all

存储过程

一个更新数据的过程:查看数据,更新数据,查看数据

按照顺序执行这三个SQL语句,涉及到多次网络请求,可以将这三个语句封装到一起,提交一次,一起执行,可以减少网络请求

1、创建存储过程:

create procedure 存储过程名称(参数)
begin
 SQL语句
end;

2、调用存储过程:

call 存储过程名称(参数)

create procedure p1()
begin
select * from student;
update student set name='张三' where id=1;
select * from student;
end;

call p1();

结果:返回这个存储过程中所有语句全部执行之后的结果

3、查看

查看某一个数据库的存储过程和状态信息

select * from information_schema.ROUTINES where ROUTINE_NAME='数据库名'

 查看存储过程的建立语句

show create  procedure 存储过程名称;

select * from information_schema.ROUTINES where ROUTINE_SCHEMA='book';
show create procedure p1;

4、删除

drop procedure 存储过程名称;

drop procedure p1;

命令行窗口使用会报错:使用  delimiter 设计结束符

变量

mysql的变量分为三种:系统变量,用户自定义变量,局部变量

系统变量

系统变量由mysql数据库本身提供,分为全局变量(global)和会话变量(session)

全局变量:在所有会话有效 

会话变量:仅在当前会话有效

 不指定时默认级别是session

show variables ;
show global variables ;
show session variables ;
select @@autocommit;
show  global variables like 'auto%';
set @@autocommit =1;

用户自定义变量

用户自己创建的变量,不需要提前声明,直接使用'@变量名'就可以,作用域是当前连接

给用户自定义变量赋值

set @变量名 =……;

select 字段名 into @变量名 from 表名---从一个表选出信息给变量赋值

(只能选定一个唯一值且唯一类型相同的属性列赋值)

使用变量

select   @变量名;

set @myname ='张三';-- 给创建的变量myname赋值为张三
set @mygender='男',@myid=1;

select @myname ,@mygender,@myid;

select count(*)  into @mycount from books;
select @mycount;
select * from books;

 将查询的count结果赋值给用户创建的变量

局部变量

定义在局部范围内生效的变量,访问之前需要使用declare来声明。可以作为存储过程中的局部变量和输入参数,局部变量的作用范围在声明的存储过程之内

声明

 declare 变量名 变量类型

给局部变量赋值

set 变量名 =……;

select 字段名 into 变量名 from 表名---从一个表选出信息给变量赋值

create procedure p6()
begin
 declare my_id  int;
    select  id into my_id from  borrow_info where book_id =13;
select my_id;
end;
call p6();

 if判断


create procedure p8()
begin
    declare mygrade int default 89;
    declare mystatus char(3);
    if mygrade>=90 then  set mystatus='优秀';
        elseif  mygrade>=75 then  set mystatus='良好';
           elseif  mygrade>=60 then  set mystatus='及格';
           else   set mystatus='不及格';
        end if;
    select mygrade,mystatus;
end;
call p8();

 存储过程的参数

 传入成绩,并返回成绩等级

create procedure p9(in  mygrade int,out mystatus char(3))-- in输入,out输出
begin

    if mygrade>=90 then  set mystatus='优秀';
        elseif  mygrade>=75 then  set mystatus='良好';
           elseif  mygrade>=60 then  set mystatus='及格';
           else   set mystatus='不及格';
        end if;
end;

  call p9(89,@result );-- 传入自定义变量,用于接收结果
select @result;
  call p9(56,@result );-- 传入自定义变量,用于接收结果
select @result;

case

根据月份返回所在季节

create procedure p10(in  month int,out season char(2))
begin
    case
        when month<=3&&month>=1  then set season ='春天';
        when month<=6&&month>=4  then set season ='夏天';
        when month<=9&&month>=7  then set season ='秋天';
        when month<=12&&month>=10 then set season ='冬天';
        else
            set season='非法';
    end case;

end;
call p10(7,@season);

select @season;

循环

while循环---条件满足开始执行


create procedure p11( in n int )
begin
    declare total int default 0;
while n>0 do
set total=total+n;
 set n=n-1;
end while ;
 select concat('从1到n'+n+'的累计和是'+total);
end;

call p11(4 );

repeat---条件不满足开始执行,条件满足退出

create procedure p12( in n int )
begin
    declare total int default 0;
   repeat
       set total=total+n;
 set n=n-1;
   until  n<=0 end repeat;-- 循环结束的条件
    select concat('从1到n'+n+'的累计和是'+total);
 end;

call p12(3);

loop循环

 1~n求和


create procedure p13( in n int )
begin
    declare total int default 0;
  loop_sum: loop
       set total=total+n;
 set n=n-1;
       if n<=0 then leave loop_sum ;
       end if;
   end loop loop_sum ;
     select concat('从1到n'+n+'的累计和是'+total);
 end;

call p13(2);

1~n之间的偶数求和

create procedure p14(in n int)
begin
     declare total int default 0;
    sumdouble: loop
         if n<=0
             then leave sumdouble;
        end if;
           if n%2=1
            then
                set n=n-1;
                   iterate sumdouble;
        end if;
              set total=total+n;
           set n=n-1;
    end loop sumdouble;
      select concat('从1到n'+n+'的偶数累计和是'+total);
end;

call p14(7);

游标

用于存储查询结果集的数据类型

游标声明

declare  游标名称 cursor for 查询结果集 --- (查询结果集内的记录会存储进入此游标)

游标开启

open 游标名称

游标数据获取

fetch 游标名称 into 变量名(循环获取)

游标关闭

close 游标名称

实现一个存储过程:查询student表中id<5的学生的id和name,将查询的结果创建进入新的表

-- 查询student表中id<5的学生的id和name,将查询的结果创建进入新的表
-- 查询信息多行多列,需要通过游标存储         1、声明游标(查询结果会存储进入此游标) 2、开启游标
-- 将查询的结果创建进入新的表                3、创建新的表 4、查询游标数据并插入新表 5、关闭游标
create procedure p15()
begin
 --   1、声明游标 所有的声明都放在游标开启之前
declare myid int;
declare myname varchar(20);
 -- 变量的声明必须在游标声明之前
declare  c_stu cursor for select id,name from student;
-- 2、开启游标
open c_stu;
 -- 3、创建新的表
create table newstu (
    id int ,
    name varchar(20)
);

 -- 4、查询游标数据
    while true do
fetch c_stu into myid,myname;
insert newstu values(myid,myname);
        end while;
 -- 5、关闭游标
    close c_stu;
end;

call p15();

注意声明 :所有的声明都放在游标开启之前,并且变量(获取游标时的变量名)的声明必须在游标声明之前 

 这个问题是因为死循环,如何知道读取游标时读取完了呢?借助mysql的条件处理程序

mysql的条件处理程序

 

 状态码02000对应的就是上面的报错原因

-- 查询student表中id<5的学生的id和name,将查询的结果创建进入新的表
-- 查询信息多行多列,需要通过游标存储         1、声明游标(查询结果会存储进入此游标) 2、开启游标
-- 将查询的结果创建进入新的表                3、创建新的表 4、查询游标数据并插入新表 5、关闭游标
create procedure p15()
begin
 --   1、声明游标 所有的声明都放在游标开启之前
declare myid int;
declare myname varchar(20);
 -- 变量的声明必须在游标声明之前
declare  c_stu cursor for select id,name from student where id<5;
 declare exit handler for SQLSTATE '02000'close c_stu;
 -- 条件处理程序:当报错出现02000状态码时触发此退出程序,伴随着关闭游标的操作
-- 2、开启游标
open c_stu;
 -- 3、创建新的表
 drop table  if  exists newstu;
create table if not exists newstu (
    id int ,
    name varchar(20)
);

 -- 4、查询游标数据
    while true do
fetch c_stu into myid,myname;
insert newstu values(myid,myname);
        end while;
 -- 5、关闭游标
    close c_stu;
end;


call p15();
select *from newstu;

 存储函数

 存储函数名称以f开头 或者

create function fun1( n int)
returns int deterministic
begin
     declare total int default 0;
  while n>0 do
      set total=total+n;
      set n=n-1;
      end while;
     return  total;
end;

 select fun1(4);

触发器

和表有关的数据结构,执行insert,delete,update后,触发并且执行触发器内定义的SQL语句集合

  • Insert触发器:向表中插入数据时被触发;

  • Update触发器:修改表中数据时被触发;

  • Delete触发器:从表中删除数据时被触发;

触发器创建

CREATE TRIGGER 触发器名 BEFORE|AFTER 触发事件

ON 表名 FOR EACH ROW

BEGIN

        执行语句列表

END;

Insert触发器

-- 这个触发器的作用:插入student_info记录后,触发并执行触发器,插入记录到s表 

create trigger tri_book
 after insert -- insert操作执行的触发器
    -- after表示在insert语句执行完之后触发 触发器
    -- before 表示在insert语句执行完之前触发 触发器
    on student.student_info for each row
    -- for each row 行级触发器
    begin
         -- 触发器触发的语句
         insert s values(new.id,new.name);-- new:可以获得新的记录的数据 old:可以获得之前记录的数据
end;
-- 这个触发器的作用:插入student_info记录时,插入记录到s表

update触发器

create trigger tri_book_update
 after update -- update操作执行的触发器

    on student.student_info for each row
    -- for each row 行级触发器
    begin
         -- 触发器触发的语句
         update s set id=NEW.id,name=new .name where id=old.id;
end;
update student_info set name='吴丽华' where id=7;
select * from student_info;
select *from s;

delete触发器

create trigger student_info_delete_trigger
 after delete

    on student.student_info for each row
    -- for each row 行级触发器
    begin
         -- 触发器触发的语句
         delete  from s where s.id=OLD.id and s.name=old .name;
end;
delete from student_info where id<=2;
select * from student_info;
select *from s;


​​​​

before和after的区别

创建两张表

create table class (
classid int primary key ,
classname varchar(20)
);
create table student(
   class_id int ,foreign key (class_id)references class(classid),
   name varchar(20)
);

before级别的触发器 

create trigger student_insert_trigger
    before insert
    on class for each row-- 主键
    begin
        insert student values(new.classid,'张三');-- 外键
    end;

 before级别的触发器:是在记录操纵之前触发,是先完成触发,再增删改

执行插入语句到class表的操作,触发了触发器, before级别的触发器,先执行触发器内的语句:  insert student values(new.classid,'张三');由于student表class_id是外键,先执行这个语句但是主键没有这个值,会报错

after级别的触发器

drop trigger student_insert_trigger;
create trigger student_insert_trigger_after
    after insert
    on class for each row
    begin
        insert student values(new.classid,'张三');
    end;
insert class values(1,'java1班');

执行插入语句到class表的操作,触发了触发器, after级别的触发器,先执行触发语句 insert class values(1,'java1班');后执行触发器内的语句 insert student values(new.classid,'张三');由于student表class_id是外键,主键元素插入在此语句执行之前,两个表都插入了数据

触发器特性

1、mysql现在只支持行级触发器,不支持语句级触发器

create trigger tri_book_update
 after update -- update操作执行的触发器

    on student.student_info for each row
    -- for each row 行级触发器
    begin
         -- 触发器触发的语句
         insert s set id=NEW.id,name=new .name;
end;
update student_info set name='吴丽华' where id<=2;
select * from student_info;
select *from s;

 任何一条记录上的操作满足触发事件都会触发该触发器,也就是说触发器的触发频率是针对每一行数据触发一次。

2、触发器不能关联在临时表和视图上

3、触发器触发条件:insert、delete、update 

4、触发时间:在增删改前或者后(before/after)

5、OLD 和 NEW 的作用

MySQL 中定义了 NEW 和 OLD,用来表示触发器的所在表中,触发了触发器的那一行数据,来引用触发器中发生变化的记录内容,具体:

  • 在INSERT型触发器中,NEW用来表示将要(BEFORE)或已经(AFTER)插入的新数据;
  • 在UPDATE型触发器中,OLD用来表示将要或已经被修改的原数据,NEW用来表示将要或已经修改为的新数据;
  • 在DELETE型触发器中,OLD用来表示将要或已经被删除的原数据;

查看触发器

查看全部触发器

SHOW TRIGGERS;

 查看详细的触发器信息

SELECT * FROM information_schema.triggers where ……;

所有触发器信息都存储在information_schema数据库下的triggers表中,可以使用SELECT语句查询,如果触发器信息过多,最好通过TRIGGER_NAME字段指定查询。

删除触发器

drop trigger 触发器名称;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值