精华内容
下载资源
问答
  • 数据库中读取Blob对象图片并显示

    千次阅读 2014-10-10 15:50:47
    大致方法就是,从数据库中读出Blob的流来,写到页面去:    Connection conn = DBManager.getConnection();  String sql = "SELECT picture FROM teacher WHERE id=1";  PreparedStatement ps = null;  ...

    第一种方法:

    大致方法就是,从数据库中读出Blob的流来,写到页面中去:

     

      Connection conn = DBManager.getConnection();

      String sql = "SELECT picture FROM teacher WHERE id=1";

      PreparedStatement ps = null;

      ResultSet rs = null;

      InputStream is = null;

      OutputStream os = null;

      try {

       ps = conn.prepareStatement(sql);

       rs = ps.executeQuery();

      

       if(rs.next()){

        is = rs.getBinaryStream(1);

       }

      

       response.setContentType("text/html");

       os = response.getOutputStream();

      

       int num;

       byte buf[] = new byte[1024];

      

       while(   (num=is.read(buf))!=-1   ){

        os.write(buf, 0, num);

       }

      

      } catch (SQLException e) {

       e.printStackTrace();

      }

     

      try {

       is.close();

       os.close();

       rs.close();

       ps.close();

      } catch (SQLException e) {

       e.printStackTrace();

      }

     

    在页面中:

     

    <%

    String path = request.getContextPath();

    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

    %>

     

    <img name="pic" src="<%=basePath+"servlet/DownloadAsStream"%>"/>

     

    搞定。

     

     

     

    第二种方法:

    整个流程分为四步,连接oracle数据库 -> 读取blob图片字段 -> 对图片进行缩放 ->把图片展示在jsp页面上。

    import java.sql.*;

    import java.io.*;

     

    import javax.imageio.ImageIO;

    import java.awt.image.BufferedImage;

    import java.awt.image.AffineTransformOp;

    import java.awt.geom.AffineTransform;

     

    public class OracleQueryBean {

        private final String oracleDriverName = "oracle.jdbc.driver.OracleDriver";

        private Connection myConnection = null;

       

        private String strTabName;

       

        private String strIDName;

      

        private String strImgName;

       

        public OracleQueryBean(){

            try{

                Class.forName(oracleDriverName);

            }catch(ClassNotFoundException ex){

                System.out.println("加载jdbc驱动失败,原因:" + ex.getMessage());

            }

        }

       

        public Connection getConnection(){

            try{

            //用户名+密码; 以下使用的Test就是Oracle里的表空间

            //从配置文件中读取数据库信息

            GetPara oGetPara = new GetPara();

            String strIP = oGetPara.getPara("serverip");

            String strPort = oGetPara.getPara("port");

            String strDBName = oGetPara.getPara("dbname");

            String strUser = oGetPara.getPara("user");

            String strPassword = oGetPara.getPara("password");

           

            this.strTabName = oGetPara.getPara("tablename");

            this.strIDName = oGetPara.getPara("imgidname");

            this.strImgName = oGetPara.getPara("imgname");

           

            String oracleUrlToConnect ="jdbc:oracle:thin:@"+strIP+":"+strPort+":"+strDBName;

                this.myConnection = DriverManager.getConnection(oracleUrlToConnect, strUser, strPassword);

            }catch(Exception ex){

                System.out.println("Can not get connection:" + ex.getMessage());

                System.out.println("请检测配置文件中的数据库信息是否正确." );

            }

            return this.myConnection;

        }

    }


    2. 读取blob字段

     

    在OracleQueryBean类中增加一个函数,来进行读取,具体代码如下:

     

       public byte[] GetImgByteById(String strID, int w, int h){

        //System.out.println("Get img data which id is " + nID);

        if(myConnection == null)

             this.getConnection();

        byte[] data = null;

        try {

                Statement stmt = myConnection.createStatement();

                ResultSet myResultSet = stmt.executeQuery("select " + this.strIDName + " from " + this.strTabName + " where " + this.strIDName + "=" + strID);

               

                StringBuffer myStringBuffer = new StringBuffer();

                if (myResultSet.next()) {

                    java.sql.Blob blob = myResultSet.getBlob(this.strImgName);

                    InputStream inStream = blob.getBinaryStream();

                    try {

                        long nLen = blob.length();

                        int nSize = (int) nLen;

                        //System.out.println("img data size is :" + nSize);

                        data = new byte[nSize];

                        inStream.read(data);

                        inStream.close();

                    } catch (IOException e) {

                        System.out.println("获取图片数据失败,原因:" + e.getMessage());

                    }

                   

                    data = ChangeImgSize(data, w, h);

                }

                System.out.println(myStringBuffer.toString());

                myConnection.commit();

                myConnection.close();

            } catch (SQLException ex) {

                System.out.println(ex.getMessage());

            }

            return data;

    }

     


    3. 缩放图片

    因为图片的大小可能不一致,但是在页面中输出的大小需要统一,所以需要

    在OracleQueryBean类中增加一个函数,来进行缩放,具体代码如下:

     

        private byte[] ChangeImgSize(byte[] data, int nw, int nh){

        byte[] newdata = null;

        try{

             BufferedImage bis = ImageIO.read(new ByteArrayInputStream(data));

                int w = bis.getWidth();

                int h = bis.getHeight();

                double sx = (double) nw / w;

                double sy = (double) nh / h;

                AffineTransform transform = new AffineTransform();

                transform.setToScale(sx, sy);

                AffineTransformOp ato = new AffineTransformOp(transform, null);

                //原始颜色

                BufferedImage bid = new BufferedImage(nw, nh, BufferedImage.TYPE_3BYTE_BGR);

                ato.filter(bis, bid);

               

                //转换成byte字节

                ByteArrayOutputStream baos = new ByteArrayOutputStream();

                ImageIO.write(bid, "jpeg", baos);

                newdata = baos.toByteArray();

               

        }catch(IOException e){

             e.printStackTrace();

        }

        return newdata;

    }


    4. 展示在页面

    页面使用OracleQueryBean来根据用户提供的图片id进行查询,在读取并进行缩放后,通过jsp页面进行展示,具体代码如下:

     

    <%@ page language="java" contentType="text/html;;charset=gbk" %>

    <jsp:useBean id="OrcleQuery" scope="page" class="HLFtiDemo.OracleQueryBean" />

    <%

        response.setContentType("image/jpeg");

        //图片在数据库中的 ID

        String strID = request.getParameter("id");

        //要缩略或放大图片的宽度

        String strWidth = request.getParameter("w");

        //要缩略或放大图片的高度

        String strHeight = request.getParameter("h");

        byte[] data = null;

        if(strID != null){

            int nWith = Integer.parseInt(strWidth);

            int nHeight = Integer.parseInt(strHeight);

            //获取图片的byte数据

            data = OrcleQuery.GetImgByteById(strID, nWith, nHeight);

            ServletOutputStream op = response.getOutputStream();       

           op.write(data, 0, data.length);

           op.close();

           op = null;

            response.flushBuffer();

            //清除输出流,防止释放时被捕获异常

            out.clear();

            out = pageContext.pushBody();

        }

    %>

     


    5. OracleQueryBean查询类的整体代码

    OracleQueryBean.java文件代码如下所示:

     

    import java.sql.*;

    import java.io.*;

     

    import javax.imageio.ImageIO;

    import java.awt.image.BufferedImage;

    import java.awt.image.AffineTransformOp;

    import java.awt.geom.AffineTransform;

     

    public class OracleQueryBean {

        private final String oracleDriverName = "oracle.jdbc.driver.OracleDriver";

     

        private Connection myConnection = null;

       

       

        private String strTabName;

       

        private String strIDName;

       

        private String strImgName;

       

        public OracleQueryBean(){

            try{

                Class.forName(oracleDriverName);

            }catch(ClassNotFoundException ex){

                System.out.println("加载jdbc驱动失败,原因:" + ex.getMessage());

            }

        }

       

        public Connection getConnection(){

            try{

            //用户名+密码; 以下使用的Test就是Oracle里的表空间

            //从配置文件中读取数据库信息

            GetPara oGetPara = new GetPara();

            String strIP = oGetPara.getPara("serverip");

            String strPort = oGetPara.getPara("port");

            String strDBName = oGetPara.getPara("dbname");

            String strUser = oGetPara.getPara("user");

            String strPassword = oGetPara.getPara("password");

           

            this.strTabName = oGetPara.getPara("tablename");

            this.strIDName = oGetPara.getPara("imgidname");

            this.strImgName = oGetPara.getPara("imgname");

           

            String oracleUrlToConnect ="jdbc:oracle:thin:@"+strIP+":"+strPort+":"+strDBName;

                this.myConnection = DriverManager.getConnection(oracleUrlToConnect, strUser, strPassword);

            }catch(Exception ex){

                System.out.println("Can not get connection:" + ex.getMessage());

                System.out.println("请检测配置文件中的数据库信息是否正确." );

            }

            return this.myConnection;

        }

       

        public byte[] GetImgByteById(String strID, int w, int h){

        //System.out.println("Get img data which id is " + nID);

        if(myConnection == null)

             this.getConnection();

        byte[] data = null;

        try {

                Statement stmt = myConnection.createStatement();

                ResultSet myResultSet = stmt.executeQuery("select " + this.strIDName + " from " + this.strTabName + " where " + this.strIDName + "=" + strID);

               

                StringBuffer myStringBuffer = new StringBuffer();

                if (myResultSet.next()) {

                    java.sql.Blob blob = myResultSet.getBlob(this.strImgName);

                    InputStream inStream = blob.getBinaryStream();

                    try {

                        long nLen = blob.length();

                        int nSize = (int) nLen;

                        //System.out.println("img data size is :" + nSize);

                        data = new byte[nSize];

                        inStream.read(data);

                        inStream.close();

                    } catch (IOException e) {

                        System.out.println("获取图片数据失败,原因:" + e.getMessage());

                    }

                   

                    data = ChangeImgSize(data, w, h);

                }

                System.out.println(myStringBuffer.toString());

                myConnection.commit();

                myConnection.close();

            } catch (SQLException ex) {

                System.out.println(ex.getMessage());

            }

            return data;

        }

       

       

        public byte[] GetImgByteById(String strID){

        //System.out.println("Get img data which id is " + nID);

        if(myConnection == null)

             this.getConnection();

        byte[] data = null;

        try {

                Statement stmt = myConnection.createStatement();

                ResultSet myResultSet = stmt.executeQuery("select " + this.strIDName + " from " + this.strTabName + " where " + this.strIDName + "=" + strID);

               

                StringBuffer myStringBuffer = new StringBuffer();

                if (myResultSet.next()) {

                    java.sql.Blob blob = myResultSet.getBlob(this.strImgName);

                    InputStream inStream = blob.getBinaryStream();

                    try {

                        long nLen = blob.length();

                        int nSize = (int) nLen;

                        data = new byte[nSize];

                        inStream.read(data);

                        inStream.close();

                    } catch (IOException e) {

                        System.out.println("获取图片数据失败,原因:" + e.getMessage());

                    }

                }

                System.out.println(myStringBuffer.toString());

                myConnection.commit();

                myConnection.close();

            } catch (SQLException ex) {

                System.out.println(ex.getMessage());

            }

            return data;

        }

       

       

        private byte[] ChangeImgSize(byte[] data, int nw, int nh){

        byte[] newdata = null;

        try{

             BufferedImage bis = ImageIO.read(new ByteArrayInputStream(data));

                int w = bis.getWidth();

                int h = bis.getHeight();

                double sx = (double) nw / w;

                double sy = (double) nh / h;

                AffineTransform transform = new AffineTransform();

                transform.setToScale(sx, sy);

                AffineTransformOp ato = new AffineTransformOp(transform, null);

                //原始颜色

                BufferedImage bid = new BufferedImage(nw, nh, BufferedImage.TYPE_3BYTE_BGR);

                ato.filter(bis, bid);          

                //转换成byte字节

                ByteArrayOutputStream baos = new ByteArrayOutputStream();

                ImageIO.write(bid, "jpeg", baos);

                newdata = baos.toByteArray();

        }catch(IOException e){

             e.printStackTrace();

        }

        return newdata;

        }

    }


    /*************************************************************************/


    下面是我的存储读取blob图片的案例

    import java.sql.*;  
    import java.io.*; 
    public class InsertPhoto {
    	public static void main(String[] args) throws Exception{
    			Class.forName("com.mysql.jdbc.Driver");  
    		   Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1/wiseweb?user=root&password=root");  
    		   File f = new File("e:/123.jpg");  
    		   FileInputStream fis = new FileInputStream(f);  
    		   String sql = "insert into photo(photo,photoName) values(?,?)";  
    		   PreparedStatement pstmt = con.prepareStatement(sql);  
    		   pstmt.setBinaryStream(1,fis,(int)f.length());  
    		   pstmt.setString(2, "测试图片");
    		   pstmt.executeUpdate();  
    		   fis.close();  
    		   pstmt.close();  
    		   con.close(); 
    	}
    }
    

    import java.awt.image.BufferedImage;
    import java.io.BufferedInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.sun.image.codec.jpeg.JPEGCodec;
    import com.sun.image.codec.jpeg.JPEGImageEncoder;
    
    
    public class ReadPhoto extends HttpServlet{
    
    	private static final long serialVersionUID = 1L;
    	
    	public void doGet(HttpServletRequest request, HttpServletResponse response){
    		if(request.getParameter("id") != null){
    			response.setContentType("image/jpeg");
    			try {
    				InputStream is = query_getPhotoImageBlob(Integer.parseInt(request.getParameter("id"))) ;
    				if(is != null){
    					is = new BufferedInputStream(is) ;
    					BufferedImage bi = ImageIO.read(is) ;
    					OutputStream os = response.getOutputStream() ;
    					JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(os) ;
    					encoder.encode(bi);
    				    os.close();  
    				    is.close();  
    				}
    			} catch(IOException e){
    				e.printStackTrace();
    			}catch (NumberFormatException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			} catch (ClassNotFoundException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			} catch (SQLException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    	}
    	
    	public static InputStream query_getPhotoImageBlob(int id) throws ClassNotFoundException, SQLException{  
    		   String sql = "select photo from photo where id="+id;  
    		   Connection con = null;  
    		   Statement stmt = null;  
    		   ResultSet rs = null;  
    		   InputStream result = null;  
    		   try {  
    		    Class.forName("com.mysql.jdbc.Driver");  
    			con = DriverManager.getConnection("jdbc:mysql://127.0.0.1/wiseweb?user=root&password=root");   
    		    stmt = con.createStatement();  
    		    rs = stmt.executeQuery(sql);  
    		    if (rs.next())  
    		    result = rs.getBlob("photo").getBinaryStream();  
    		   } catch (SQLException e) {  
    		    // TODO: handle exception  
    		    System.err.println(e.getMessage());  
    		   }finally{  
    			   rs.close();  
    			   stmt.close();  
    			   con.close(); 
    		   }  
    		   return result;  
    		} 
    }
    

    jsp显示

    	<img style="width:320px;height:240px" src="<%=basePath%>/genImage?id=3"/>  

    web.xml中配置

    <servlet>
    			<servlet-name>genImage</servlet-name>
    			<servlet-class>ReadPhoto</servlet-class>
    		</servlet>
    		<servlet-mapping>
    			<servlet-name>genImage</servlet-name>
    			<url-pattern>/genImage</url-pattern>
    		</servlet-mapping>


    展开全文
  • 又拍网是一个照片分享社区,从2005年6月至今积累了260万用户,1.1亿张照片,目前的日访问量为200多万。5年的发展历程里经历过许多起伏,也积累了一些经验,在这篇文章里,我要介绍一些我们在技术上的积累。 ...

    注: 关于数据库分库,横切,纵切,这个话题已经谈了好多年。这篇文章,是我找到的非常好的一篇分库的分享文。

     

    又拍网是一个照片分享社区,从2005年6月至今积累了260万用户,1.1亿张照片,目前的日访问量为200多万。5年的发展历程里经历过许多起伏,也积累了一些经验,在这篇文章里,我要介绍一些我们在技术上的积累。

    又拍网和大多数Web2.0站点一样,构建于大量开源软件之上,包括MySQLPHPnginxPythonmemcachedredisSolrHadoopRabbitMQ等等。又拍网的服务器端开发语言主要是PHPPython,其中PHP用于编写Web逻辑(通过HTTP和用户直接打交道), 而Python则主要用于开发内部服务和后台任务。在客户端则使用了大量的Javascript, 这里要感谢一下MooTools这个JS框架,它使得我们很享受前端开发过程。 另外,我们把图片处理过程从PHP进程里独立出来变成一个服务。这个服务基于nginx,但是是作为nginx的一个模块而开放REST API。


    图1:开发语言

    由于PHP的单线程模型,我们把耗时较久的运算和I/O操作从HTTP请求周期中分离出来, 交给由Python实现的任务进程来完成,以保证请求响应速度。这些任务主要包括:邮件发送、数据索引、数据聚合和好友动态推送(稍候会有介绍)等等。通常这些任务由用户触发,并且,用户的一个行为可能会触发多种任务的执行。 比如,用户上传了一张新的照片,我们需要更新索引,也需要向他的朋友推送一条新的动态。PHP通过消息队列(我们用的是RabbitMQ)来触发任务执行。


    图2:PHP和Python的协作

    数据库一向是网站架构中最具挑战性的,瓶颈通常出现在这里。又拍网的照片数据量很大,数据库也几度出现严重的压力问题。 因此,这里我主要介绍一下又拍网在分库设计这方面的一些尝试。

    分库设计

    和很多使用MySQL的2.0站点一样,又拍网的MySQL集群经历了从最初的一个主库一个从库、到一个主库多个从库、 然后到多个主库多个从库的一个发展过程。


    图3:数据库的进化过程

    最初是由一台主库和一台从库组成,当时从库只用作备份和容灾,当主库出现故障时,从库就手动变成主库,一般情况下,从库不作读写操作(同步除外)。随着压力的增加,我们加上了memcached,当时只用其缓存单行数据。 但是,单行数据的缓存并不能很好地解决压力问题,因为单行数据的查询通常很快。所以我们把一些实时性要求不高的Query放到从库去执行。后面又通过添加多个从库来分流查询压力,不过随着数据量的增加,主库的写压力也越来越大。

    在参考了一些相关产品和其它网站的做法后,我们决定进行数据库拆分。也就是将数据存放到不同的数据库服务器中,一般可以按两个纬度来拆分数据:

    垂直拆分:是指按功能模块拆分,比如可以将群组相关表和照片相关表存放在不同的数据库中,这种方式多个数据库之间的表结构不同

    水平拆分:而水平拆分是将同一个表的数据进行分块保存到不同的数据库中,这些数据库中的表结构完全相同

    拆分方式

    一般都会先进行垂直拆分,因为这种方式拆分方式实现起来比较简单,根据表名访问不同的数据库就可以了。但是垂直拆分方式并不能彻底解决所有压力问题,另外,也要看应用类型是否合适这种拆分方式。如果合适的话,也能很好的起到分散数据库压力的作用。比如对于豆瓣我觉得比较适合采用垂直拆分, 因为豆瓣的各核心业务/模块(书籍、电影、音乐)相对独立,数据的增加速度也比较平稳。不同的是,又拍网的核心业务对象是用户上传的照片,而照片数据的增加速度随着用户量的增加越来越快。压力基本上都在照片表上,显然垂直拆分并不能从根本上解决我们的问题,所以,我们采用水平拆分的方式。

    拆分规则

    水平拆分实现起来相对复杂,我们要先确定一个拆分规则,也就是按什么条件将数据进行切分。 一般2.0网站都以用户为中心,数据基本都跟随用户,比如用户的照片、朋友和评论等等。因此一个比较自然的选择是根据用户来切分。每个用户都对应一个数据库,访问某个用户的数据时, 我们要先确定他/她所对应的数据库,然后连接到该数据库进行实际的数据读写。

    那么,怎么样对应用户和数据库呢?我们有这些选择:

    按算法对应

    最简单的算法是按用户ID的奇偶性来对应,将奇数ID的用户对应到数据库A,而偶数ID的用户则对应到数据库B。这个方法的最大问题是,只能分成两个库。另一个算法是按用户ID所在区间对应,比如ID在0-10000之间的用户对应到数据库A, ID在10000-20000这个范围的对应到数据库B,以此类推。按算法分实现起来比较方便,也比较高效,但是不能满足后续的伸缩性要求,如果需要增加数据库节点,必需调整算法或移动很大的数据集, 比较难做到在不停止服务的前提下进行扩充数据库节点。

    按索引/映射表对应

    这种方法是指建立一个索引表,保存每个用户的ID和数据库ID的对应关系,每次读写用户数据时先从这个表获取对应数据库。新用户注册后,在所有可用的数据库中随机挑选一个为其建立索引。这种方法比较灵活,有很好的伸缩性。一个缺点是增加了一次数据库访问,所以性能上没有按算法对应好。

    比较之后,我们采用的是索引表的方式,我们愿意为其灵活性损失一些性能,更何况我们还有memcached, 因为索引数据基本不会改变的缘故,缓存命中率非常高。所以能很大程度上减少了性能损失。


    图4:数据访问过程

    索引表的方式能够比较方便地添加数据库节点,在增加节点时,只要将其添加到可用数据库列表里即可。 当然如果需要平衡各个节点的压力的话,还是需要进行数据的迁移,但是这个时候的迁移是少量的,可以逐步进行。要迁移用户A的数据,首先要将其状态置为迁移数据中,这个状态的用户不能进行写操作,并在页面上进行提示。 然后将用户A的数据全部复制到新增加的节点上后,更新映射表,然后将用户A的状态置为正常,最后将原来对应的数据库上的数据删除。这个过程通常会在临晨进行,所以,所以很少会有用户碰到迁移数据中的情况。

    当然,有些数据是不属于某个用户的,比如系统消息、配置等等,我们把这些数据保存在一个全局库中。

    问题

    分库会给你在应用的开发和部署上都带来很多麻烦。

    不能执行跨库的关联查询

    如果我们需要查询的数据分布于不同的数据库,我们没办法通过JOIN的方式查询获得。比如要获得好友的最新照片,你不能保证所有好友的数据都在同一个数据库里。一个解决办法是通过多次查询,再进行聚合的方式。我们需要尽量避免类似的需求。有些需求可以通过保存多份数据来解决,比如User-A和User-B的数据库分别是DB-1和DB-2, 当User-A评论了User-B的照片时,我们会同时在DB-1和DB-2中保存这条评论信息,我们首先在DB-2中的photo_comments表中插入一条新的记录,然后在DB-1中的user_comments表中插入一条新的记录。这两个表的结构如下图所示。这样我们可以通过查询photo_comments表得到User-B的某张照片的所有评论, 也可以通过查询user_comments表获得User-A的所有评论。另外可以考虑使用全文检索工具来解决某些需求, 我们使用Solr来提供全站标签检索和照片搜索服务。


    图5:评论表结构

    不能保证数据的一致/完整性

    跨库的数据没有外键约束,也没有事务保证。比如上面的评论照片的例子, 很可能出现成功插入photo_comments表,但是插入user_comments表时却出错了。一个办法是在两个库上都开启事务,然后先插入photo_comments,再插入user_comments, 然后提交两个事务。这个办法也不能完全保证这个操作的原子性。

    所有查询必须提供数据库线索

    比如要查看一张照片,仅凭一个照片ID是不够的,还必须提供上传这张照片的用户的ID(也就是数据库线索),才能找到它实际的存放位置。因此,我们必须重新设计很多URL地址,而有些老的地址我们又必须保证其仍然有效。我们把照片地址改成/photos/{username}/{photo_id}/的形式,然后对于系统升级前上传的照片ID, 我们又增加一张映射表,保存photo_id和user_id的对应关系。当访问老的照片地址时,我们通过查询这张表获得用户信息, 然后再重定向到新的地址。

    自增ID

    如果要在节点数据库上使用自增字段,那么我们就不能保证全局唯一。这倒不是很严重的问题,但是当节点之间的数据发生关系时,就会使得问题变得比较麻烦。我们可以再来看看上面提到的评论的例子。如果photo_comments表中的comment_id的自增字段,当我们在DB-2.photo_comments表插入新的评论时, 得到一个新的comment_id,假如值为101,而User-A的ID为1,那么我们还需要在DB-1.user_comments表中插入(1, 101 ...)。 User-A是个很活跃的用户,他又评论了User-C的照片,而User-C的数据库是DB-3。 很巧的是这条新评论的ID也是101,这种情况很用可能发生。那么我们又在DB-1.user_comments表中插入一行像这样(1, 101 ...)的数据。 那么我们要怎么设置user_comments表的主键呢(标识一行数据)?可以不设啊,不幸的是有的时候(框架、缓存等原因)必需设置。那么可以以user_id、 comment_id和photo_id为组合主键,但是photo_id也有可能一样(的确很巧)。看来只能再加上photo_owner_id了, 但是这个结果又让我们实在有点无法接受,太复杂的组合键在写入时会带来一定的性能影响,这样的自然键看起来也很不自然。所以,我们放弃了在节点上使用自增字段,想办法让这些ID变成全局唯一。为此增加了一个专门用来生成ID的数据库,这个库中的表结构都很简单,只有一个自增字段id。 当我们要插入新的评论时,我们先在ID库的photo_comments表里插入一条空的记录,以获得一个唯一的评论ID。 当然这些逻辑都已经封装在我们的框架里了,对于开发人员是透明的。 为什么不用其它方案呢,比如一些支持incr操作的Key-Value数据库。我们还是比较放心把数据放在MySQL里。 另外,我们会定期清理ID库的数据,以保证获取新ID的效率。

    实现

    我们称前面提到的一个数据库节点为Shard,一个Shard由两个台物理服务器组成, 我们称它们为Node-A和Node-B,Node-A和Node-B之间是配置成Master-Master相互复制的。 虽然是Master-Master的部署方式,但是同一时间我们还是只使用其中一个,原因是复制的延迟问题, 当然在Web应用里,我们可以在用户会话里放置一个A或B来保证同一用户一次会话里只访问一个数据库, 这样可以避免一些延迟问题。但是我们的Python任务是没有任何状态的,不能保证和PHP应用读写相同的数据库。那么为什么不配置成Master-Slave呢?我们觉得只用一台太浪费了,所以我们在每台服务器上都创建多个逻辑数据库。 如下图所示,在Node-A和Node-B上我们都建立了shard_001和shard_002两个逻辑数据库, Node-A上的shard_001和Node-B上的shard_001组成一个Shard,而同一时间只有一个逻辑数据库处于Active状态。 这个时候如果需要访问Shard-001的数据时,我们连接的是Node-A上的shard_001, 而访问Shard-002的数据则是连接Node-B上的shard_002。以这种交叉的方式将压力分散到每台物理服务器上。 以Master-Master方式部署的另一个好处是,我们可以不停止服务的情况下进行表结构升级, 升级前先停止复制,升级Inactive的库,然后升级应用,再将已经升级好的数据库切换成Active状态, 原来的Active数据库切换成Inactive状态,然后升级它的表结构,最后恢复复制。 当然这个步骤不一定适合所有升级过程,如果表结构的更改会导致数据复制失败,那么还是需要停止服务再升级的。


    图6:数据库布局

    前面提到过添加服务器时,为了保证负载的平衡,我们需要迁移一部分数据到新的服务器上。为了避免短期内迁移的必要,我们在实际部署的时候,每台机器上部署了8个逻辑数据库, 添加服务器后,我们只要将这些逻辑数据库迁移到新服务器就可以了。最好是每次添加一倍的服务器, 然后将每台的1/2逻辑数据迁移到一台新服务器上,这样能很好的平衡负载。当然,最后到了每台上只有一个逻辑库时,迁移就无法避免了,不过那应该是比较久远的事情了。

    我们把分库逻辑都封装在我们的PHP框架里了,开发人员基本上不需要被这些繁琐的事情困扰。下面是使用我们的框架进行照片数据的读写的一些例子:

    <?php
        $Photos = new ShardedDBTable('Photos', 'yp_photos', 'user_id', array(
                    'photo_id'    => array('type' => 'long', 'primary' => true, 'global_auto_increment' => true),
                    'user_id'     => array('type' => 'long'),
                    'title'       => array('type' => 'string'),
                    'posted_date' => array('type' => 'date'),
                ));
    
        $photo = $Photos->new_object(array('user_id' => 1, 'title' => 'Workforme'));
        $photo->insert();
    
        // 加载ID为10001的照片,注意第一个参数为用户ID
        $photo = $Photos->load(1, 10001);
    
        // 更改照片属性
        $photo->title = 'Database Sharding';
        $photo->update();
    
        // 删除照片
        $photo->delete();
    
        // 获取ID为1的用户在2010-06-01之后上传的照片
        $photos = $Photos->fetch(array('user_id' => 1, 'posted_date__gt' => '2010-06-01'));
    ?>
    

    首先要定义一个ShardedDBTable对象,所有的API都是通过这个对象开放。第一个参数是对象类型名称, 如果这个名称已经存在,那么将返回之前定义的对象。你也可以通过get_table('Photos')这个函数来获取之前定义的Table对象。 第二个参数是对应的数据库表名,而第三个参数是数据库线索字段,你会发现在后面的所有API中全部需要指定这个字段的值。 第四个参数是字段定义,其中photo_id字段的global_auto_increment属性被置为true,这就是前面所说的全局自增ID, 只要指定了这个属性,框架会处理好ID的事情。

    如果我们要访问全局库中的数据,我们需要定义一个DBTable对象。

    <?php
        $Users = new DBTable('Users', 'yp_users', array(
                    'user_id'  => array('type' => 'long', 'primary' => true, 'auto_increment' => true),
                    'username' => array('type' => 'string'),
                ));
    ?>
    

    DBTable是ShardedDBTable的父类,除了定义时参数有些不同(DBTable不需要指定数据库线索字段),它们提供一样的API。

    缓存

    我们的框架提供了缓存功能,对开发人员是透明的。

    <?php
        $photo = $Photos->load(1, 10001);
    ?>
    

    比如上面的方法调用,框架先尝试以Photos-1-10001为Key在缓存中查找,未找到的话再执行数据库查询并放入缓存。当更改照片属性或删除照片时,框架负责从缓存中删除该照片。这种单个对象的缓存实现起来比较简单。稍微麻烦的是像下面这样的列表查询结果的缓存。

    <?php
        $photos = $Photos->fetch(array('user_id' => 1, 'posted_date__gt' => '2010-06-01'));
    ?>
    

    我们把这个查询分成两步,第一步先查出符合条件的照片ID,然后再根据照片ID分别查找具体的照片信息。 这么做可以更好的利用缓存。第一个查询的缓存Key为Photos-list-{shard_key}-{md5(查询条件SQL语句)}, Value是照片ID列表(逗号间隔)。其中shard_key为user_id的值1。目前来看,列表缓存也不麻烦。 但是如果用户修改了某张照片的上传时间呢,这个时候缓存中的数据就不一定符合条件了。所以,我们需要一个机制来保证我们不会从缓存中得到过期的列表数据。我们为每张表设置了一个revision,当该表的数据发生变化时(调用insert/update/delete方法), 我们就更新它的revision,所以我们把列表的缓存Key改为Photos-list-{shard_key}-{md5(查询条件SQL语句)}-{revision}, 这样我们就不会再得到过期列表了。

    revision信息也是存放在缓存里的,Key为Photos-revision。这样做看起来不错,但是好像列表缓存的利用率不会太高。因为我们是以整个数据类型的revision为缓存Key的后缀,显然这个revision更新的非常频繁,任何一个用户修改或上传了照片都会导致它的更新,哪怕那个用户根本不在我们要查询的Shard里。要隔离用户的动作对其他用户的影响,我们可以通过缩小revision的作用范围来达到这个目的。 所以revision的缓存Key变成Photos-{shard_key}-revision,这样的话当ID为1的用户修改了他的照片信息时, 只会更新Photos-1-revision这个Key所对应的revision。

    因为全局库没有shard_key,所以修改了全局库中的表的一行数据,还是会导致整个表的缓存失效。 但是大部分情况下,数据都是有区域范围的,比如我们的帮助论坛的主题帖子, 帖子属于主题。修改了其中一个主题的一个帖子,没必要使所有主题的帖子缓存都失效。 所以我们在DBTable上增加了一个叫isolate_key的属性。

    <?php
    $GLOBALS['Posts'] = new DBTable('Posts', 'yp_posts', array(
            'topic_id'    => array('type' => 'long', 'primary' => true),
            'post_id'     => array('type' => 'long', 'primary' => true, 'auto_increment' => true),
            'author_id'   => array('type' => 'long'),
            'content'     => array('type' => 'string'),
            'posted_at'   => array('type' => 'datetime'),
            'modified_at' => array('type' => 'datetime'),
            'modified_by' => array('type' => 'long'),
        ), 'topic_id');
    ?>
    

    注意构造函数的最后一个参数topic_id就是指以字段topic_id作为isolate_key,它的作用和shard_key一样用于隔离revision的作用范围。

    ShardedDBTable继承自DBTable,所以也可以指定isolate_key。 ShardedDBTable指定了isolate_key的话,能够更大幅度缩小revision的作用范围。 比如相册和照片的关联表yp_album_photos,当用户往他的其中一个相册里添加了新的照片时, 会导致其它相册的照片列表缓存也失效。如果我指定这张表的isolate_key为album_id的话, 我们就把这种影响限制在了本相册内。

    我们的缓存分为两级,第一级只是一个PHP数组,有效范围是Request。而第二级是memcached。这么做的原因是,很多数据在一个Request周期内需要加载多次,这样可以减少memcached的网络请求。另外我们的框架也会尽可能的发送memcached的gets命令来获取数据, 从而减少网络请求。

    总结

    这个架构使得我们在很长一段时间内都不必再为数据库压力所困扰。我们的设计很多地方参考了netlogflickr的实现,因此非常感谢他们将一些实现细节发布出来。

    关于作者:

    周兆兆(Zola,不是你熟知的那个),又拍网架构师。6年IT从业经验,不太专注于某项技术,对很多技术都感兴趣。


    展开全文
  • 本人在学习web开发过程一直使用MySQL数据库,所以根据自己的经验谈谈如何对MySQL数据库进行优化,水平较低,有错误希望大家指出来,谢谢! 一.数据库的优化 在一些大型项目,对数据库的读写性能要求较高时,...

    本人在学习web开发过程中一直使用MySQL数据库,所以根据自己的经验谈谈如何对MySQL数据库进行优化,水平较低,有错误希望大家指出来,谢谢!
    一.数据库的优化
    在一些大型项目中,对数据库的读写性能要求较高时,我们需要查找和定位慢查询,并对其优化,优化的方法如下:
    (1)创建索引:我们需要创建合适的索引,这样就可以先查询索引,然后直接找对应的记录;
    那么我们怎么定位和查找慢查询呢?在项目自验项目转测试之前,在启动MySQL数据库时开启慢查询,并且把执行慢的语句写到日志中,在运行一定时间后,通过查看日志找到慢查询语句。使用explain慢查询语句,来详细分析语句的问题。
    (2)分表:当一张表的数据比较多或者一张表的某些字段的值比较多并且很少使用时,我们需要采用水平分表和垂直分表来优化;
    我们在设计数据库表需要遵循范式,表的范式是必须符合1NF,才能满足2NF,进一步满足3NF。
    1NF:即表的列具有原子性,不可再分解,即列的信息,不能分解,只要数据库是关系型数据库,例如MySQL,Oracle,db2,sql server等等,就自动满足1NF,关系型数据库是不允许分割列的;
    2NF:表中的记录是唯一的,通常我们设计一个主键来实现;
    3NF:即数据库中的表中不要有冗余数据,通俗地说,表的信息,如果能够被推导出来,就不应该单独设计一个字段来存放(外键)
    反3NF:没有冗余的数据库未必是最好的数据库,有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据,具体的做法是:在概念数据模型设计时遵守第三范式,降低范式标准的工作放到物理数据模型设计时考虑,降低范式就是增加字段,允许冗余。在实际应用中,订单和订单项,相册浏览次数和照片的浏览次数设计时需要遵循反3NF。
    水平分表即按行分表,MySQL表数据一般达到百万级别,查询效率会很低,容易造成表锁,甚至堆积很多连接,甚至挂了,水平分表可以很大程度上减轻压力。
    水平分表的策略如下:
    (一)按时间分表,这种分表方式有一定的局限性,当数据有较强的时效性,如微博发送记录,微信消息记录等等,这种数据很少有用户会查询几个月前的数据,可以按月分表;
    (二)按区间范围分表:一般在有严格的自增id需求上,如按照user_id水平分表;
    (三)hash分表:通过一个原始目标的Id或者名称通过一定的hash算法计算出数据存储表的表名,然后访问相应的表。
    (3)读写分离:当一台数据库服务器不能满足需求时,我们需要采用读写分离的方式进行集群;
    一台数据库服务器支持的最大并发连接数是有限的,如果用户并发访问太多,一台服务器无法满足需求时,我们需要集群处理,Mysql的最常用的集群处理技术就是读写分离,其关键步骤分为主从同步和读写分离。
    主从同步:数据库最终会把数据持久化到磁盘上,如果集群必须确保每个数据库服务器的数据是一致的,能改变数据库数据 的操作都往主数据库去写,而其他的数据库从主数据库上同步数据。
    读写分离:使用负载均衡来实现写的操作都在主数据库服务器上完成,而读的操作在从数据库服务器上完成。
    (4)使用缓存:比较常用的方法是使用非关系型数据库redis来进行缓存;
    在持久层(dao:data access object)和数据库之间添加一个缓存器,如果用户访问的数据已经缓存起来,用户再次访问时可以直接从缓存中获取,不用访问数据库,而缓存是在内存完成的,访问速度快。
    作用:减轻数据库服务器压力,减少访问时间。
    Java中常用的缓存有:hibernate的二级缓存,但是该缓存不能完成分布式缓存,最常用的是使用redis或者memcache作为中央缓存,对缓存的数据进行集中处理。
    (5)一些常用的优化技巧。
    DDL优化:
    (一)通过禁用索引来提供导入数据性能,这个操作主要针对有数据库的表添加数据
    第一步:去除key;
    第二步:批量插入数据;
    第三步:恢复key。
    (二)关闭唯一校验
    (三)修改事务提交方式(导入)即变多次提交为一次

    DML优化(变多次提交为一次):
    例如
    insert into test values(1,2);
    insert into test values(1,3);
    insert into test values(1,4);
    我们可以合并为一条insert into test values(1,2),(1,3),(1,4);

    DQL优化
    order by 优化
    1.多用索引排序;
    2.普通结果排序(非索引排序)
    group by 优化是使用order by null 取消默认排序
    最常用的还是DDL优化和DML优化

    二.redis简单介绍和使用场景
    redis是一个key-value的nosql数据库,先存到内存中,会根据一定的策略持久化到磁盘,即使断电也不会丢失数据,支持的数据类型比较多。主要用来做缓存数据库和web集群时作为中央缓存存放session,关于session我在上一篇博客中做了简单的介绍,因为自身的水平较低,所以写的都是皮毛。
    redis的使用场景:
    (1)缓存:把经常需要查询的,很少修改的数据,放到读速度很快的内存空间中,以便下次访问减少访问时间,减轻服务器压力;
    (2)计数器:redis中的计数器是原子性的内存操作,可以解决库存溢出问题,进销存系统库存溢出。
    (3)session缓存服务器:web集群时作为session缓存服务器,缓存队列等

    redis对象保存方式
    如果是数据完全可以使用JSON方式,redis直接set get使用起来门槛较低,redis没有提供专门的设置对象方法,需要自己进行改写。如果担心JSON转对象会消耗资源的情况,这个问题需要考虑几个方面,第一,使用的JSON转换lib是否就会存在性能问题;第二:数据的数量级级别,如果是存储百万级的大数据对象,建议采用存储序列化对象方式,如果是少量的数据级对象或者数据对象字段不多,还是建议采用JSON转换成String方式。

    redis的数据淘汰机制
    在redis中,允许用户设置最大使用内存大小,在内存限定的情况下是很有用的,假如在一台8G内存的服务器上部署了4个redis服务点,每个服务点分配1.5G的内存大小,减少内存紧张的情况,由此获取更为稳健的服务。redis内存数据集大小上升到一定大小的时候,就会执行数据淘汰策略,redis提供了6种数据淘汰策略:volatile-lru,volatile-ttl,volatile-random,allkeys-lru,allkeys-random,no-enviction
    java访问redis
    我们可以使用jedis.java客户端来访问redis服务器,有点类似通过jdbc访问mysql一样,如果是spring集群,可以使用spring data来访问redis,spring data 只是对jedis的二次封装。当一台redis数据库服务器无法满足需求时,我们可以使用redis集群来处理,这有点类似MySQL的读写分离。
    希望大家积极指出错误,一定改正,谢谢!

    展开全文
  • 博客上传的照片会被放在网站的站点目录。自己也可以通过在动态网页的博客右键“复制图片的地址”,查看图片所放的具体位置。 第二步:将"/usr/share/nginx/html/wp-content/uploads/2020/11"这个目录先移放到...

    web服务器与NFS存储服务器建立连接
    因为真实的网站不可能只有一台web服务器,一般都是一个web集群。web集群不仅可以提高处理请求的效率,还可以提供冗余的能力,以防止一台web服务器坏掉网站就无法工作。
    我们通过浏览器访问一个网站时,网站的负载均衡服务器就会把我们的请求分发给web集群中的一台web服务器处理,若是web服务器没有和NFS存储服务器建立连接,那么客户在网站上传的图片、音频、视频、附件等数据就只会存储在处理你请求的那台web主机上。当你下次再访问相同的网站时,负载均衡服务器可能会把你的请求分发给web集群中其它的web服务器,这时你可能就会找不见之前上传到此网站的数据了。
    所以通过web服务器和NFS存储服务器建立连接,web集群中所有web主机把客户上传的图片、音频、视频等数据信息都存放在NFS存储服务器中,客户从网站获取之前上传的图片、音频等数也是从NFS存储服务器上获取。这样就不会出现找不见自己上传的数据的情况了。

    实验条件:

    • 一台部署好LNMP架构的虚拟主机作为web服务器
    • 一台部署好NFS服务的虚拟主机作为NFS存储服务器
    • 按照我这篇博文LNMP动态网页部署好动态网页

    第一步:
    在自己部署好的动态网页网页发布一个博文:
    在这里插入图片描述
    博客中上传的照片会被放在网站的站点目录中。自己也可以通过在动态网页的博客中右键“复制图片的地址”,查看图片所放的具体位置。
    在这里插入图片描述

    第二步:将"/usr/share/nginx/html/wp-content/uploads/2020/11"这个目录先移放到"/tmp"目录下,否则直接将NFS共享目录挂载在"/usr/share/nginx/html/wp-content/uploads/2020"下的话,就会把”11“这个目录中的数据全部覆盖掉。
    在这里插入图片描述
    第三步:将NFS共享目录挂载到"/usr/share/nginx/html/wp-content/uploads/2020"下,这样就可以实现动态网页中上传的图片数据都会被自动存储到NFS的共享目录中。
    在这里插入图片描述
    第四步:将之前移走的"11"目录,再移回"/usr/share/nginx/html/wp-content/uploads/2020"目录下。移动时可能会出现权限不允许的情况。这是因为在配置NFS配置文件"/etc/exports"时出现了问题:
    在这里插入图片描述

    web服务器和数据库服务器建立联系
    web服务器和数据库服务器建立联系的原因和上面的web服务器和NFS存储服务器建立联系的原因差不多。因为数据库服务器主要存储的就是字符数据信息,web集群中所有的web主机把客户在网站上传的所有的字符数据信息都存储在数据库服务器中,以防止客户两次登录相同的网站,却发现数据不一样。
    实验条件:

    • 一台部署LNMP架构的web服务器
    • 一台数据库服务器

    第一步:将web服务器本地数据库数据进行备份
    在这里插入图片描述
    第二步:将数据库备份文件迁移到数据库服务器
    在这里插入图片描述
    第三步:在数据库服务器中恢复数据信息
    在这里插入图片描述
    第四步:删除数据库服务器中数据库无用的用户信息;不删除的话,可能会致使以后无法登录数据库。
    在这里插入图片描述
    第五步:设置允许哪些主机以什么用户的身份可以操作数据库服务器中的相关数据库;这个用户就是动态网页初始化时设定的用户。
    在这里插入图片描述
    第六步:修改web服务器中动态网页的代码文件信息,让动态网页中产生的字符数据信息自动存储到数据库服务器中,而不是存储在本地。
    在这里插入图片描述
    第七步:停止web服务器上的mariadb数据库服务,以后的数据库服务就让数据库服务器中数据库服务提供。

    TIPS:
    a.数据库服务器中的数据库服务若是没有启动,访问网站时,浏览器页面会显示“Error establishing a database connection”,说明无法连接3306端口(数据库服务的端口),需要开启数据库服务
    b.若是web服务器上PHP服务没有开器,浏览器页面会报502错误,表示nginx服务无法和后端的PHP服务建立连接。
    c.wordpress动态网页会在初始化时会记录自己域名,若是只更改nginx服务扩展配置文件的域名,再使用浏览器访问新的域名时,浏览器会自动跳转到初始化时记录的域名上去。若是不想让浏览器跳转,可以在wordpress页面的设置中修改它记录的域名信息;也可以更改数据库中记录和域名有关的表的信息。

    展开全文
  • 整理自丁俊老师plsql文档: 1、基本介绍 oracle和pl/sql都支持lob(large object)类型,用来存储大...在pl/sql,可以声明的lob的类型变量如下: BFILE :二进制文件,存储在数据库外的操作系统文件,只读的。把
  • asp.net实现图片保存到数据库和从数据库中读取图片,代码如下: public void imgToDB(string sql) { //参数sql要求保存的imge变量名称为@images //调用方法如:imgToDB("update UserPhoto set Photo=@images ...
  • oracle数据库中的大对象

    千次阅读 2013-12-12 14:29:36
    整理自丁俊老师plsql文档: 1、基本介绍 oracle和pl/sql都支持lob(large object)类型,用来存储大数量数据,如...在pl/sql,可以声明的lob的类型变量如下: BFILE :二进制文件,存储在数据库外的操作系统
  • // 保存到数据库中的路径,线上 //String saveDBPath = request.getContextPath() + Global.USERFILES_BASE_URL + suffix + "/" + tempFileName; /* String saveDBPath = Global.USERFILES_BASE_URL + suffix + ...
  • Web上的用户登录功能安全

    万次阅读 多人点赞 2016-07-07 10:04:14
    Web上的用户登录功能应该是最基本的功能了,可是在我看过一些站点的用户登录功能后,我觉得很有必要写一篇文章教大家怎么来做用户登录功能。下面 的文章告诉大家这个功能可能并没有你所想像的那么简单,这是一个关系...
  • 宠物网站管理信息系统 一、 需求分析 该系统主要提供宠物商店与宠物主人之间的买卖功能,包括登录,对宠物的增删查改,用户信息的查询与修改,以及用户上传图片的功能。 通过选择宠物主人或者商店来进行登录,然后...
  • 本文实例讲述了Java从数据库中读取Blob对象图片并显示的方法。分享给大家供大家参考。具体实现方法如下: 第一种方法: 大致方法就是,从数据库中读出Blob的流来,写到页面去: 复制代码 代码如下: ...
  • 你会做Web上的用户登录功能吗?

    千次阅读 2011-12-22 12:54:30
    Web上的用户登录功能应该是最基本的功能了,可是在我看过一些站点的用户登录功能后,我觉得很有必要写一篇文章教大家怎么来做用户登录功能。下面的文章告诉大家这个功能可能并没有你所想像的那么简单,这是一个关系...
  • 照片是存到数据库oracle的Blob字段。 难点有两个: 1,图片的上传;2,Blob字段的读取。   先说图片的上传吧, 我使用common-fileupload来实现的 写个servlet,实现上传到服务器。   UpPhotoServlet....
  • WEB2.0网站构架分析

    2009-03-27 09:30:00
    FROM:http://www.fovweb.com/optimizer/web20-site-architecture-analysis/一、 web2.0网站常用可用性功能模块分析Web2.0网站是指将传统的网站构架(平台、内容源、用户、传播方式等)转化到以用户为核心的网站构架...
  • WEB用户安全登录功能

    千次阅读 2017-02-17 20:09:12
    Web上的用户登录功能应该是最基本的功能了,可是在我看过一些站点的用户登录功能后,我觉得很有必要写一篇文章教大家怎么来做用户登录功能。下面 的文章告诉大家这个功能可能并没有你所想像的那么简单,这是一个关系...
  • 数据库总结

    2018-06-29 18:39:22
    可以授予用户访问表的特定部分的权限,而不是整个表的访问权限。 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。   性能问题 因为视图不包含数据,所以每次使用视图时,都必须处理查询执行时需要...
  • 照片是存到数据库oracle的Blob字段。 难点有两个: 1,图片的上传;2,Blob字段的读取。   先说图片的上传吧, 我使用common-fileupload来实现的 写个servlet,实现上传到服务器。   ...
  • 数据库基础概述

    千次阅读 2013-05-15 16:06:47
    通过数据库管理系统,可以有效的组织和管理存储在数据库中的数据。如今,已经存在的Oracle、SQLServer、MySQL等诸多优秀的数据库。 详解内容: 数据存储方式 数据库在开发的作用 数据库访问技术 MySQL...
  • 我们在web项目经常有上传图片的功能,上传的图片存在哪里呢? 存储方式一般有:存入数据库中,跟随项目相对路径,还有就是再专门设计一个图片服务器用于图片处理。 当图片量大的时候,或者查询数据量大的时候...
  • 总结web应用常用的各种cachecache是提高应用性能重要的一个环节,写篇文章总结一下用过的各种对于动态内容的cache。文章以Nginx,Rails,Mysql,Redis作为例子,换成其他web服务器,语言,数据库,缓存服务都是...
  • 图片艺廊管理网站说明 数据库设计: 主要由三张表:用户表、图片表、用户图片对应关系表。 用户表: 主要字段如下: 用户ID 整型 主键 自增长; 用户姓名 字符型 ; 用户密码 字符型 ;...
  • 数据库系统设计概述

    2020-08-03 11:20:34
    本文将从数据库的发展、数据库的分类、常见数据库架构,数据库常见概念和技术等方面探讨这个我们接触最多的底层系统,并通过穿插不同数据库的实现原理,来了解数据库的具体实现。
  • Web开发常见的认证机制

    千次阅读 2017-03-14 18:12:41
    HTTP基本认证(HTTP Basic Auth)在HTTP,HTTP基本认证是一种允许Web浏览器或者其他客户端在请求时提供用户名和口令形式的身份凭证的一种登录验证方式。简单而言,HTTP基本认证就是我们平时在网站中最常用的通过...
  • 关于图片或者文件在数据库的存储方式归纳

    万次阅读 多人点赞 2016-07-31 19:45:14
    商品图片,用户上传的头像,其他方面的图片。目前业界存储图片有两种做法: 1、 把图片直接以二进制形式存储在数据库中 一般数据库提供一个二进制字段来存储二进制数据。...2、 图片存储在磁盘上,数据库字段
  • //得到数据库连接(驱动包是weblogic的,没有下载任何新版本)    Connection con= null ;   ResultSet rs= null ;     InputStream in= null ;   BufferedInputStream bis= null ;     ...
  • 云计算下的新型数据库技术    摘要:在这个信息化的时代,我们的一举一动都离...云计算通过整合,管理和调配分布在互联网的所有计算资源,以统一的界面同时向用户提供服务。互联网提供的各种计算形式的应用以及提

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,299
精华内容 4,519
关键字:

web网站中获取数据库中用户照片