Store database, good pattern for simultaneous access - php

I am kinda new to database designing so i ask for some advices or some kind of a good pattern.
The situation is that, there is one database, few tables and many users. How should i design the database, or / and which types of queries should i use, to make it work, if users can interact with the database simultaneously? I mean, they have access to and can change the same set of data.
I was thinking about transactions, but I am not sure, if that is the right / good / the only solution.
UPDATE:
By many i mean hundreds, maybe thousands at all. Clients will be connecting to MySQL through WWW page in PHP. They will use operations such: insert, update, delete and select, sometimes join. It's a small database for 5-20 clients and one-two admins. Clients will be updating and selecting info. I am thinking about transactions with storing some info in $_SESSION.

a simple approach that can be very effective is the row versioning.
add a version int field to the main table,
when insert, set it to 0
when update, increment it by one; in the where it should be the version field
EXAMPLE:
CREATE TABLE myTable (
id INT NOT NULL,
name VARCHAR(50) NOT NULL,
vs INT NOT NULL,
)
INSERT INTO myTable VALUES (1, 'Sebastian', 0)
-- first user reads, vs = 0
SELECT * FROM myTable WHERE id = 1
-- second user reads, vs = 0
SELECT * FROM myTable WHERE id = 1
-- first user writes, vs = 1
UPDATE myTable SET name = 'Juan Sebastian', vs = vs + 1 WHERE id = 1 AND vs = 0
(1 row affected)
-- second user writes, no rows affected, because vs is different, show error to the user or do your logic
UPDATE myTable SET name = 'Julian', vs = vs + 1 WHERE id = 1 AND vs = 0
(0 rows affected)

Use InnoDB as a engine type. In opposite to MyISAM it supports row-level blocking, so you wouldn't have to block entire table when someone is updating some record.

Related

Select random row where condition apply effectvely

I have a table of names with structure likes this :
id int(11) -Auto Increment PK
name varchar(20)
gender varchar(10)
taken tinyint - bool value
I want to get a random name of a single row where gender is say male and taken is false. How can I do that without slowing down ?
What comes to mind is, SELECT all the rows where gender = male and taken = false. Then use php's rand(1, total_rows) and use the name from that randomly generated record number for the array of results.
Or I can use, but RAND() is going to slow down the process (taken from other questions on stackoverflow)
SELECT * FROM xyz WHERE (`long`='0' AND lat='0') ORDER BY RAND() LIMIT 1
You can take the following approach:
select the id list that meet your criteria, like SELECT id FROM table WHERE name=...
choose an id randomly with php
fetch whole data with that id, like SELECT * FROM table WHERE id=<id>
This approach would maximize the query cache in MySQL. The query in step 3 has a great chance of hitting the same id, in which case query cache can accelerate database access. Further more, if caching like memcached or redis is used in the future, step 3 can also be taken care of by them, without even going to db.

How do I get the latest set ID in a MySQL DB table without sacrificing integrity?

I'm using PHP to insert groups of records into a MySQL DB.
Whenever I insert a group of records, I want to give that group a unique set ID that is incremented by 1 for each group of records in the DB.
Currently, I'm checking the latest set ID in the DB and incrementing it by 1 for each new set of records.
The thing that scares me though is what happens if I query the DB to get the latest set ID, and before I can insert a new set of records with that set ID + 1, another insert occurs on the table thus taking the set ID I was about to use?
While fairly unlikely, something like that could greatly sacrifice the integrity of the data.
What can I do to prevent such a thing from happening? Is there any way to temporarily lock the DB table so that no other inserts can occur until I have performed a SELECT/INSERT combo?
Locking the table is one option, but that approach impacts concurrency.
The approach I would recommend is that you use a separate table with AUTO_INCREMENT column, and use a separate INSERT into that table, and a SELECT LAST_INSERT_ID() to retrieve the auto_increment value.
And then use that value as the group identifier for the group of rows you insert into your original table.
The basic approach is:
LOCK TABLE foo WRITE;
SELECT MAX(id) + 1 FROM foo
INSERT ...
INSERT ...
UNLOCK TABLES;
Locking the table prevents any other process from changing the table until you explicitly unlock it.
Having said that, seriously consider just using a table with an AUTO_INCREMENT column. MySQL will do the work of maintaining unique keys wholly automatically, and then you can simply refer to those keys from your existing table.

managing concurrency using update queries where condition

I am using mysql, php.
table
user_meetup
id, user_id, meetup_id with unique on (user_id, meetup_id)
if my meetup is limited by places(10 places) 9 users already marked rsvp and if 2 users doing RSVP at the same time (i just have consider a case of concurrency)
A -> select count(id) from user_meetup -> result : 9 hence go ahead
B -> select count(id) from user_meetup -> result : 9 hence go ahead
A -> insert ......
B -> insert ......
now both of them will get place and my count became 11,
my solution was to add one more table
user_meetup_count
id, meetup_id, user_count ( count set to 0 as default value and record is created as soon as meetup is created)
now if use update query like
update user_meetup_count set user_count = user_count + 1 where user_count < 10
and if I write in user_meetup table based on number of rows updated by above query
if it returns 0 mean its full and if it returns 1 I got the place
will this work in case 2 user try at same time ?
is my approach to solve concurrency problem right ?
is there a better way ?
what are the tools to testing this type of situations ?
Use lock tables before counting. And unlock it after inserting.
Or you could use GET_LOCK then RELEASE_LOCK. With this you do not need to lock all entry table.
Or explore theme about gap locking for innodb tables. With this you need to use transactions.
And you could use jMeter for testing your queries.

Does MYSQL load the whole table into cache everytime?

Lets say I have a table, with say 1 million rows, with the first column being a primary key.
Then, if I run the following:
SELECT * FROM table WHERE id='tomato117' LIMIT 1
Does the table ALL get put into the cache (thereby causing the query to slow as more and more rows get added) or would the number of rows of the table not matter, since the query uses the primary key?
edit: (added limit 1)
If the id is define as primary key, which only one record with value tomato117, so limit does not useful.
Using SELECT * will trigger mysql read from disk because unlikely all columns are stored into index. (mysql not able to fetch from index) In theory, it will affect performance.
However, your sql is matching query cache condition. So, mysql will stored the result into query cache for subsequent usage.
If you query cache size is huge, mysql will keep store all sql results into query cache until memory full.
This come with a cost, if there is an update on your table, query cache invalidation will be harder for mysql.
http://www.mysqlperformanceblog.com/2007/03/23/beware-large-query_cache-sizes/
http://www.mysqlperformanceblog.com/2006/06/09/why-mysql-could-be-slow-with-large-tables/
nothing of the sort.
It will only fetch the row you selected and perhaps a few other blocks. They will remain in cache until something pushes them out.
By cache, I refer to the innodb buffer pool, not query cache, which should probably be off anyway.
SELECT * FROM table WHERE id = 'tomato117' LIMIT 1
When tomato117 is found, it stops searching, if you don't set LIMIT 1 it will search until end of table. tomato117 can be second, and it will still search 1 000 000 rows for other tomato117.
http://forge.mysql.com/wiki/Top10SQLPerformanceTips
Showing rows 0 - 0 (1 total, Query took 0.0159 sec)
SELECT *
FROM 'forum_posts'
WHERE pid = 643154
LIMIT 0 , 30
Showing rows 0 - 0 (1 total, Query took 0.0003 sec)
SELECT *
FROM `forum_posts`
WHERE pid = 643154
LIMIT 1
Table is about 1GB, 600 000+ rows.
If you add the word EXPLAIN before the word SELECT, it will show you a table with a summary of how many rows it's reading instead of the normal results.
If your table has an index on the id column (including if it's set as primary key), the engine will be able to jump straight to the exact row (or rows, for a non-unique index) and only read the minimal amount of date. If there's no index, it will need to read the whole table.

Transaction problem in MyISAM

Here is the problem. I have couple tables in MySQL MyISAM tables. And also i have several queries one depend on another. Something of this kind:
CREATE TABLE users (
name varchar(255) DEFAULT NULL PRYMARY KEY,
money int(10) unsigned DEFAULT NULL
);
INSERT INTO users(name, money) VALUES('user1', 700);
INSERT INTO users(name, money) VALUES('user2', 200);
I need to transfer money from 1 user to anouther
<?php
$query1 = "UPDATE users SET money=money-50 WHERE name = 'user1'";
$query2 = "UPDATE users SET money=money+50 WHERE name = 'user2'";
The problem is if connection breaks between these two queries, the money just get lost, first user looses them, the other one doesn't get them. I could use InnoDB or BDB to start transaction, and rollback both queries on error in any of them, but still i have this asignment for MyISAM.
How this problem normally get solved?
Firstly, as several people have mentioned this isn't a good idea, and you shouldn't do this in any real system. But I assume this is a homework assignment, and the goal is to figure out how to fake atomic updates in a system that doesn't support it.
You can do it by basically creating your own transaction log system. The ideas is to create a set of idempotent operations, i.e., operations you can repeat again if they get interrupted, and get the correct result. Addition and subtraction are not idempotent, because if you add or subtract multiple times, you'll end up with a different result. Assignment is. So you can do something like this:
CREATE TABLE transactions(
id int auto_increment primary key,
committed boolean default false,
user1 varchar(255),
user2 varchar(255),
balance1 int,
balance2 int,
index (id, committed)
);
Then your "transaction" looks something like this:
INSERT INTO transactions(user1, user2, balance1, balance2)
VALUES(
'user1',
'user2',
(SELECT money + 50 FROM users where name='user1'),
(SELECT money - 50 FROM users where name='user2')
);
You then have a separate system or function that commits transactions. Find the first uncommitted transaction, update both the accounts with the stored values, and mark the transaction as committed. If the process gets interrupted, you'll be able to recover because you can play back transactions and there will be no harm done if you play back a transaction more than once.
MyISAM does not provide any mechanism for handling this internally. If you need atomicity, use an engine which does support transactions, such as the InnoDB engine. This is the usual and accepted solution to this kind of problem.
Another possibility would be to store transactions rather than totals.
CREATE TABLE users(name VARCHAR(255), PRIMARY KEY (name));
CREATE TABLE transactions(from_user VARCHAR(255), to_user VARCHAR(255), amount INT);
This means transactions are now a single query, but finding the current balance is more difficult.
The transaction:
INSERT INTO transactions VALUES('user1', 'user2', 50);
Finding the balance is harder:
SELECT (SELECT SUM(amount) FROM transactions WHERE to_user='user2') - (SELECT SUM(amount) FROM transactions WHERE from_user='user2')
Since the record can't be only half inserted, this resolves the issue. Note I didn't say this was a good idea. Use a transactional database.
Note: There is one more way to do this which is rather ugly but should still be atomic with MyISAM.
UPDATE users SET money=IF(name='user1',money-50, money+50) WHERE name='user1' OR name='user2';
UPDATE users u1
INNER JOIN users u2
SET u1.money=u1.money-50, u2.money=u2.money+50
WHERE u1.name = 'user1'
AND u2.name = 'user2'

Categories