编写高质量的SQL需要从以下几个方面注意,基本原则、表字段注意事项、索引使用注意事项、SQL注意事项。
基本原则
一、尽量不要在数据库里做运算。如果遇到运算尽可能在应用程序层进行计算。
二、控制数据库表数量、控制单表数据量、控制表的字段数。建议单库不要超过四百张表,建议单表字段不要超过五十个,建议单表的数据量不要超过一千万。
三、不要编写大SQL、不要使用大事务。SQL尽量写的简单点拒绝编写大SQL,可以将大SQL拆分成多个小SQL,在应用层聚合。大事务拆分成多个小事务,快速提交。
表字段注意事项
一、选择合适数值字段类型。能用小字段类型的就用小字段类型,如tinyint就比int(1)在表示小数据时合适。
二、能用数字表示就不要用字符。如可以用无符号INT存储IP而不是字符串表示。
三、避免使用NULL字段。原因NULL字段查询优化难,含NULL复合索引失效。
四、少用或拆分TEXT/BLOB字段。字段太大需要更多的空间,性能低下,如需使用拆分到单独表。
五、不要在表字段中存储图片。
索引使用注意事项
一、合理添加索引。索引添加太多会影响更新速度。能够使用复合索引的避免加多个单独索引。
二、字符字段建立前缀索引。
三、不在索引列做运算。索引列做运算会导致索引失效。
四、尽量不使用外建。
SQL类注意事项
一、 SQL语句尽可能简单。大SQL拆分成多个小SQL。
二、事务编写尽量短小。事务即开即用用完立即关闭。
三、尽量不要使用select *。只取需要的列。
四、改写OR为IN或者改写为UNION *** 作。OR在数据量大的时候性能低于IN。
五、避免NOT、!=、>、NOT IN、NOT EXISTS、NOT LIKE等查询。
六、避免%前缀模糊查询。
七、能用UNION ALL不要用UNION。
八、GROUP BY中去除排序。自带排序。
九、同类型的字段做比较。字符类和字符类比较,数值类和数值类比较,不要混在一起比较。
十、尽量单表查询,尽量不要多表关联查询。多表关联查询可以拆分成单表查询在应用程序中聚合数据。
十一、复合索引的多列注意最左原则。
上述注意事项能避免很多性能低下的SQL,希望在开发过程中能引起注意。
分别是原子性、一致性、隔离性、持久性。
原子性是指事务包含的所有 *** 作要么全部成功,要么全部失败回滚,因此事务的 *** 作如果成功就必须要完全应用到数据库,如果 *** 作失败则不能对数据库有任何影响。
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。举例来说,假设用户A和用户B两者的钱加起来一共是1000,那么不管A和B之间如何转账、转几次账,事务结束后两个用户的钱相加起来应该还得是1000,这就是事务的一致性。
隔离性是当多个用户并发访问数据库时,比如同时 *** 作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的 *** 作所干扰,多个并发事务之间要相互隔离。关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的 *** 作。例如我们在使用JDBC *** 作数据库时,在提交事务方法后,提示用户事务 *** 作完成,当我们程序执行完成直到看到提示后,就可以认定事务已经正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成。否则的话就会造成我们虽然看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。这是不允许的。
在数据库 *** 作中,在并发的情况下可能出现如下问题:
正是为了解决以上情况,数据库提供了几种隔离级别。
数据库事务的隔离级别有4个,由低到高依次为Read uncommitted(未授权读取、读未提交)、Read committed(授权读取、读提交)、Repeatable read(可重复读取)、Serializable(序列化),这四个级别可以逐个解决脏读、不可重复读、幻象读这几类问题。
虽然数据库的隔离级别可以解决大多数问题,但是灵活度较差,为此又提出了悲观锁和乐观锁的概念。
悲观锁,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制。也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统的数据访问层中实现了加锁机制,也无法保证外部系统不会修改数据。
商品t_items表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单(此时该商品无法再次下单),那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。
如果不采用锁,那么 *** 作方法如下:
但是上面这种场景在高并发访问的情况下很可能会出现问题。例如当第一步 *** 作中,查询出来的商品status为1。但是当我们执行第三步Update *** 作的时候,有可能出现其他人先一步对商品下单把t_items中的status修改为2了,但是我们并不知道数据已经被修改了,这样就可能造成同一个商品被下单2次,使得数据不一致。所以说这种方式是不安全的。
在上面的场景中,商品信息从查询出来到修改,中间有一个处理订单的过程,使用悲观锁的原理就是,当我们在查询出t_items信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为t_items被锁定了,就不会出现有第三者来对其进行修改了。需要注意的是,要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新 *** 作后,MySQL会立刻将结果进行提交。我们可以使用命令设置MySQL为非autocommit模式: set autocommit=0
设置完autocommit后,我们就可以执行我们的正常业务了。具体如下:
上面的begin/commit为事务的开始和结束,因为在前一步我们关闭了mysql的autocommit,所以需要手动控制事务的提交。
上面的第一步我们执行了一次查询 *** 作: select status from t_items where id=1 for update 与普通查询不一样的是,我们使用了 select…for update 的方式,这样就通过数据库实现了悲观锁。此时在t_items表中,id为1的那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。需要注意的是,在事务中,只有 SELECT ... FOR UPDATE 或 LOCK IN SHARE MODE *** 作同一个数据时才会等待其它事务结束后才执行,一般 SELECT ... 则不受此影响。拿上面的实例来说,当我执行 select status from t_items where id=1 for update 后。我在另外的事务中如果再次执行 select status from t_items where id=1 for update 则第二个事务会一直等待第一个事务的提交,此时第二个查询处于阻塞的状态,但是如果我是在第二个事务中执行 select status from t_items where id=1 则能正常查询出数据,不会受第一个事务的影响。
使用 select…for update 会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键或者索引,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。举例如下:
1、 select * from t_items where id=1 for update
这条语句明确指定主键(id=1),并且有此数据(id=1的数据存在),则采用row lock。只锁定当前这条数据。
2、 select * from t_items where id=3 for update
这条语句明确指定主键,但是却查无此数据,此时不会产生lock(没有元数据,又去lock谁呢?)。
3、 select * from t_items where name='手机' for update
这条语句没有指定数据的主键,那么此时产生table lock,即在当前事务提交前整张数据表的所有字段将无法被查询。
4、 select * from t_items where id>0 for update 或者 select * from t_items where id>1 for update (注:>在SQL中表示不等于)
上述两条语句的主键都不明确,也会产生table lock。
5、 select * from t_items where status=1 for update (假设为status字段添加了索引)
这条语句明确指定了索引,并且有此数据,则产生row lock。
6、 select * from t_items where status=3 for update (假设为status字段添加了索引)
这条语句明确指定索引,但是根据索引查无此数据,也就不会产生lock。
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以只会在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回用户错误的信息,让用户决定如何去做。实现乐观锁一般来说有以下2种方式:
MySQL 中事务的隔离级别一共分为四种,分别如下:
序列化(SERIALIZABLE):如果隔离级别为序列化,则用户之间通过一个接一个顺序地执行当前的事务,这种隔离级别提供了事务之间最大限度的隔离。
可重复读(REPEATABLE READ):在可重复读在这一隔离级别上,事务不会被看成是一个序列。不过,当前正在执行事务的变化仍然不能被外部看到,也就是说,如果用户在另外一个事务中执行同条 SELECT 语句数次,结果总是相同的。(因为正在执行的事务所产生的数据变化不能被外部看到)。
提交读(READ COMMITTED):READ COMMITTED 隔离级别的安全性比 REPEATABLE READ 隔离级别的安全性要差。处于 READ COMMITTED 级别的事务可以看到其他事务对数据的修改。也就是说,在事务处理期间,如果其他事务修改了相应的表,那么同一个事务的多个 SELECT 语句可能返回不同的结果。
未提交读(READ UNCOMMITTED):READ UNCOMMITTED 提供了事务之间最小限度的隔离。除了容易产生虚幻的读 *** 作和不能重复的读 *** 作外,处于这个隔离级的事务可以读到其他事务还没有提交的数据,如果这个事务使用其他事务不提交的变化作为计算的基础,然后那些未提交的变化被它们的父事务撤销,这就导致了大量的数据变化。
应用环境
与其他的大型数据库例如 Oracle、DB2、SQL Server等相比,MySQL自有它的不足之处,但是这丝毫也没有减少它受欢迎的程度。对于一般的个人使用者和中小型企业来说,MySQL提供的功能已经绰绰有余,而且由于 MySQL是开放源码软件,因此可以大大降低总体拥有成本。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)