18.Mysql SQL优化

18.SQL优化
18.1 优化SQL语句的一般步骤
18.1.1 通过show status命令了解各种SQL的执行频率
show [session|global] status; -- 查看服务器状态信息
show session status; -- 查看session(当前连接)级别的服务器状态信息,默认session级别
show global status; -- 查看global(数据库启动至今)级别的服务器状态信息
show status like ‘Com_%‘; -- 查看当前session的所有统计信息,输出为类型和次数。
show status like ‘Innodb_rows_%‘; -- 查看当前session的Innodb引擎的统计信息,输出为类型和次数。
Innodb_rows_read 50908229195 -- 查询返回行数
Innodb_rows_inserted 1587576 -- 新增操作行数
Innodb_rows_deleted 101485 -- 删除操作行数
Innodb_rows_updated 119280 -- 修改操作行数
show status like ‘Com_commit‘; -- 提交次数
show status like ‘Com_rollback‘; -- 回滚次数
show status like ‘Connections‘; -- 连接服务器次数
show status like ‘Uptime‘; -- 服务器工作时间
show status like ‘Slow_queries‘; -- 慢查询次数

18.1.2 定位执行效率较低的SQL语句
1.通过慢查询日志定位那些执行效率低的SQL语句,
slow_query_log :指定是否开启慢查询日志
log_slow_queries :指定是否开启慢查询日志(该参数要被slow_query_log取代,做兼容性保留)
slow_query_log_file :指定慢日志文件存放位置,可以为空,系统会给一个缺省的文件host_name-slow.log
long_query_time :设定慢查询的阀值,超出次设定值的SQL即被记录到慢查询日志,缺省值为10s
min_examined_row_limit :查询检查返回少于该参数指定行的SQL不被记录到慢查询日志
log_queries_not_using_indexes :不使用索引的慢查询日志是否记录到索引
注意:慢查询日志在查询后才记录。
2.使用show processlist命令查看当前mysql的线程,包括线程状态、是否锁表等。

18.1.3 通过explain分析SQL的执行计划
explain SQL; -- 查看SQL的执行计划
show warnings; -- 查看优化器将SQL的改写后的结果
explain partitions SQL; -- 查看分区表SQL的执行计划及所使用的分区

explain SQL输出解析:
select_type:查询类型,包括:simple(单表查询,不使用表连接或子查询),primary(主查询,即外层查询),union(),subquery(子查询的第一个select)。
table:查询的表。
type:访问类型,包括:ALL全表扫描--》index索引全部扫描--》range索引范围扫描--》ref索引前缀扫描--》eq_ref索引唯一扫描--》const,system常量唯一扫描--》NULL不需要访问表或索引
index索引全部扫描:无条件查询索引列,如:select 索引列 from 表
range索引范围扫描:包括如下操作符<,<=,>,>=,between的查询语句
ref索引前缀扫描:使用非唯一索引或唯一索引的前缀扫描,可能返回1行或多行。
eq_ref索引唯一扫描:使用唯一索引扫描(主键索引或唯一键索引),只返回1行。
const,system常量唯一扫描:使用唯一索引扫描(主键索引或唯一键索引)与常量匹配,只返回1行。
NULL:不需要访问表或索引,如:select 1+2。
ref_or_null:非唯一索引扫描,且索引列包含null。
index_merge:索引合并优化
unique_subquery:单行子查询
index_subquery:多行子查询
possible_keys:备选的索引
key :选中的索引
key_len :选中索引的字段长度
rows :扫描的行数
Extra :执行情况的说明和描述,如:using where等

18.1.4 通过show profile分析SQL
Profiling可以对某一条sql的性能进行分析,如查询SQL执行状态,System lock和Table lock,I/O消耗和CPU消耗 。
Profiling是从 mysql5.0.3版本以后才开放的。
启动profile之后,所有查询包括错误的语句都会记录在内。
关闭会话或者set profiling=0 就关闭了。(如果将profiling_history_size参数设置为0,同样具有关闭MySQL的profiling效果。)
--在mysql5.7之后,通过performance_schema.profiling表来查看。
select status,sum(duration) as "总花费时间",count(*) as "执行次数",sum(duration)/count(*) as "平均每次花费时间" from performance_schema.profiling where [email protected]_id group by state,order by sum(duration) desc;

参数说明:
@@have_profiling 当前数据库是否支持Profiling,YES支持
@@profiling 当前数据库是否开启Profiling,1开启,0关闭
开启Profiling set profiling=1;
关闭Profiling set profiling=0;
在开启Profiling的状态下执行SQL语句,
查看SQL语句query_id:show profiles;
通过query_id查看SQL语句分析:show profile for query "query_id";
通过query_id查看SQL语句的I/O和CPU:show profile 分析类型 for query "query_id";
分析类型分为:
all 显示所有性能信息,
block io 显示块IO的次数
context switches 上下文切换相关开销
cpu 显示用户和系统的CPU使用情况
ipc 显示发送和接收的消息数量
memory 显示占用内存情况
page faults 显示页面故障
source 显示源代码的函数名称,以及在源码文件中的位置
swap 显示交换次数相关开销的信息
例子:
show profile all for query 1;
首行列说明
"Status": "query end", 状态
"Duration": "1.751142", 持续时间
"CPU_user": "0.008999", cpu用户
"CPU_system": "0.003999", cpu系统
"Context_voluntary": "98", 上下文主动切换
"Context_involuntary": "0", 上下文被动切换
"Block_ops_in": "8", 块输入操作
"Block_ops_out": "32", 块输出操作
"Messages_sent": "0", 消息发出
"Messages_received": "0", 消息接受
"Page_faults_major": "0", 主分页错误
"Page_faults_minor": "0", 次分页错误
"Swaps": "0", 交换次数
"Source_function": "mys", 源功能
"Source_file": "sql_", 源文件
"Source_line": "4465" 源代码行
首列每行信息(SQL执行状态)说明:
starting: 开始
checking permissions:检查权限
Opening tables: 打开表
init : 初始化
System lock : 系统锁
optimizing : 优化
statistics : 统计
preparing : 准备
executing : 执行
Sending data : 发送数据
Sorting result : 排序
end : 结束
query end : 查询 结束
closing tables : 关闭表 /去除TMP 表
freeing items : 释放项
cleaning up : 清理
一般只查看CPU和BLOCK IO即可:
SHOW profile CPU,BLOCK IO FOR query 1;

18.1.5 通过trace可以分析优化器如何选择执行计划
首先,打开trace,设置格式为JSON,并设置trace的内存大小(10MB),避免解析过程因默认内存(16K)过小不能完全显示。
show variables like ‘%optimizer_trace%‘;
+------------------------------+----------------------------------------------------------------------------+
| Variable_name | Value |
+------------------------------+----------------------------------------------------------------------------+
| optimizer_trace | enabled=on,one_line=off |
| optimizer_trace_features | greedy_search=on,range_optimizer=on,dynamic_range=on,repeated_subselect=on |
| optimizer_trace_limit | 1 |
| optimizer_trace_max_mem_size | 16384 |
| optimizer_trace_offset | -1
set optimizer_trace="enabled=on";
set end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;
接着,执行SQL语句;
最终,在performance_schema.optimizer_trace表查看分析结果。

18.1.6 确定问题并采取相应的优化措施
show status; --查看系统状态
show processlist; --查看当前线程及状态
通过慢查询日志找到历史上比较慢的的SQL;
通过explain分析执行计划;
通过performance_schema.profiling分析SQL在哪个状态产生了CPU耗时时长或IO高;
通过performance_schema.optimizer_trace分析执行计划为什么和预期的不一致;
最终通过增加索引、改建索引、改写SQL的措施优化SQL执行速度。

18.2 索引问题
18.2.1 索引的存储分类
索引由存储引擎实现,每种存储引擎支持的不同类型的索引。
InnoDB仅支持B-Tree索引。
MyISAM支持B-Tree索引、R-Tree索引、Full-Text索引。
Memory支持B-Tree索引、Hash索引。
Mysql不支持函数索引,但支持前缀索引,前缀索引在Order by和Group by操作中不能使用。
前缀索引即只对某个列的前N个字符创建索引,
语法:
create index 索引名 on 表名(列名(前缀长度));
例子:
create index idx_emp_ename on emp(ename(3));
Hash索引只支持等值比较(=),不支持范围比较(<、<=、>、>=)。
B-Tree索引是将索引列构造成平衡多叉树,包括一个根节点,多个分支节点,多个叶子节点。
B-Tree索引可用于关键字等值比较、关键字范围比较和关键字前缀比较等。
创建B-Tree索引语法:
create index 索引名 on 表名(列名); -- 如果包含多个列名,则称为复合索引。
alter table 表名 add index 索引名(列名);
删除B-Tree索引语法:
drop index 索引名;
alter table 表名 drop index 索引名;
18.2.2 mysql如何使用索引
访问类型type=const
当where条件全部使用到B-Tree索引,且索引列使用等值比较,且右值为常量时访问类型type=const。
访问类型type=range
当where条件全部使用到B-Tree索引,且索引列使用范围比较,且右值为常量时访问类型type=range。
当where条件使用到B-Tree索引(前缀索引),且索引列或复合索引的首使用右模糊比较(like ‘xxx%‘)时,访问类型type=range。
访问类型type=ref
当where条件部分使用到B-Tree索引(复合索引的首列),且为等值比较,且右值为常量时访问类型type=ref。
当where条件全部使用到B-Tree索引(复合索引)时,且首列为精确匹配,非首列为范围匹配时,访问类型type=ref。
当where条件索引列 is null时,访问类型type=ref。
当where条件部分使用到B-Tree索引(复合索引的非首列)时,将不会使用索引。
访问类型type=eq_ref
当where条件部分使用B-Tree索引(唯一索引) 时,访问类型type=eq_ref。
Extra=using where
表示通过索引找到记录的编号,再去表里查找记录的详细信息。
Extra=using index
表示通过索引找到记录的信息后,不需要再去表里查找记录的详细信息。
Extra=using index condition
表示根据复合索引首列的条件在索引里找,再根据非首列的条件在索引里进行二次过滤,最后再去表里查找记录的详细信息。

索引失效:
左模糊(like ‘%xxx‘)和全模糊(like ‘%xxx%‘)将导致索引失效;
索引列出现数据类型隐式转换时(字符列=数字)将导致索引失效;
where条件中不包括复合索引的首列时,将导致复合索引失效;
当使用索引查询到的记录是全部记录的20%以上时,使用索引比全表扫描更慢,将导致索引失效;
or操作将导致索引失效;

18.2.3 查看索引使用情况
show status like ‘Handler_read%‘;
Handler_read_key :表示一个行被索引值读的次数,值高时考虑增加索引;
Handler_read_rnd_next :表示在数据文件中读取下一行的请求数,值高时说明全表扫描多,应考虑增加索引。

18.3 两个简单实用的优化方法
18.3.1 定期分析表和检查表
分析表语法:
analyze [local|no_write_to_binlog] table 表名,...;
用于分析和存储表的关键字分布,分析的结果将可以使系统得到准确的统计信息,使生成的SQL执行计划更准确。
分析过程会对表进行锁定。
检查表语法:
check table 表名,... [{quick|fast|medium|extended|changed}]
用于检查一个或多个表(或视图)是否有错误。

18.3.2 定期优化表
优化表语法:
optimize [local|no_write_to_binlog] table 表名,...;
用于合并空间碎片,删除语句和修改语句(由大改小)会导致产生空间碎片。
innodb_file_per_table参数可以为每个表设置独立的表空间,即每个表单独一个数据文件(.ibd),表数据和表上索引的数据均存在该文件中。
另一种回收空间碎片的方法是修改表(不修改存储引擎),alter table 表名 engine=innodb;
analyze、check、optimize、alter table执行期间都会对表进行锁定,请避开业务高峰期。

18.4 常用SQL的优化
18.4.1 大批量插入数据
对于MyISAM存储引擎,在导入数据前可disable keys,即只插入数据不插入索引。
例子:
alter table 表名 disable keys;
load data infile ‘/home/mysql/文件名.txt‘ into table 表名;
alter table 表名 enable keys;
对于InnoDB存储引擎,可以将导入数据按照主键列有序排列,关闭唯一约束检查,关闭自动提交等优化速度。
set unique_checks=0;
set autocommit=0;
load data infile ‘/home/mysql/文件名.txt‘ into table 表名;
set unique_checks=1;
set autocommit=1;

18.4.2 优化insert语句
尽量使用多行插入,以减少客户端与数据库之间的连接、关闭等消耗;
insert into 表名 values (值列表1),(值列表2),...;
使用insert delayed 语句,将数据先存放在内存队列中;
将索引文件和数据文件放在不同的磁盘上(MyISAM);
增加参数bulk_insert_buffer_size的值(MyISAM);
使用load data infile。

18.4.3 优化order by语句
检查表上的索引:
show index from 表名;
-- cardinality 不同值的个数
-- index_type 索引类型
通过索引顺序扫描的结果是有序的,不需要额外的排序操作,Extra=using index。
未通过索引顺序扫描的结果是无序的,需要对结果进行再排序,Extra=using filesort。
排序操作根据排序缓冲区(sort_buffer_size)容量设置和结果记录量大小决定是否使用磁盘文件或临时表。
filesort排序算法:将结果在参数sort_buffer_size设置的内存中进行排序;
如果该内存装载不能装载全部结果数据,将按照参数sort_buffer_size设置将结果数据分块,
每块在内存中排序后分别保存在硬盘中,最终合并各个块中结果数据输出。
sort_buffer_size为每个进程分配单独的sort buffer排序区。
1.优化的目标是:尽量减少额外的排序,通过索引直接返回数据。
当where条件和order by使用相同的索引,且是相同的升降序,否则需要额外排序。
使用索引排序的例子:
select * from 表名 order by key_part1,key_part2;
select * from 表名 order by key_part1 desc,key_part2 desc;
select * from 表名 where key_part1=xxx order by key_part1 desc,key_part2 desc; -- 范围比较将导致需要排序
需要额外排序的例子:
select * from 表名 order by key_part1 desc,key_part2 asc;
select * from 表名 order by key1,key2;
select * from 表名 where key1=xxx order by key2;
2.filesort优化
自动选择两次扫描算法或一次扫描算法来优化排序。
两次扫描算法:首先根据条件取出排序字段和行指针信息,根据排序字段在内存中排序,根据排序后的行指针顺序的回表去读行信息。
优点:内存开销少,能尽量避免排序时内存与硬盘交换数据。
缺点:需要从表中读取两次数据,且第二次读取时回产生大量的随机I/O操作。
一次扫描算法:一次性取出满足条件的行的所有信息,然后在排序区排序后输出结果。
优点:只用读取一次数据,效率较高。
缺点:内存开销大。
Mysql根据参数max_length_for_sort_data与SQL结果进行比较,参数max_length_for_sort_data更大时采用一次扫描算法;否则采用两次扫描算法。

18.4.4 优化group by语句
Mysql对所有 group by col1,col2进行排序,
如果SQL包含了相同列的 order by语句,则只会进行一次排序;
如果SQL包含了不同列的 order by语句,则会进行两次排序;
如果SQL包含了order by null 语句,则不会进行排序。

18.4.5 优化嵌套查询
子查询可以将多步操作一次完成,避免事务或者锁表。
子查询一般可等价的写成表连接,且连接的效率要优于子查询。

18.4.6 优化or条件
or 关键字可等价的替换为in或union。

18.4.7 优化分页查询
在索引上完成分页操作,再通过行指针取表中查询当前页的记录。
select * from 表名 order by 排序列 limit 之前页数*每页显示行数,每页显示行数;
在排序列上建索引后,可改写为
select * from 表名 a,(select 主键列 from 表名 order by 排序列 limit 之前页数*每页显示行数,每页显示行数) b where a.主键列=b.主键列;
把分页查询转换为位置查询,即将前一页排序列的临界值传递给下一页的查询语句作为查询条件。
上述两种方法在遇到排序列有重复值时均不能保证结果正确,不建议使用。

18.4.8 使用SQL提示
SQL提示是在SQL语句中增加提示信息告诉优化器怎么执行SQL语句。
use index提示:告诉SQL解释器使用指定的索引
select * from 表名 use index(索引名) where ...;
force index提示:告诉SQL解释器强制使用指定的索引
select * from 表名 force index(索引名) where ...;
ignore index提示:告诉SQL解释器忽略指定的索引
select * from 表名 ignore index(索引名) where ...;

18.5 常用SQL技巧
18.5.1 正则表达式的使用
正则表达式是指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串。
Mysql提供regexp命令来实现正则表达式,regexp中模式串是区分大小写的。
"^"在字符串开始处进行匹配,返回值:1匹配,0不匹配。
select ‘abcdefg‘ regexp ‘^a‘;
"$"在字符串末尾处进行匹配,返回值:1匹配,0不匹配。
select ‘abcdefg‘ regexp ‘g$‘;
"."匹配任意单个字符,包括换行符,返回值:1匹配,0不匹配。
select ‘abcdefg‘ regexp ‘.h‘,‘abcdefg‘ regexp ‘.f‘; -- 查找字符串中是否包含.后个哪个字符
"[...]"匹配[]内的任意字符,返回值:1匹配,0不匹配。
select ‘abcdefg‘ regexp "[fhk]"; -- 查找字符串中是否包含[]内的任意一个或多个字符
"[^...]"不匹配[]内的任意字符,返回值:0匹配,1不匹配。
select ‘abcdefg‘ regexp "[^hk]"; -- 查找字符串中是否不包含[]内的任意一个或多个字符
a* 匹配0个或多个a
a+ 匹配1个或多个a
a? 匹配0个或1个a
a|b 匹配a或b
a(m) 匹配m个a
a(m,) 匹配大于等于m个a
a(,n) 匹配0到n个a
a(m,n) 匹配m个到n个a
(...) 将模式元素组成单一元素

18.5.2 Rand()函数
Rand()函数 产生随机数;
order by rand() 对记录进行随机排序;
order by rand() limit n 随机输出n行记录。
随机抽样例子:
select * from 表名 order by rand() limit 100;

18.5.3 利用group by的with rollup子句
SQL中使用group by的with rollup子句可以对每个分组的信息再进行聚合。
例子1:对所有分组再次聚合
select 分组列1,sum(聚合列),count(聚合列),avg(聚合列),max(聚合列),min(聚合列)
from 表名
where 1=1 group by 分组列1 with rollup;
例子2:先对分组按分组列1再聚合,再对所有分组再次聚合
select 分组列1,分组列2,sum(聚合列),count(聚合列),avg(聚合列),max(聚合列),min(聚合列)
from 表名
where 1=1 group by 分组列1,分组列2 with rollup;
注意:with rollup子句后不能再跟order by子句,但可以跟limit子句。

18.5.4 用bit group functions做统计
bit_and()函数:按位与操作;
bit_or()函数:按位或操作;
例子:分组后对聚合列进行按位与、按位或操作
select 分组列,bit_and(聚合列),bit_or(聚合列) from 表名 where 1=1 group by 分组列;

18.5.5 数据库名、表名大小写问题
操作系统大小写敏感性决定了数据库名、表名、表别名的大小写敏感性。
Unix和Linux对大小写是敏感的,Windows对大小写不敏感。
建议数据库名、表名统一采用大写或小写。
列名、索引名、存储过程名、函数名、触发器名在所有平台大小写均不敏感。
Mysql参数lower_case_tables_name=0,依据操作系统大小写敏感性保存并查找;
Mysql参数lower_case_tables_name=1,mysql自动转小写保存并查找;
Mysql参数lower_case_tables_name=2,依据操作系统大小写敏感性保存,mysql自动转小写查找。

18.5.6 使用外键需要注意的问题
InnoDB存储引擎支持外键;其余存储引擎不支持外键(不会报错,但不存在外键约束)。

18.6 小结

原文地址:https://www.cnblogs.com/BradMiller/p/10123397.html

时间: 2024-10-24 21:43:31

18.Mysql SQL优化的相关文章

mysql sql优化

前言 有人反馈之前几篇文章过于理论缺少实际操作细节.这篇文章就多一些可操作性的内容吧. 注:这篇文章是以 MySQL 为背景,非常多内容同一时候适用于其它关系型数据库,须要有一些索引知识为基础. 优化目标 1.降低 IO 次数 IO永远是数据库最easy瓶颈的地方,这是由数据库的职责所决定的,大部分数据库操作中超过90%的时间都是 IO 操作所占用的,降低 IO 次数是 SQL 优化中须要第一优先考虑.当然,也是收效最明显的优化手段. 2.减少 CPU 计算 除了 IO 瓶颈之外,SQL优化中须

mysql sql优化及注意事项

sql优化分析 通过slow_log等方式可以捕获慢查询sql,然后就是减少其对io和cpu的使用(不合理的索引.不必要的数据访问和排序)当我们面对具体的sql时,首先查看其执行计划A.看其是否使用索引B.查看其查询的记录数C.确定索引的代价是否过高D.是否可以使用复合索引E.是否有“using temporary”F.是否有“using filesort” 创建高效索引 mysql的innodb有自己特殊的聚集索引(数据是按聚集索引的顺序存储的并和索引存储在一起),索引访问效率较高,次要索引是

(MYSQL)SQL优化工具 - SQLAdvisor 安装使用详解

一.SQLAdvisor简介 SQLAdvisor是由美团点评公司技术工程部DBA团队(北京)开发维护的一个分析SQL给出索引优化建议的工具.它基于MySQL原生态词法解析,结合分析SQL中的where条件.聚合条件.多表Join关系 给出索引优化建议.目前SQLAdvisor在美团点评广泛应用,包括美团支付.酒店旅游.外卖.团购等产品线,公司内部对SQLAdvisor的开发全面转到github上,开源和内部使用保持一致. 二.SQLAdvisor安装 1.拉取最新代码 git clone ht

MySQL SQL优化之in与range查询

本文来自:http://hidba.ga/2014/09/24/in-and-range/ 首先我们来说下in()这种方式的查询.在<高性能MySQL>里面提及用in这种方式可以有效的替代一定的range查询,提升查询效率,因为在一条索引里面,range字段后面的部分是不生效的.使用in这种方式其实MySQL优化器是转化成了n*m种组合方式来进行查询,最终将返回值合并,有点类似union但是更高效.同时它存在这一些问题: 老版本的MySQL在IN()组合条件过多的时候会发生很多问题.查询优化可

Mysql——SQL优化-统计某种类型的个数

有时我们想统计某种类型有多少个,会用这个SQL.全表扫描之余,还要filesort,耗时1.34秒. mysql> select country,count(*) from t1 group by country; +---------+----------+ | country | count(*) | +---------+----------+ | NULL | 32 | | africa | 524288 | | america | 524288 | | china | 524288 |

mysql sql优化之expain

explain显示了mysql如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句. 1. id SELECT识别符.这是SELECT查询序列号.查询序号即为sql语句执行的顺序 2.select_type select类型,它有以下几种值 2.1 simple 它表示简单的select,没有union和子查询 2.2 primary 最外面的select,在有子查询的语句中,最外面的select查询就是primary,上图中就是这样 2.3 union  

MySQL SQL优化之‘%’

设计索引的主要目的就是帮助我们快速获取查询结果,而以%开头的like查询则不能够使用B-Tree索引.考虑到innodb的表都是聚簇表(类似于oracle中的索引组织表),且二级索引叶节点中记录的结构为(索引字段->主键字段),我们可以通过改写sql(mysql优化器比较笨,需要给它足够的提示)采取一种轻量级的方式代替全表扫:使用索引全扫描找到主键,再根据主键回表获取数据的方法.这种方式的速度优势在单行记录数据量较大.表中记录较多的情况下体现的尤为明显,因为此时索引全扫描带来的IO开销相对于全表

MySQL SQL优化之字符串索引隐式转换

之前有用户很不解:SQL语句非常简单,就是select * from test_1 where user_id=1 这种类型,而且user_id上已经建立索引了,怎么还是查询很慢? test_1的表结构: CREATE TABLE `test_1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` varchar(30) NOT NULL, `name` varchar(30) DEFAULT NULL, PRIMARY KEY (`id`),

MySQL SQL优化-让你脑洞大开

由于分库分表的原因,和开发规定了不能使用 表表JOIN 语句.因此,我们要将 JOIN 语句的转化成使用 IN 来做.如现在有 表 A(a_id, c_a)c_a有普通索引,表 B(b_id, c_a) 这两个表要关联, 应该转化为以下步骤处理: 先查询B中的 a_id SELECT c_a FROM B WHERE xxx; 使用 IN 查询 A 表 SELECT a_id, ... FROM A WHERE c_a IN(在 1 中查出来的 c_a) 场景 现在表的数据量有 800万. 一般